From ef0e5a642d33ac62f070c45a61cb42647b2744cd Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Mon, 12 Sep 2022 13:31:45 +0200 Subject: Updated Upstream (Bukkit/CraftBukkit/Spigot) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 9ae3f10f SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API 48c0c547 PR-786: Add methods to get sounds from entities CraftBukkit Changes: 5cc9c022a SPIGOT-7152: Handle hand item changing during air interact event 4ffa1acf6 SPIGOT-7154: Players get kicked when interacting with a conversation 4daa21123 SPIGOT-3842: Add Player#fireworkBoost() and expand Firework API e5d6a9bbf PR-1100: Add methods to get sounds from entities b7e9f1c8b SPIGOT-7146: Reduce use of Material switch in ItemMeta Spigot Changes: 4c157bb4 Rebuild patches --- patches/api/0005-Adventure.patch | 6 +- patches/api/0012-Entity-Origin-API.patch | 4 +- ...-for-working-with-arrows-stuck-in-living-.patch | 4 +- patches/api/0048-Fireworks-API-s.patch | 11 +- patches/api/0054-Fix-upstream-javadocs.patch | 8 +- .../api/0060-Shoulder-Entities-Release-API.patch | 4 +- patches/api/0061-Entity-fromMobSpawner.patch | 4 +- patches/api/0067-LivingEntity-setKiller.patch | 4 +- .../0094-Add-openSign-method-to-HumanEntity.patch | 4 +- ...0-Make-shield-blocking-delay-configurable.patch | 4 +- ...117-LivingEntity-Hand-Raised-Item-Use-API.patch | 8 +- .../api/0120-InventoryCloseEvent-Reason-API.patch | 4 +- patches/api/0122-Entity-getChunk-API.patch | 4 +- patches/api/0146-Async-Chunks-API.patch | 4 +- ...7-Add-ray-tracing-methods-to-LivingEntity.patch | 4 +- patches/api/0151-Mob-Pathfinding-API.patch | 8 +- .../0159-Add-LivingEntity-getTargetEntity.patch | 4 +- patches/api/0160-Add-sun-related-API.patch | 4 +- patches/api/0175-Entity-getEntitySpawnReason.patch | 4 +- patches/api/0188-Entity-Jump-API.patch | 4 +- patches/api/0204-Potential-bed-API.patch | 4 +- patches/api/0212-Add-entity-liquid-API.patch | 4 +- ...d-playPickupItemAnimation-to-LivingEntity.patch | 4 +- ...itional-open-container-api-to-HumanEntity.patch | 4 +- patches/api/0225-Entity-isTicking.patch | 4 +- ...-Javadocs-for-Entity.getEntitySpawnReason.patch | 4 +- .../0235-Add-LivingEntity-clearActiveItem.patch | 4 +- .../0241-Expose-LivingEntity-hurt-direction.patch | 4 +- patches/api/0271-Expose-Tracked-Players.patch | 4 +- .../0280-add-isDeeplySleeping-to-HumanEntity.patch | 4 +- patches/api/0300-Add-Mob-lookAt-API.patch | 4 +- .../api/0310-Add-more-line-of-sight-methods.patch | 4 +- patches/api/0312-Missing-Entity-Behavior-API.patch | 8 +- patches/api/0316-Stinger-API.patch | 4 +- patches/api/0331-Left-handed-API.patch | 8 +- .../0339-Add-Raw-Byte-Entity-Serialization.patch | 4 +- patches/api/0346-Entity-powdered-snow-API.patch | 4 +- patches/api/0359-Freeze-Tick-Lock-API.patch | 4 +- patches/api/0371-More-Projectile-API.patch | 50 +- patches/api/0385-Add-Player-getFishHook.patch | 4 +- patches/api/0386-More-Teleport-API.patch | 4 +- patches/api/0389-Collision-API.patch | 4 +- patches/server/0008-MC-Utils.patch | 10 +- patches/server/0009-Adventure.patch | 52 +- patches/server/0012-Timings-v2.patch | 26 +- ...nerfed-mobs-to-jump-and-take-water-damage.patch | 4 +- ...rable-despawn-distances-for-living-entiti.patch | 4 +- .../server/0032-Player-affects-spawning-API.patch | 4 +- patches/server/0035-Entity-Origin-API.patch | 10 +- .../0055-Ensure-commands-are-not-ran-async.patch | 8 +- ...t-more-informative-in-maxHealth-exception.patch | 4 +- ...061-Add-configurable-portal-search-radius.patch | 4 +- patches/server/0062-Add-velocity-warnings.patch | 4 +- ...le-Scoreboards-for-non-players-by-default.patch | 6 +- ...-for-working-with-arrows-stuck-in-living-.patch | 4 +- .../server/0069-Complete-resource-pack-API.patch | 4 +- .../0076-Custom-replacement-for-eaten-items.patch | 8 +- ...-health-absorb-values-and-repair-bad-data.patch | 4 +- .../0087-Add-PlayerUseUnknownEntityEvent.patch | 4 +- .../0127-Properly-fix-item-duplication-bug.patch | 4 +- patches/server/0128-Firework-API-s.patch | 14 +- ...n-t-allow-entities-to-ride-themselves-572.patch | 4 +- patches/server/0134-Cap-Entity-Collisions.patch | 6 +- ...to-make-parrots-stay-on-shoulders-despite.patch | 4 +- patches/server/0143-Item-canEntityPickup.patch | 4 +- .../0149-Shoulder-Entities-Release-API.patch | 4 +- patches/server/0152-Entity-fromMobSpawner.patch | 10 +- patches/server/0158-LivingEntity-setKiller.patch | 14 +- ...5-handle-ServerboundKeepAlivePacket-async.patch | 4 +- .../server/0169-Add-PlayerArmorChangeEvent.patch | 4 +- patches/server/0182-Add-ArmorStand-Item-Meta.patch | 22 +- .../0195-Add-openSign-method-to-HumanEntity.patch | 4 +- ...-that-allowed-colored-signs-to-be-created.patch | 4 +- patches/server/0211-Fix-CraftEntity-hashCode.patch | 4 +- ...4-Make-shield-blocking-delay-configurable.patch | 8 +- ...17-Implement-EntityKnockbackByEntityEvent.patch | 6 +- ...219-LivingEntity-Hand-Raised-Item-Use-API.patch | 4 +- .../0223-InventoryCloseEvent-Reason-API.patch | 10 +- ...yer-inventory-when-cancelling-PlayerInter.patch | 4 +- ...7-add-more-information-to-Entity.toString.patch | 4 +- ...0-Add-ray-tracing-methods-to-LivingEntity.patch | 10 +- patches/server/0262-Improve-death-events.patch | 6 +- patches/server/0264-Mob-Pathfinding-API.patch | 4 +- .../0275-Add-LivingEntity-getTargetEntity.patch | 8 +- patches/server/0276-Add-sun-related-API.patch | 4 +- ...orce-entity-dismount-during-teleportation.patch | 14 +- .../0306-Limit-Client-Sign-length-more.patch | 4 +- .../server/0312-Entity-getEntitySpawnReason.patch | 10 +- ...e-entity-Metadata-for-all-tracked-players.patch | 4 +- ...335-Prevent-consuming-the-wrong-itemstack.patch | 8 +- .../0336-Dont-send-unnecessary-sign-update.patch | 4 +- ...onError-when-player-hand-set-to-empty-typ.patch | 23 - .../0338-Flat-bedrock-generator-settings.patch | 196 + .../0339-Flat-bedrock-generator-settings.patch | 196 - ...c-chunk-loads-when-villagers-try-to-find-.patch | 20 + ...MC-145656-Fix-Follow-Range-Initial-Target.patch | 50 + ...c-chunk-loads-when-villagers-try-to-find-.patch | 20 - .../0341-Duplicate-UUID-Resolve-Option.patch | 138 + ...MC-145656-Fix-Follow-Range-Initial-Target.patch | 50 - .../0342-Duplicate-UUID-Resolve-Option.patch | 138 - patches/server/0342-Optimize-Hoppers.patch | 460 + patches/server/0343-Optimize-Hoppers.patch | 460 - ...343-PlayerDeathEvent-shouldDropExperience.patch | 19 + ...344-PlayerDeathEvent-shouldDropExperience.patch | 19 - ...ees-loading-chunks-checking-hive-position.patch | 18 + ...load-Chunks-from-Hoppers-and-other-things.patch | 32 + ...ees-loading-chunks-checking-hive-position.patch | 18 - ...load-Chunks-from-Hoppers-and-other-things.patch | 32 - ...st-serializing-mismatching-chunk-coordina.patch | 57 + ...st-serializing-mismatching-chunk-coordina.patch | 57 - ...47-Optimise-IEntityAccess-getPlayerByUUID.patch | 26 + .../0348-Fix-items-not-falling-correctly.patch | 42 + ...48-Optimise-IEntityAccess-getPlayerByUUID.patch | 26 - .../0349-Fix-items-not-falling-correctly.patch | 42 - patches/server/0349-Lag-compensate-eating.patch | 75 + patches/server/0350-Lag-compensate-eating.patch | 75 - ...-Optimize-call-to-getFluid-for-explosions.patch | 19 + ...rework-in-stack-not-having-effects-when-d.patch | 23 + ...-Optimize-call-to-getFluid-for-explosions.patch | 19 - .../0352-Add-effect-to-block-break-naturally.patch | 37 + ...rework-in-stack-not-having-effects-when-d.patch | 23 - .../0353-Add-effect-to-block-break-naturally.patch | 37 - .../server/0353-Entity-Activation-Range-2.0.patch | 808 + .../server/0354-Entity-Activation-Range-2.0.patch | 808 - .../server/0354-Increase-Light-Queue-Size.patch | 28 + patches/server/0355-Fix-Light-Command.patch | 186 + .../server/0355-Increase-Light-Queue-Size.patch | 28 - patches/server/0356-Anti-Xray.patch | 1607 ++ patches/server/0356-Fix-Light-Command.patch | 186 - patches/server/0357-Anti-Xray.patch | 1607 -- ...7-Implement-alternative-item-despawn-rate.patch | 63 + ...8-Implement-alternative-item-despawn-rate.patch | 63 - .../server/0358-Tracking-Range-Improvements.patch | 75 + ...59-Fix-items-vanishing-through-end-portal.patch | 28 + .../server/0359-Tracking-Range-Improvements.patch | 75 - ...60-Fix-items-vanishing-through-end-portal.patch | 28 - ...-implement-optional-per-player-mob-spawns.patch | 578 + ...oid-hopper-searches-if-there-are-no-items.patch | 124 + ...-implement-optional-per-player-mob-spawns.patch | 578 - ...oid-hopper-searches-if-there-are-no-items.patch | 124 - ...Bees-get-gravity-in-void.-Fixes-MC-167279.patch | 34 + ...Bees-get-gravity-in-void.-Fixes-MC-167279.patch | 34 - ...timise-getChunkAt-calls-for-loaded-chunks.patch | 66 + .../0364-Add-debug-for-sync-chunk-loads.patch | 328 + ...timise-getChunkAt-calls-for-loaded-chunks.patch | 66 - .../0365-Add-debug-for-sync-chunk-loads.patch | 328 - .../0365-Remove-garbage-Java-version-check.patch | 21 + patches/server/0366-Add-ThrownEggHatchEvent.patch | 27 + .../0366-Remove-garbage-Java-version-check.patch | 21 - patches/server/0367-Add-ThrownEggHatchEvent.patch | 27 - patches/server/0367-Entity-Jump-API.patch | 73 + ...option-to-nerf-pigmen-from-nether-portals.patch | 49 + patches/server/0368-Entity-Jump-API.patch | 73 - ...option-to-nerf-pigmen-from-nether-portals.patch | 49 - .../server/0369-Make-the-GUI-graph-fancier.patch | 398 + .../server/0370-Make-the-GUI-graph-fancier.patch | 398 - .../0370-add-hand-to-BlockMultiPlaceEvent.patch | 30 + ...ate-tripwire-hook-placement-before-update.patch | 18 + .../0371-add-hand-to-BlockMultiPlaceEvent.patch | 30 - ...tion-to-allow-iron-golems-to-spawn-in-air.patch | 19 + ...ate-tripwire-hook-placement-before-update.patch | 18 - ...tion-to-allow-iron-golems-to-spawn-in-air.patch | 19 - ...rable-chance-of-villager-zombie-infection.patch | 30 + ...rable-chance-of-villager-zombie-infection.patch | 30 - patches/server/0374-Optimise-Chunk-getFluid.patch | 61 + patches/server/0375-Optimise-Chunk-getFluid.patch | 61 - ...ots-verbose-world-setting-to-false-by-def.patch | 19 + .../0376-Add-tick-times-API-and-mspt-command.patch | 207 + ...ots-verbose-world-setting-to-false-by-def.patch | 19 - .../0377-Add-tick-times-API-and-mspt-command.patch | 207 - .../0377-Expose-MinecraftServer-isRunning.patch | 22 + ...0378-Add-Raw-Byte-ItemStack-Serialization.patch | 65 + .../0378-Expose-MinecraftServer-isRunning.patch | 22 - ...0379-Add-Raw-Byte-ItemStack-Serialization.patch | 65 - ...trol-spawn-settings-and-per-player-option.patch | 96 + ...trol-spawn-settings-and-per-player-option.patch | 96 - ...te-Connections-shouldn-t-hold-up-shutdown.patch | 25 + ...ot-allow-bees-to-load-chunks-for-beehives.patch | 42 + ...te-Connections-shouldn-t-hold-up-shutdown.patch | 25 - ...ot-allow-bees-to-load-chunks-for-beehives.patch | 42 - ...ouble-PlayerChunkMap-adds-crashing-server.patch | 48 + patches/server/0383-Don-t-tick-dead-players.patch | 21 + ...ouble-PlayerChunkMap-adds-crashing-server.patch | 48 - ...4-Dead-Player-s-shouldn-t-be-able-to-move.patch | 21 + patches/server/0384-Don-t-tick-dead-players.patch | 21 - ...5-Dead-Player-s-shouldn-t-be-able-to-move.patch | 21 - ...385-Optimize-Collision-to-not-load-chunks.patch | 111 + ...on-t-move-existing-players-to-world-spawn.patch | 46 + ...386-Optimize-Collision-to-not-load-chunks.patch | 111 - ...on-t-move-existing-players-to-world-spawn.patch | 46 - ...ize-GoalSelector-Goal.Flag-Set-operations.patch | 169 + .../server/0388-Improved-Watchdog-Support.patch | 591 + ...ize-GoalSelector-Goal.Flag-Set-operations.patch | 169 - .../server/0389-Improved-Watchdog-Support.patch | 591 - patches/server/0389-Optimize-Pathfinding.patch | 43 + patches/server/0390-Optimize-Pathfinding.patch | 43 - .../0390-Reduce-Either-Optional-allocation.patch | 48 + .../0391-Reduce-Either-Optional-allocation.patch | 48 - ...Reduce-memory-footprint-of-NBTTagCompound.patch | 50 + ...2-Prevent-opening-inventories-when-frozen.patch | 50 + ...Reduce-memory-footprint-of-NBTTagCompound.patch | 50 - .../0393-Optimise-ArraySetSorted-removeIf.patch | 88 + ...3-Prevent-opening-inventories-when-frozen.patch | 50 - ...t-run-entity-collision-code-if-not-needed.patch | 30 + .../0394-Optimise-ArraySetSorted-removeIf.patch | 88 - ...t-run-entity-collision-code-if-not-needed.patch | 30 - .../0395-Implement-Player-Client-Options-API.patch | 151 + ...-if-player-is-attempted-to-be-removed-fro.patch | 23 + .../0396-Implement-Player-Client-Options-API.patch | 151 - ...-if-player-is-attempted-to-be-removed-fro.patch | 23 - ...7-Fix-Chunk-Post-Processing-deadlock-risk.patch | 73 + ...8-Fix-Chunk-Post-Processing-deadlock-risk.patch | 73 - ...anding-Broken-behavior-of-PlayerJoinEvent.patch | 110 + ...anding-Broken-behavior-of-PlayerJoinEvent.patch | 110 - ...0399-Load-Chunks-for-Login-Asynchronously.patch | 280 + ...0400-Load-Chunks-for-Login-Asynchronously.patch | 280 - ...-to-spawn-point-if-spawn-in-unloaded-worl.patch | 27 + ...-Add-PlayerAttackEntityCooldownResetEvent.patch | 28 + ...-to-spawn-point-if-spawn-in-unloaded-worl.patch | 27 - ...-Add-PlayerAttackEntityCooldownResetEvent.patch | 28 - ...-Don-t-fire-BlockFade-on-worldgen-threads.patch | 28 + ...d-phantom-creative-and-insomniac-controls.patch | 43 + ...-Don-t-fire-BlockFade-on-worldgen-threads.patch | 28 - ...d-phantom-creative-and-insomniac-controls.patch | 43 - ...s-item-duplication-issues-and-teleport-is.patch | 167 + ...s-item-duplication-issues-and-teleport-is.patch | 167 - patches/server/0405-Villager-Restocks-API.patch | 29 + ...date-PickItem-Packet-and-kick-for-invalid.patch | 26 + patches/server/0406-Villager-Restocks-API.patch | 29 - patches/server/0407-Expose-game-version.patch | 24 + ...date-PickItem-Packet-and-kick-for-invalid.patch | 26 - patches/server/0408-Expose-game-version.patch | 24 - .../server/0408-Optimize-Voxel-Shape-Merging.patch | 121 + .../server/0409-Optimize-Voxel-Shape-Merging.patch | 121 - ...n-JDK-per-thread-native-byte-buffer-cache.patch | 30 + ...n-JDK-per-thread-native-byte-buffer-cache.patch | 30 - patches/server/0410-misc-debugging-dumps.patch | 87 + .../0411-Prevent-teleporting-dead-entities.patch | 24 + patches/server/0411-misc-debugging-dumps.patch | 87 - ...-stacktraces-in-log-messages-crash-report.patch | 611 + .../0412-Prevent-teleporting-dead-entities.patch | 24 - ...-stacktraces-in-log-messages-crash-report.patch | 611 - patches/server/0413-Implement-Mob-Goal-API.patch | 915 + .../server/0414-Add-villager-reputation-API.patch | 127 + patches/server/0414-Implement-Mob-Goal-API.patch | 915 - .../server/0415-Add-villager-reputation-API.patch | 127 - ...n-for-maximum-exp-value-when-merging-orbs.patch | 42 + patches/server/0416-ExperienceOrbMergeEvent.patch | 23 + ...n-for-maximum-exp-value-when-merging-orbs.patch | 42 - patches/server/0417-ExperienceOrbMergeEvent.patch | 23 - .../0417-Fix-PotionEffect-ignores-icon-flag.patch | 19 + .../0418-Fix-PotionEffect-ignores-icon-flag.patch | 19 - ...imize-brigadier-child-sorting-performance.patch | 28 + ...imize-brigadier-child-sorting-performance.patch | 28 - patches/server/0419-Potential-bed-API.patch | 44 + patches/server/0420-Potential-bed-API.patch | 44 - ...0420-Wait-for-Async-Tasks-during-shutdown.patch | 63 + ...tyRaider-respects-game-and-entity-rules-f.patch | 19 + ...0421-Wait-for-Async-Tasks-during-shutdown.patch | 63 - ...tyRaider-respects-game-and-entity-rules-f.patch | 19 - ...rock-and-End-Portal-Frames-from-being-des.patch | 169 + ...rock-and-End-Portal-Frames-from-being-des.patch | 169 - ...-MutableInt-allocations-from-light-engine.patch | 50 + ...-MutableInt-allocations-from-light-engine.patch | 50 - ...uce-allocation-of-Vec3D-by-entity-tracker.patch | 58 + .../server/0425-Ensure-safe-gateway-teleport.patch | 26 + ...uce-allocation-of-Vec3D-by-entity-tracker.patch | 58 - ...option-for-console-having-all-permissions.patch | 47 + .../server/0426-Ensure-safe-gateway-teleport.patch | 26 - ...option-for-console-having-all-permissions.patch | 47 - ...yPlayerCloseEnoughForSpawning-to-use-dist.patch | 352 + ...yPlayerCloseEnoughForSpawning-to-use-dist.patch | 352 - ...e-distance-map-to-optimise-entity-tracker.patch | 395 + ...ServerLevels-chunk-level-checking-methods.patch | 69 + ...e-distance-map-to-optimise-entity-tracker.patch | 395 - ...430-Fix-villager-trading-demand-MC-163962.patch | 20 + ...ServerLevels-chunk-level-checking-methods.patch | 69 - ...431-Fix-villager-trading-demand-MC-163962.patch | 20 - .../server/0431-Maps-shouldn-t-load-chunks.patch | 32 + .../server/0432-Maps-shouldn-t-load-chunks.patch | 32 - ...sed-lookup-for-Treasure-Maps-Fixes-lag-fr.patch | 27 + ...heduler-runTaskTimerAsynchronously-Plugin.patch | 21 + ...sed-lookup-for-Treasure-Maps-Fixes-lag-fr.patch | 27 - ...heduler-runTaskTimerAsynchronously-Plugin.patch | 21 - ...ix-piston-physics-inconsistency-MC-188840.patch | 80 + ...ix-piston-physics-inconsistency-MC-188840.patch | 80 - patches/server/0435-Fix-sand-duping.patch | 37 + ...ix-missing-chunks-due-to-integer-overflow.patch | 29 + patches/server/0436-Fix-sand-duping.patch | 37 - ...ix-missing-chunks-due-to-integer-overflow.patch | 29 - ...ition-desync-in-playerconnection-causing-.patch | 31 + ...y-getHolder-method-without-block-snapshot.patch | 42 + ...ition-desync-in-playerconnection-causing-.patch | 31 - patches/server/0439-Improve-Arrow-API.patch | 38 + ...y-getHolder-method-without-block-snapshot.patch | 42 - ...-and-implement-PlayerRecipeBookClickEvent.patch | 29 + patches/server/0440-Improve-Arrow-API.patch | 38 - ...-and-implement-PlayerRecipeBookClickEvent.patch | 29 - .../0441-Hide-sync-chunk-writes-behind-flag.patch | 23 + .../0442-Add-permission-for-command-blocks.patch | 79 + .../0442-Hide-sync-chunk-writes-behind-flag.patch | 23 - .../0443-Add-permission-for-command-blocks.patch | 79 - ...43-Ensure-Entity-AABB-s-are-never-invalid.patch | 46 + ...44-Ensure-Entity-AABB-s-are-never-invalid.patch | 46 - ...r-World-Difficulty-Remembering-Difficulty.patch | 118 + ...r-World-Difficulty-Remembering-Difficulty.patch | 118 - patches/server/0445-Paper-dumpitem-command.patch | 92 + .../0446-Don-t-allow-null-UUID-s-for-chat.patch | 23 + patches/server/0446-Paper-dumpitem-command.patch | 92 - .../0447-Don-t-allow-null-UUID-s-for-chat.patch | 23 - ...prove-Legacy-Component-serialization-size.patch | 56 + ...prove-Legacy-Component-serialization-size.patch | 56 - .../0448-Optimize-Bit-Operations-by-inlining.patch | 213 + ...9-Add-Plugin-Tickets-to-API-Chunk-Methods.patch | 121 + .../0449-Optimize-Bit-Operations-by-inlining.patch | 213 - ...0-Add-Plugin-Tickets-to-API-Chunk-Methods.patch | 121 - .../0450-incremental-chunk-and-player-saving.patch | 375 + ...n-write-operations-for-updating-light-dat.patch | 297 + .../0451-incremental-chunk-and-player-saving.patch | 375 - ...n-write-operations-for-updating-light-dat.patch | 297 - .../0452-Support-old-UUID-format-for-NBT.patch | 63 + ...lean-up-duplicated-GameProfile-Properties.patch | 47 + .../0453-Support-old-UUID-format-for-NBT.patch | 63 - ...lean-up-duplicated-GameProfile-Properties.patch | 47 - ...54-Convert-legacy-attributes-in-Item-Meta.patch | 44 + ...55-Convert-legacy-attributes-in-Item-Meta.patch | 44 - .../0455-Remove-some-streams-from-structures.patch | 48 + .../0456-Remove-some-streams-from-structures.patch | 48 - ...eams-from-classes-related-villager-gossip.patch | 71 + ...eams-from-classes-related-villager-gossip.patch | 71 - .../0457-Support-components-in-ItemMeta.patch | 83 + ...tityTargetLivingEntityEvent-for-1.16-mobs.patch | 59 + .../0458-Support-components-in-ItemMeta.patch | 83 - patches/server/0459-Add-entity-liquid-API.patch | 40 + ...tityTargetLivingEntityEvent-for-1.16-mobs.patch | 59 - patches/server/0460-Add-entity-liquid-API.patch | 40 - ...460-Update-itemstack-legacy-name-and-lore.patch | 63 + ...61-Spawn-player-in-correct-world-on-login.patch | 30 + ...461-Update-itemstack-legacy-name-and-lore.patch | 63 - patches/server/0462-Add-PrepareResultEvent.patch | 152 + ...62-Spawn-player-in-correct-world-on-login.patch | 30 - patches/server/0463-Add-PrepareResultEvent.patch | 152 - ...-chunk-for-portal-on-world-gen-entity-add.patch | 19 + ...-chunk-for-portal-on-world-gen-entity-add.patch | 19 - ...ptimize-NetworkManager-Exception-Handling.patch | 67 + ...ptimize-NetworkManager-Exception-Handling.patch | 67 - ...e-advancement-data-player-iteration-to-be.patch | 54 + ...466-Fix-arrows-never-despawning-MC-125757.patch | 22 + ...e-advancement-data-player-iteration-to-be.patch | 54 - ...467-Fix-arrows-never-despawning-MC-125757.patch | 22 - ...-Safe-Vanilla-Command-permission-checking.patch | 53 + patches/server/0468-Fix-SPIGOT-5989.patch | 61 + ...-Safe-Vanilla-Command-permission-checking.patch | 53 - ...T-5824-Bukkit-world-container-is-not-used.patch | 50 + patches/server/0469-Fix-SPIGOT-5989.patch | 61 - ...T-5824-Bukkit-world-container-is-not-used.patch | 50 - ...PIGOT-5885-Unable-to-disable-advancements.patch | 18 + ...mentDataPlayer-leak-due-from-quitting-ear.patch | 74 + ...PIGOT-5885-Unable-to-disable-advancements.patch | 18 - ...-strikeLighting-call-to-World-spigot-stri.patch | 19 + ...mentDataPlayer-leak-due-from-quitting-ear.patch | 74 - ...-strikeLighting-call-to-World-spigot-stri.patch | 19 - ...0473-Fix-some-rails-connecting-improperly.patch | 84 + ...gex-mistake-in-CB-NBT-int-deserialization.patch | 27 + ...0474-Fix-some-rails-connecting-improperly.patch | 84 - ...the-server-load-chunks-from-newer-version.patch | 37 + ...gex-mistake-in-CB-NBT-int-deserialization.patch | 27 - patches/server/0476-Brand-support.patch | 75 + ...the-server-load-chunks-from-newer-version.patch | 37 - patches/server/0477-Add-setMaxPlayers-API.patch | 37 + patches/server/0477-Brand-support.patch | 75 - ...d-playPickupItemAnimation-to-LivingEntity.patch | 21 + patches/server/0478-Add-setMaxPlayers-API.patch | 37 - ...d-playPickupItemAnimation-to-LivingEntity.patch | 21 - .../server/0479-Don-t-require-FACING-data.patch | 37 + .../server/0480-Don-t-require-FACING-data.patch | 37 - ...nChangeEvent-not-firing-for-all-use-cases.patch | 39 + patches/server/0481-Add-moon-phase-API.patch | 22 + ...nChangeEvent-not-firing-for-all-use-cases.patch | 39 - patches/server/0482-Add-moon-phase-API.patch | 22 - ...482-Improve-Chunk-Status-Transition-Speed.patch | 81 + ...483-Improve-Chunk-Status-Transition-Speed.patch | 81 - ...event-headless-pistons-from-being-created.patch | 27 + patches/server/0484-Add-BellRingEvent.patch | 28 + ...event-headless-pistons-from-being-created.patch | 27 - patches/server/0485-Add-BellRingEvent.patch | 28 - ...0485-Add-zombie-targets-turtle-egg-config.patch | 19 + ...0486-Add-zombie-targets-turtle-egg-config.patch | 19 - patches/server/0486-Buffer-joins-to-world.patch | 37 + patches/server/0487-Buffer-joins-to-world.patch | 37 - .../0487-Eigencraft-redstone-implementation.patch | 1139 + .../0488-Eigencraft-redstone-implementation.patch | 1139 - ...-colors-not-working-in-some-kick-messages.patch | 39 + ...-colors-not-working-in-some-kick-messages.patch | 39 - ...ortalCreateEvent-needs-to-know-its-entity.patch | 138 + patches/server/0490-Fix-CraftTeam-null-check.patch | 19 + ...ortalCreateEvent-needs-to-know-its-entity.patch | 138 - patches/server/0491-Add-more-Evoker-API.patch | 35 + patches/server/0491-Fix-CraftTeam-null-check.patch | 19 - .../0492-Add-methods-to-get-translation-keys.patch | 174 + patches/server/0492-Add-more-Evoker-API.patch | 35 - .../0493-Add-methods-to-get-translation-keys.patch | 174 - ...3-Create-HoverEvent-from-ItemStack-Entity.patch | 51 + patches/server/0494-Cache-block-data-strings.patch | 62 + ...4-Create-HoverEvent-from-ItemStack-Entity.patch | 51 - patches/server/0495-Cache-block-data-strings.patch | 62 - ...Teleportation-and-cancel-velocity-if-tele.patch | 83 + ...itional-open-container-api-to-HumanEntity.patch | 81 + ...Teleportation-and-cancel-velocity-if-tele.patch | 83 - ...itional-open-container-api-to-HumanEntity.patch | 81 - ...he-DataFixerUpper-Rewrite-Rules-on-demand.patch | 54 + ...he-DataFixerUpper-Rewrite-Rules-on-demand.patch | 54 - ...k-drop-capture-to-capture-all-items-added.patch | 43 + ...rk-dirty-in-invalid-locations-SPIGOT-6086.patch | 18 + ...k-drop-capture-to-capture-all-items-added.patch | 43 - ...rk-dirty-in-invalid-locations-SPIGOT-6086.patch | 18 - ...Entity-Counter-to-allow-plugins-to-use-va.patch | 37 + ...Entity-Counter-to-allow-plugins-to-use-va.patch | 37 - ...azily-track-plugin-scoreboards-by-default.patch | 76 + patches/server/0502-Entity-isTicking.patch | 42 + ...azily-track-plugin-scoreboards-by-default.patch | 76 - patches/server/0503-Entity-isTicking.patch | 42 - ...cking-non-whitelisted-player-when-white-l.patch | 27 + ...04-Fix-Concurrency-issue-in-ShufflingList.patch | 69 + ...cking-non-whitelisted-player-when-white-l.patch | 27 - ...05-Fix-Concurrency-issue-in-ShufflingList.patch | 69 - ...0505-Reset-Ender-Crystals-on-Dragon-Spawn.patch | 24 + ...ix-for-large-move-vectors-crashing-server.patch | 92 + ...0506-Reset-Ender-Crystals-on-Dragon-Spawn.patch | 24 - ...ix-for-large-move-vectors-crashing-server.patch | 92 - patches/server/0507-Optimise-getType-calls.patch | 94 + patches/server/0508-Optimise-getType-calls.patch | 94 - patches/server/0508-Villager-resetOffers.patch | 40 + ...e-inlinig-for-some-hot-IBlockData-methods.patch | 96 + patches/server/0509-Villager-resetOffers.patch | 40 - ...e-inlinig-for-some-hot-IBlockData-methods.patch | 96 - ...ck-place-order-when-capturing-blockstates.patch | 24 + ...duce-blockpos-allocation-from-pathfinding.patch | 28 + ...ck-place-order-when-capturing-blockstates.patch | 24 - ...Fix-item-locations-dropped-from-campfires.patch | 24 + ...duce-blockpos-allocation-from-pathfinding.patch | 28 - ...Fix-item-locations-dropped-from-campfires.patch | 24 - patches/server/0513-Player-elytra-boost-API.patch | 31 + .../0514-Fixed-TileEntityBell-memory-leak.patch | 39 + patches/server/0514-Player-elytra-boost-API.patch | 31 - ...-bubbling-up-when-item-stack-is-empty-in-.patch | 46 + .../0515-Fixed-TileEntityBell-memory-leak.patch | 39 - .../0516-Add-getOfflinePlayerIfCached-String.patch | 39 + ...-bubbling-up-when-item-stack-is-empty-in-.patch | 46 - .../0517-Add-getOfflinePlayerIfCached-String.patch | 39 - patches/server/0517-Add-ignore-discounts-API.patch | 154 + patches/server/0518-Add-ignore-discounts-API.patch | 154 - .../0518-Toggle-for-removing-existing-dragon.patch | 19 + ...519-Fix-client-lag-on-advancement-loading.patch | 35 + .../0519-Toggle-for-removing-existing-dragon.patch | 19 - ...520-Fix-client-lag-on-advancement-loading.patch | 35 - .../server/0520-Item-no-age-no-player-pickup.patch | 50 + .../server/0521-Item-no-age-no-player-pickup.patch | 50 - ...thfinder-Remove-Streams-Optimized-collect.patch | 135 + .../0522-Beacon-API-custom-effect-ranges.patch | 131 + ...thfinder-Remove-Streams-Optimized-collect.patch | 135 - patches/server/0523-Add-API-for-quit-reason.patch | 63 + .../0523-Beacon-API-custom-effect-ranges.patch | 131 - patches/server/0524-Add-API-for-quit-reason.patch | 63 - ...andering-Trader-spawn-rate-config-options.patch | 87 + ...andering-Trader-spawn-rate-config-options.patch | 87 - patches/server/0525-Expose-world-spawn-angle.patch | 19 + patches/server/0526-Add-Destroy-Speed-API.patch | 38 + patches/server/0526-Expose-world-spawn-angle.patch | 19 - patches/server/0527-Add-Destroy-Speed-API.patch | 38 - ...Player-spawnParticle-x-y-z-precision-loss.patch | 19 + .../0528-Add-LivingEntity-clearActiveItem.patch | 24 + ...Player-spawnParticle-x-y-z-precision-loss.patch | 19 - .../0529-Add-LivingEntity-clearActiveItem.patch | 24 - .../server/0529-Add-PlayerItemCooldownEvent.patch | 27 + .../server/0530-Add-PlayerItemCooldownEvent.patch | 27 - ...ly-improve-performance-of-the-end-generat.patch | 63 + patches/server/0531-More-lightning-API.patch | 49 + ...ly-improve-performance-of-the-end-generat.patch | 63 - ...mbing-should-not-bypass-cramming-gamerule.patch | 157 + patches/server/0532-More-lightning-API.patch | 49 - ...-Added-missing-default-perms-for-commands.patch | 206 + ...mbing-should-not-bypass-cramming-gamerule.patch | 157 - .../server/0534-Add-PlayerShearBlockEvent.patch | 70 + ...-Added-missing-default-perms-for-commands.patch | 206 - .../server/0535-Add-PlayerShearBlockEvent.patch | 70 - ...x-curing-zombie-villager-discount-exploit.patch | 29 + ...x-curing-zombie-villager-discount-exploit.patch | 29 - patches/server/0536-Limit-recipe-packets.patch | 41 + ...37-Fix-CraftSound-backwards-compatibility.patch | 21 + patches/server/0537-Limit-recipe-packets.patch | 41 - ...38-Fix-CraftSound-backwards-compatibility.patch | 21 - .../0538-Player-Chunk-Load-Unload-Events.patch | 32 + .../0539-Optimize-Dynamic-get-Missing-Keys.patch | 34 + .../0539-Player-Chunk-Load-Unload-Events.patch | 32 - .../0540-Expose-LivingEntity-hurt-direction.patch | 26 + .../0540-Optimize-Dynamic-get-Missing-Keys.patch | 34 - ...1-Add-OBSTRUCTED-reason-to-BedEnterResult.patch | 21 + .../0541-Expose-LivingEntity-hurt-direction.patch | 26 - ...2-Add-OBSTRUCTED-reason-to-BedEnterResult.patch | 21 - ...h-from-invalid-ingredient-lists-in-Villag.patch | 24 + ...-PlayerTradeEvent-and-PlayerPurchaseEvent.patch | 251 + ...h-from-invalid-ingredient-lists-in-Villag.patch | 24 - ...-PlayerTradeEvent-and-PlayerPurchaseEvent.patch | 251 - patches/server/0544-Implement-TargetHitEvent.patch | 42 + patches/server/0545-Implement-TargetHitEvent.patch | 42 - .../0545-MC-4-Fix-item-position-desync.patch | 49 + .../0546-Additional-Block-Material-API-s.patch | 40 + .../0546-MC-4-Fix-item-position-desync.patch | 49 - .../0547-Additional-Block-Material-API-s.patch | 40 - patches/server/0547-Fix-harming-potion-dupe.patch | 49 + patches/server/0548-Fix-harming-potion-dupe.patch | 49 - ...PI-to-get-Material-from-Boats-and-Minecar.patch | 62 + patches/server/0549-Cache-burn-durations.patch | 36 + ...PI-to-get-Material-from-Boats-and-Minecar.patch | 62 - ...ling-mob-spawner-spawn-egg-transformation.patch | 19 + patches/server/0550-Cache-burn-durations.patch | 36 - ...ling-mob-spawner-spawn-egg-transformation.patch | 19 - ...0551-Fix-Not-a-string-Map-Conversion-spam.patch | 54 + ...0552-Fix-Not-a-string-Map-Conversion-spam.patch | 54 - ...-Implement-PlayerFlowerPotManipulateEvent.patch | 37 + ...eract-event-not-being-called-in-adventure.patch | 29 + ...-Implement-PlayerFlowerPotManipulateEvent.patch | 37 - ...eract-event-not-being-called-in-adventure.patch | 29 - .../server/0554-Zombie-API-breaking-doors.patch | 22 + .../0555-Fix-nerfed-slime-when-splitting.patch | 18 + .../server/0555-Zombie-API-breaking-doors.patch | 22 - .../server/0556-Add-EntityLoadCrossbowEvent.patch | 42 + .../0556-Fix-nerfed-slime-when-splitting.patch | 18 - .../server/0557-Add-EntityLoadCrossbowEvent.patch | 42 - patches/server/0557-Guardian-beam-workaround.patch | 20 + .../0558-Added-WorldGameRuleChangeEvent.patch | 98 + patches/server/0558-Guardian-beam-workaround.patch | 20 - .../0559-Added-ServerResourcesReloadedEvent.patch | 54 + .../0559-Added-WorldGameRuleChangeEvent.patch | 98 - .../0560-Added-ServerResourcesReloadedEvent.patch | 54 - ...d-world-settings-for-mobs-picking-up-loot.patch | 32 + ...d-world-settings-for-mobs-picking-up-loot.patch | 32 - ...0561-Implemented-BlockFailedDispenseEvent.patch | 50 + .../0562-Added-PlayerLecternPageChangeEvent.patch | 46 + ...0562-Implemented-BlockFailedDispenseEvent.patch | 50 - .../0563-Added-PlayerLecternPageChangeEvent.patch | 46 - .../0563-Added-PlayerLoomPatternSelectEvent.patch | 48 + .../0564-Added-PlayerLoomPatternSelectEvent.patch | 48 - ...564-Configurable-door-breaking-difficulty.patch | 33 + ...565-Configurable-door-breaking-difficulty.patch | 33 - ...65-Empty-commands-shall-not-be-dispatched.patch | 18 + ...66-Empty-commands-shall-not-be-dispatched.patch | 18 - ...ent-API-to-expose-exact-interaction-point.patch | 59 + ...ent-API-to-expose-exact-interaction-point.patch | 59 - patches/server/0567-Remove-stale-POIs.patch | 22 + .../server/0568-Fix-villager-boat-exploit.patch | 25 + patches/server/0568-Remove-stale-POIs.patch | 22 - patches/server/0569-Add-sendOpLevel-API.patch | 51 + .../server/0569-Fix-villager-boat-exploit.patch | 25 - patches/server/0570-Add-PaperRegistry.patch | 242 + patches/server/0570-Add-sendOpLevel-API.patch | 51 - patches/server/0571-Add-PaperRegistry.patch | 242 - .../server/0571-Add-StructuresLocateEvent.patch | 214 + .../server/0572-Add-StructuresLocateEvent.patch | 214 - ...option-for-requiring-a-player-participant.patch | 42 + ...option-for-requiring-a-player-participant.patch | 42 - ...ojectileHitEvent-call-when-fireballs-dead.patch | 21 + ...ojectileHitEvent-call-when-fireballs-dead.patch | 21 - ...-component-with-empty-text-instead-of-thr.patch | 25 + .../0575-Make-schedule-command-per-world.patch | 28 + ...-component-with-empty-text-instead-of-thr.patch | 25 - .../0576-Configurable-max-leash-distance.patch | 28 + .../0576-Make-schedule-command-per-world.patch | 28 - .../0577-Configurable-max-leash-distance.patch | 28 - .../0577-Implement-BlockPreDispenseEvent.patch | 34 + ...d-firing-of-PlayerChangeBeaconEffectEvent.patch | 40 + .../0578-Implement-BlockPreDispenseEvent.patch | 34 - ...-toggle-for-always-placing-the-dragon-egg.patch | 19 + ...d-firing-of-PlayerChangeBeaconEffectEvent.patch | 40 - ...-toggle-for-always-placing-the-dragon-egg.patch | 19 - ...-Added-PlayerStonecutterRecipeSelectEvent.patch | 51 + ...-dropLeash-variable-to-EntityUnleashEvent.patch | 140 + ...-Added-PlayerStonecutterRecipeSelectEvent.patch | 51 - ...-dropLeash-variable-to-EntityUnleashEvent.patch | 140 - ...Reset-shield-blocking-on-dimension-change.patch | 22 + ...Reset-shield-blocking-on-dimension-change.patch | 22 - patches/server/0583-add-DragonEggFormEvent.patch | 35 + patches/server/0584-EntityMoveEvent.patch | 55 + patches/server/0584-add-DragonEggFormEvent.patch | 35 - patches/server/0585-EntityMoveEvent.patch | 55 - ...n-to-disable-pathfinding-updates-on-block.patch | 26 + .../0586-Inline-shift-direction-fields.patch | 55 + ...n-to-disable-pathfinding-updates-on-block.patch | 26 - ...-Allow-adding-items-to-BlockDropItemEvent.patch | 44 + .../0587-Inline-shift-direction-fields.patch | 55 - ...-getMainThreadExecutor-to-BukkitScheduler.patch | 26 + ...-Allow-adding-items-to-BlockDropItemEvent.patch | 44 - ...-getMainThreadExecutor-to-BukkitScheduler.patch | 26 - ...iving-entity-allow-attribute-registration.patch | 60 + ...0590-fix-dead-slime-setSize-invincibility.patch | 19 + ...iving-entity-allow-attribute-registration.patch | 60 - ...etRecipes-should-return-an-immutable-list.patch | 19 + ...0591-fix-dead-slime-setSize-invincibility.patch | 19 - ...dd-support-for-hex-color-codes-in-console.patch | 326 + ...etRecipes-should-return-an-immutable-list.patch | 19 - ...dd-support-for-hex-color-codes-in-console.patch | 326 - patches/server/0593-Expose-Tracked-Players.patch | 29 + patches/server/0594-Expose-Tracked-Players.patch | 29 - .../0594-Remove-streams-from-SensorNearest.patch | 88 + .../0595-Remove-streams-from-SensorNearest.patch | 88 - ...w-proper-exception-on-empty-JsonList-file.patch | 18 + patches/server/0596-Improve-ServerGUI.patch | 377 + ...w-proper-exception-on-empty-JsonList-file.patch | 18 - patches/server/0597-Improve-ServerGUI.patch | 377 - ...-pressure-plate-EntityInteractEvent-for-i.patch | 19 + .../0598-fix-converting-txt-to-json-file.patch | 61 + ...-pressure-plate-EntityInteractEvent-for-i.patch | 19 - patches/server/0599-Add-worldborder-events.patch | 72 + .../0599-fix-converting-txt-to-json-file.patch | 61 - patches/server/0600-Add-worldborder-events.patch | 72 - .../server/0600-added-PlayerNameEntityEvent.patch | 38 + ...event-grindstones-from-overstacking-items.patch | 26 + .../server/0601-added-PlayerNameEntityEvent.patch | 38 - .../server/0602-Add-recipe-to-cook-events.patch | 43 + ...event-grindstones-from-overstacking-items.patch | 26 - patches/server/0603-Add-Block-isValidTool.patch | 20 + .../server/0603-Add-recipe-to-cook-events.patch | 43 - patches/server/0604-Add-Block-isValidTool.patch | 20 - ...Allow-using-signs-inside-spawn-protection.patch | 19 + ...Allow-using-signs-inside-spawn-protection.patch | 19 - patches/server/0605-Expand-world-key-API.patch | 84 + ...ast-alternative-constructor-for-Rotations.patch | 30 + patches/server/0606-Expand-world-key-API.patch | 84 - ...ast-alternative-constructor-for-Rotations.patch | 30 - patches/server/0607-Item-Rarity-API.patch | 61 + patches/server/0608-Item-Rarity-API.patch | 61 - ...spawnTimer-for-Wandering-Traders-spawned-.patch | 58 + ...spawnTimer-for-Wandering-Traders-spawned-.patch | 58 - ...609-copy-TESign-isEditable-from-snapshots.patch | 18 + ...carried-item-when-player-has-disconnected.patch | 27 + ...610-copy-TESign-isEditable-from-snapshots.patch | 18 - ...carried-item-when-player-has-disconnected.patch | 27 - ...d-whitelist-use-configurable-kick-message.patch | 19 + ...on-t-ignore-result-of-PlayerEditBookEvent.patch | 19 + ...d-whitelist-use-configurable-kick-message.patch | 19 - ...on-t-ignore-result-of-PlayerEditBookEvent.patch | 19 - .../0613-Entity-load-save-limit-per-chunk.patch | 58 + .../0614-Entity-load-save-limit-per-chunk.patch | 58 - patches/server/0614-Expose-protocol-version.patch | 22 + ...sole-tab-completions-for-brigadier-comman.patch | 295 + patches/server/0615-Expose-protocol-version.patch | 22 - ...sole-tab-completions-for-brigadier-comman.patch | 295 - ...layerItemConsumeEvent-cancelling-properly.patch | 22 + patches/server/0617-Add-bypass-host-check.patch | 30 + ...layerItemConsumeEvent-cancelling-properly.patch | 22 - patches/server/0618-Add-bypass-host-check.patch | 30 - .../0618-Set-area-affect-cloud-rotation.patch | 18 + .../0619-Set-area-affect-cloud-rotation.patch | 18 - .../0619-add-isDeeplySleeping-to-HumanEntity.patch | 24 + .../0620-add-consumeFuel-to-FurnaceBurnEvent.patch | 19 + .../0620-add-isDeeplySleeping-to-HumanEntity.patch | 24 - .../0621-add-consumeFuel-to-FurnaceBurnEvent.patch | 19 - ...dd-get-set-drop-chance-to-EntityEquipment.patch | 48 + ...dd-get-set-drop-chance-to-EntityEquipment.patch | 48 - ...0622-fix-PigZombieAngerEvent-cancellation.patch | 35 + ...23-Fix-checkReach-check-for-Shulker-boxes.patch | 18 + ...0623-fix-PigZombieAngerEvent-cancellation.patch | 35 - ...24-Fix-checkReach-check-for-Shulker-boxes.patch | 18 - ...0624-fix-PlayerItemHeldEvent-firing-twice.patch | 18 + .../server/0625-Added-PlayerDeepSleepEvent.patch | 22 + ...0625-fix-PlayerItemHeldEvent-firing-twice.patch | 18 - .../server/0626-Added-PlayerDeepSleepEvent.patch | 22 - patches/server/0626-More-World-API.patch | 94 + .../0627-Added-PlayerBedFailEnterEvent.patch | 36 + patches/server/0627-More-World-API.patch | 94 - .../0628-Added-PlayerBedFailEnterEvent.patch | 36 - ...ethods-to-convert-between-Component-and-B.patch | 55 + ...respawn-acting-as-a-bed-respawn-from-the-.patch | 36 + ...ethods-to-convert-between-Component-and-B.patch | 55 - ...respawn-acting-as-a-bed-respawn-from-the-.patch | 36 - ...uce-beacon-activation-deactivation-events.patch | 37 + ...uce-beacon-activation-deactivation-events.patch | 37 - ...31-add-RespawnFlags-to-PlayerRespawnEvent.patch | 45 + ...0632-Add-Channel-initialization-listeners.patch | 155 + ...32-add-RespawnFlags-to-PlayerRespawnEvent.patch | 45 - ...0633-Add-Channel-initialization-listeners.patch | 155 - ...ty-commands-if-tab-completion-is-disabled.patch | 24 + .../server/0634-Add-more-WanderingTrader-API.patch | 65 + ...ty-commands-if-tab-completion-is-disabled.patch | 24 - ...0635-Add-EntityBlockStorage-clearEntities.patch | 37 + .../server/0635-Add-more-WanderingTrader-API.patch | 65 - ...ure-message-to-PlayerAdvancementDoneEvent.patch | 32 + ...0636-Add-EntityBlockStorage-clearEntities.patch | 37 - ...ure-message-to-PlayerAdvancementDoneEvent.patch | 32 - ...d-raw-address-to-AsyncPlayerPreLoginEvent.patch | 25 + ...d-raw-address-to-AsyncPlayerPreLoginEvent.patch | 25 - patches/server/0638-Inventory-close.patch | 25 + patches/server/0639-Inventory-close.patch | 25 - ...ortalCreateEvent-players-and-end-platform.patch | 33 + ...d-burn-in-sunlight-API-for-Phantoms-and-S.patch | 131 + ...ortalCreateEvent-players-and-end-platform.patch | 33 - ...d-burn-in-sunlight-API-for-Phantoms-and-S.patch | 131 - .../server/0641-Fix-CraftPotionBrewer-cache.patch | 44 + patches/server/0642-Add-basic-Datapack-API.patch | 125 + .../server/0642-Fix-CraftPotionBrewer-cache.patch | 44 - patches/server/0643-Add-basic-Datapack-API.patch | 125 - ...nvironment-variable-to-disable-server-gui.patch | 18 + ...nvironment-variable-to-disable-server-gui.patch | 18 - ...44-additions-to-PlayerGameModeChangeEvent.patch | 153 + .../server/0645-ItemStack-repair-check-API.patch | 79 + ...45-additions-to-PlayerGameModeChangeEvent.patch | 153 - .../server/0646-ItemStack-repair-check-API.patch | 79 - patches/server/0646-More-Enchantment-API.patch | 155 + patches/server/0647-More-Enchantment-API.patch | 155 - ...647-Move-range-check-for-block-placing-up.patch | 22 + ...48-Fix-and-optimise-world-force-upgrading.patch | 389 + ...648-Move-range-check-for-block-placing-up.patch | 22 - patches/server/0649-Add-Mob-lookAt-API.patch | 64 + ...49-Fix-and-optimise-world-force-upgrading.patch | 389 - patches/server/0650-Add-Mob-lookAt-API.patch | 64 - .../0650-Add-Unix-domain-socket-support.patch | 141 + .../server/0651-Add-EntityInsideBlockEvent.patch | 258 + .../0651-Add-Unix-domain-socket-support.patch | 141 - .../server/0652-Add-EntityInsideBlockEvent.patch | 258 - .../0652-Attributes-API-for-item-defaults.patch | 30 + ...-Add-cause-to-Weather-ThunderChangeEvents.patch | 118 + .../0653-Attributes-API-for-item-defaults.patch | 30 - ...-Add-cause-to-Weather-ThunderChangeEvents.patch | 118 - patches/server/0654-More-Lidded-Block-API.patch | 75 + .../0655-Limit-item-frame-cursors-on-maps.patch | 23 + patches/server/0655-More-Lidded-Block-API.patch | 75 - .../server/0656-Add-PlayerKickEvent-causes.patch | 435 + .../0656-Limit-item-frame-cursors-on-maps.patch | 23 - .../server/0657-Add-PlayerKickEvent-causes.patch | 435 - .../0657-Add-PufferFishStateChangeEvent.patch | 50 + .../0658-Add-PufferFishStateChangeEvent.patch | 50 - ...x-PlayerBucketEmptyEvent-result-itemstack.patch | 42 + ...x-PlayerBucketEmptyEvent-result-itemstack.patch | 42 - ...-PalettedContainer-instead-of-ThreadingDe.patch | 91 + ...option-to-fix-items-merging-through-walls.patch | 25 + ...-PalettedContainer-instead-of-ThreadingDe.patch | 91 - .../server/0661-Add-BellRevealRaiderEvent.patch | 32 + ...option-to-fix-items-merging-through-walls.patch | 25 - .../server/0662-Add-BellRevealRaiderEvent.patch | 32 - .../0662-Fix-invulnerable-end-crystals.patch | 65 + .../0663-Add-ElderGuardianAppearanceEvent.patch | 48 + .../0663-Fix-invulnerable-end-crystals.patch | 65 - .../0664-Add-ElderGuardianAppearanceEvent.patch | 48 - .../0664-Fix-dangerous-end-portal-logic.patch | 86 + .../0665-Fix-dangerous-end-portal-logic.patch | 86 - ...timize-Biome-Mob-Lookups-for-Mob-Spawning.patch | 57 + .../0666-Make-item-validations-configurable.patch | 55 + ...timize-Biome-Mob-Lookups-for-Mob-Spawning.patch | 57 - patches/server/0667-Line-Of-Sight-Changes.patch | 74 + .../0667-Make-item-validations-configurable.patch | 55 - patches/server/0668-Line-Of-Sight-Changes.patch | 74 - .../server/0668-add-per-world-spawn-limits.patch | 25 + ...otionSplashEvent-for-water-splash-potions.patch | 76 + .../server/0669-add-per-world-spawn-limits.patch | 25 - .../server/0670-Add-more-LimitedRegion-API.patch | 56 + ...otionSplashEvent-for-water-splash-potions.patch | 76 - .../server/0671-Add-more-LimitedRegion-API.patch | 56 - ...-Fix-PlayerDropItemEvent-using-wrong-item.patch | 35 + ...-Fix-PlayerDropItemEvent-using-wrong-item.patch | 35 - .../server/0672-Missing-Entity-Behavior-API.patch | 612 + ...isconnect-for-book-edit-is-called-on-main.patch | 19 + .../server/0673-Missing-Entity-Behavior-API.patch | 612 - ...isconnect-for-book-edit-is-called-on-main.patch | 19 - ...value-of-Block-applyBoneMeal-always-being.patch | 19 + ...value-of-Block-applyBoneMeal-always-being.patch | 19 - ...Use-getChunkIfLoadedImmediately-in-places.patch | 49 + ...ands-from-signs-not-firing-command-events.patch | 126 + ...Use-getChunkIfLoadedImmediately-in-places.patch | 49 - patches/server/0677-Adds-PlayerArmSwingEvent.patch | 19 + ...ands-from-signs-not-firing-command-events.patch | 126 - patches/server/0678-Adds-PlayerArmSwingEvent.patch | 19 - ...s-kick-event-leave-message-not-being-sent.patch | 85 + ...config-for-mobs-immune-to-default-effects.patch | 57 + ...s-kick-event-leave-message-not-being-sent.patch | 85 - ...config-for-mobs-immune-to-default-effects.patch | 57 - ...Fix-incorrect-message-for-outdated-client.patch | 19 + ...81-Don-t-apply-cramming-damage-to-players.patch | 25 + ...Fix-incorrect-message-for-outdated-client.patch | 19 - ...82-Don-t-apply-cramming-damage-to-players.patch | 25 - ...ons-and-timings-for-sensors-and-behaviors.patch | 134 + ...-Add-a-bunch-of-missing-forceDrop-toggles.patch | 48 + ...ons-and-timings-for-sensors-and-behaviors.patch | 134 - ...-Add-a-bunch-of-missing-forceDrop-toggles.patch | 48 - patches/server/0684-Stinger-API.patch | 39 + ...sistency-issue-with-empty-map-items-in-CB.patch | 31 + patches/server/0685-Stinger-API.patch | 39 - .../server/0686-Add-System.out-err-catcher.patch | 118 + ...sistency-issue-with-empty-map-items-in-CB.patch | 31 - .../server/0687-Add-System.out-err-catcher.patch | 118 - .../server/0687-Fix-test-not-bootstrapping.patch | 24 + .../server/0688-Fix-test-not-bootstrapping.patch | 24 - ...Events-to-contain-the-source-jars-in-stac.patch | 246 + .../0689-Improve-boat-collision-performance.patch | 70 + ...Events-to-contain-the-source-jars-in-stac.patch | 246 - .../0690-Improve-boat-collision-performance.patch | 70 - ...event-AFK-kick-while-watching-end-credits.patch | 19 + ...ing-writing-of-comments-to-server.propert.patch | 70 + ...event-AFK-kick-while-watching-end-credits.patch | 19 - patches/server/0692-Add-PlayerSetSpawnEvent.patch | 146 + ...ing-writing-of-comments-to-server.propert.patch | 70 - patches/server/0693-Add-PlayerSetSpawnEvent.patch | 146 - ...-hoppers-respect-inventory-max-stack-size.patch | 32 + ...-hoppers-respect-inventory-max-stack-size.patch | 32 - ...-Optimize-entity-tracker-passenger-checks.patch | 19 + ...Config-option-for-Piglins-guarding-chests.patch | 18 + ...-Optimize-entity-tracker-passenger-checks.patch | 19 - .../server/0696-Added-EntityDamageItemEvent.patch | 65 + ...Config-option-for-Piglins-guarding-chests.patch | 18 - .../server/0697-Added-EntityDamageItemEvent.patch | 65 - ...697-Optimize-indirect-passenger-iteration.patch | 52 + ...rops-position-losing-precision-millions-o.patch | 41 + ...698-Optimize-indirect-passenger-iteration.patch | 52 - ...ble-item-frame-map-cursor-update-interval.patch | 19 + ...rops-position-losing-precision-millions-o.patch | 41 - ...ble-item-frame-map-cursor-update-interval.patch | 19 - .../0700-Make-EntityUnleashEvent-cancellable.patch | 41 + .../0701-Clear-bucket-NBT-after-dispense.patch | 20 + .../0701-Make-EntityUnleashEvent-cancellable.patch | 41 - ...rEye-target-without-changing-other-things.patch | 54 + .../0702-Clear-bucket-NBT-after-dispense.patch | 20 - patches/server/0703-Add-BlockBreakBlockEvent.patch | 86 + ...rEye-target-without-changing-other-things.patch | 54 - patches/server/0704-Add-BlockBreakBlockEvent.patch | 86 - ...n-to-prevent-NBT-copy-in-smithing-recipes.patch | 84 + patches/server/0705-More-CommandBlock-API.patch | 100 + ...n-to-prevent-NBT-copy-in-smithing-recipes.patch | 84 - ...06-Add-missing-team-sidebar-display-slots.patch | 102 + patches/server/0706-More-CommandBlock-API.patch | 100 - .../0707-Add-back-EntityPortalExitEvent.patch | 45 + ...07-Add-missing-team-sidebar-display-slots.patch | 102 - .../0708-Add-back-EntityPortalExitEvent.patch | 45 - ...ods-to-find-targets-for-lightning-strikes.patch | 58 + ...ods-to-find-targets-for-lightning-strikes.patch | 58 - .../0709-Get-entity-default-attributes.patch | 159 + .../0710-Get-entity-default-attributes.patch | 159 - patches/server/0710-Left-handed-API.patch | 26 + .../server/0711-Add-advancement-display-API.patch | 159 + patches/server/0711-Left-handed-API.patch | 26 - .../0712-Add-ItemFactory-getMonsterEgg-API.patch | 28 + .../server/0712-Add-advancement-display-API.patch | 159 - .../0713-Add-ItemFactory-getMonsterEgg-API.patch | 28 - patches/server/0713-Add-critical-damage-API.patch | 135 + patches/server/0714-Add-critical-damage-API.patch | 135 - .../0714-Fix-issues-with-mob-conversion.patch | 44 + ...dd-isCollidable-methods-to-various-places.patch | 55 + .../0715-Fix-issues-with-mob-conversion.patch | 44 - ...dd-isCollidable-methods-to-various-places.patch | 55 - patches/server/0716-Goat-ram-API.patch | 42 + ...0717-Add-API-for-resetting-a-single-score.patch | 24 + patches/server/0717-Goat-ram-API.patch | 42 - ...0718-Add-API-for-resetting-a-single-score.patch | 24 - .../0718-Add-Raw-Byte-Entity-Serialization.patch | 81 + .../0719-Add-Raw-Byte-Entity-Serialization.patch | 81 - .../0719-Vanilla-command-permission-fixes.patch | 79 + ...w-the-server-to-unload-chunks-at-request-.patch | 23 + .../0720-Vanilla-command-permission-fixes.patch | 79 - ...w-the-server-to-unload-chunks-at-request-.patch | 23 - ...close-logic-for-inventories-on-chunk-unlo.patch | 68 + ...-handle-recursion-for-chunkholder-updates.patch | 37 + ...close-logic-for-inventories-on-chunk-unlo.patch | 68 - ...-handle-recursion-for-chunkholder-updates.patch | 37 - .../0723-Fix-GameProfileCache-concurrency.patch | 128 + .../0724-Fix-GameProfileCache-concurrency.patch | 128 - ...-Fix-chunks-refusing-to-unload-at-low-TPS.patch | 26 + ...w-ticket-level-changes-while-unloading-pl.patch | 62 + ...-Fix-chunks-refusing-to-unload-at-low-TPS.patch | 26 - ...w-ticket-level-changes-when-updating-chun.patch | 40 + ...w-ticket-level-changes-while-unloading-pl.patch | 62 - ...w-ticket-level-changes-when-updating-chun.patch | 40 - ...727-Log-when-the-async-catcher-is-tripped.patch | 20 + ...Add-paper-mobcaps-and-paper-playermobcaps.patch | 349 + ...728-Log-when-the-async-catcher-is-tripped.patch | 20 - ...Add-paper-mobcaps-and-paper-playermobcaps.patch | 349 - ...oad-calls-removing-tickets-for-sync-loads.patch | 80 + ...oad-calls-removing-tickets-for-sync-loads.patch | 80 - ...0-Sanitize-ResourceLocation-error-logging.patch | 22 + ...w-controlled-flushing-for-network-manager.patch | 147 + ...1-Sanitize-ResourceLocation-error-logging.patch | 22 - ...w-controlled-flushing-for-network-manager.patch | 147 - .../server/0732-Optimise-general-POI-access.patch | 1059 + patches/server/0733-Add-more-async-catchers.patch | 44 + .../server/0733-Optimise-general-POI-access.patch | 1059 - patches/server/0734-Add-more-async-catchers.patch | 44 - ...-Rewrite-entity-bounding-box-lookup-calls.patch | 1300 ++ .../0735-Optimise-chunk-tick-iteration.patch | 205 + ...-Rewrite-entity-bounding-box-lookup-calls.patch | 1300 -- .../server/0736-Execute-chunk-tasks-mid-tick.patch | 179 + .../0736-Optimise-chunk-tick-iteration.patch | 205 - ...recalculate-regionfile-header-if-it-is-co.patch | 766 + .../server/0737-Execute-chunk-tasks-mid-tick.patch | 179 - ...recalculate-regionfile-header-if-it-is-co.patch | 766 - ...e-implementation-for-blockstate-state-loo.patch | 343 + ...e-implementation-for-blockstate-state-loo.patch | 343 - ...Detail-more-information-in-watchdog-dumps.patch | 297 + ...Detail-more-information-in-watchdog-dumps.patch | 297 - ...-Manually-inline-methods-in-BlockPosition.patch | 63 + .../0741-Distance-manager-tick-timings.patch | 40 + ...-Manually-inline-methods-in-BlockPosition.patch | 63 - .../0742-Distance-manager-tick-timings.patch | 40 - ...scheduler-threads-according-to-the-plugin.patch | 33 + ...nlined-getChunkAt-has-inlined-logic-for-l.patch | 34 + ...scheduler-threads-according-to-the-plugin.patch | 33 - .../server/0744-Add-packet-limiter-config.patch | 98 + ...nlined-getChunkAt-has-inlined-logic-for-l.patch | 34 - .../server/0745-Add-packet-limiter-config.patch | 98 - ...-LevelStem-registry-when-loading-default-.patch | 45 + ...neighbour-chunk-data-off-disk-when-conver.patch | 21 + ...-LevelStem-registry-when-loading-default-.patch | 45 - ...te-flush-calls-for-entity-tracker-packets.patch | 52 + ...neighbour-chunk-data-off-disk-when-conver.patch | 21 - ...te-flush-calls-for-entity-tracker-packets.patch | 52 - ...-Don-t-lookup-fluid-state-when-raytracing.patch | 20 + ...-Don-t-lookup-fluid-state-when-raytracing.patch | 20 - patches/server/0749-Time-scoreboard-search.patch | 43 + ...l-pos-packets-for-hard-colliding-entities.patch | 23 + patches/server/0750-Time-scoreboard-search.patch | 43 - .../0751-Do-not-run-raytrace-logic-for-AIR.patch | 22 + ...l-pos-packets-for-hard-colliding-entities.patch | 23 - .../0752-Do-not-run-raytrace-logic-for-AIR.patch | 22 - ...752-Oprimise-map-impl-for-tracked-players.patch | 29 + ...753-Oprimise-map-impl-for-tracked-players.patch | 29 - ...53-Optimise-BlockSoil-nearby-water-lookup.patch | 51 + ...al-addition-of-entities-to-entity-ticklis.patch | 89 + ...54-Optimise-BlockSoil-nearby-water-lookup.patch | 51 - ...al-addition-of-entities-to-entity-ticklis.patch | 89 - .../0755-Optimise-random-block-ticking.patch | 449 + .../0756-Optimise-non-flush-packet-sending.patch | 55 + .../0756-Optimise-random-block-ticking.patch | 449 - .../0757-Optimise-nearby-player-lookups.patch | 427 + .../0757-Optimise-non-flush-packet-sending.patch | 55 - .../server/0758-Optimise-WorldServer-notify.patch | 337 + .../0758-Optimise-nearby-player-lookups.patch | 427 - .../server/0759-Optimise-WorldServer-notify.patch | 337 - .../0759-Remove-streams-for-villager-AI.patch | 216 + .../0760-Remove-streams-for-villager-AI.patch | 216 - .../server/0760-Rewrite-dataconverter-system.patch | 22747 +++++++++++++++++++ .../server/0761-Rewrite-dataconverter-system.patch | 22747 ------------------- ...e-Velocity-compression-and-cipher-natives.patch | 364 + ...dgen-thread-worker-count-for-low-core-cou.patch | 31 + ...e-Velocity-compression-and-cipher-natives.patch | 364 - ...ess-entity-loads-in-CraftChunk-getEntitie.patch | 68 + ...dgen-thread-worker-count-for-low-core-cou.patch | 31 - ...ch-modifications-to-critical-entity-state.patch | 133 + ...ess-entity-loads-in-CraftChunk-getEntitie.patch | 68 - ...ch-modifications-to-critical-entity-state.patch | 133 - ...0765-Fix-Bukkit-NamespacedKey-shenanigans.patch | 45 + ...0766-Fix-Bukkit-NamespacedKey-shenanigans.patch | 45 - ...t-inventory-not-closing-on-entity-removal.patch | 22 + ...-requirement-before-suggesting-root-nodes.patch | 32 + ...t-inventory-not-closing-on-entity-removal.patch | 22 - ...-requirement-before-suggesting-root-nodes.patch | 32 - ...nd-to-ServerboundCommandSuggestionPacket-.patch | 23 + ...nd-to-ServerboundCommandSuggestionPacket-.patch | 23 - ...PatternColor-on-tropical-fish-bucket-meta.patch | 71 + .../server/0770-Ensure-valid-vehicle-status.patch | 19 + ...PatternColor-on-tropical-fish-bucket-meta.patch | 71 - .../server/0771-Ensure-valid-vehicle-status.patch | 19 - ...ent-softlocked-end-exit-portal-generation.patch | 22 + ...corator-causing-a-crash-when-trying-to-ge.patch | 19 + ...ent-softlocked-end-exit-portal-generation.patch | 22 - ...73-Don-t-log-debug-logging-being-disabled.patch | 19 + ...corator-causing-a-crash-when-trying-to-ge.patch | 19 - ...74-Don-t-log-debug-logging-being-disabled.patch | 19 - ...x-various-menus-with-empty-level-accesses.patch | 23 + .../server/0775-Preserve-overstacked-loot.patch | 56 + ...x-various-menus-with-empty-level-accesses.patch | 23 - .../server/0776-Preserve-overstacked-loot.patch | 56 - ...76-Update-head-rotation-in-missing-places.patch | 29 + ...77-Update-head-rotation-in-missing-places.patch | 29 - ...event-unintended-light-block-manipulation.patch | 18 + .../0778-Fix-CraftCriteria-defaults-map.patch | 19 + ...event-unintended-light-block-manipulation.patch | 18 - .../0779-Fix-CraftCriteria-defaults-map.patch | 19 - .../0779-Fix-upstreams-block-state-factories.patch | 370 + ...ig-option-for-logging-player-ip-addresses.patch | 79 + .../0780-Fix-upstreams-block-state-factories.patch | 370 - ...ig-option-for-logging-player-ip-addresses.patch | 79 - .../server/0781-Configurable-feature-seeds.patch | 40 + .../server/0782-Configurable-feature-seeds.patch | 40 - ...andWrapper-didnt-account-for-entity-sende.patch | 23 + .../0783-Add-root-admin-user-detection.patch | 79 + ...andWrapper-didnt-account-for-entity-sende.patch | 23 - .../0784-Add-root-admin-user-detection.patch | 79 - ...84-Always-allow-item-changing-in-Fireball.patch | 19 + ...85-Always-allow-item-changing-in-Fireball.patch | 19 - ...5-don-t-attempt-to-teleport-dead-entities.patch | 19 + ...excessive-velocity-through-repeated-crits.patch | 38 + ...6-don-t-attempt-to-teleport-dead-entities.patch | 19 - ...excessive-velocity-through-repeated-crits.patch | 38 - ...nt-side-code-using-deprecated-for-removal.patch | 31 + ...nt-side-code-using-deprecated-for-removal.patch | 31 - patches/server/0788-Rewrite-the-light-engine.patch | 5290 +++++ ...e-protochunk-light-sources-unless-it-is-m.patch | 53 + patches/server/0789-Rewrite-the-light-engine.patch | 5290 ----- ...e-protochunk-light-sources-unless-it-is-m.patch | 53 - ...-Fix-removing-recipes-from-RecipeIterator.patch | 49 + ...-Fix-removing-recipes-from-RecipeIterator.patch | 49 - ...ding-oversized-item-data-in-equipment-and.patch | 86 + ...92-Hide-unnecessary-itemmeta-from-clients.patch | 107 + ...ding-oversized-item-data-in-equipment-and.patch | 86 - ...-modifier-changing-growth-for-other-crops.patch | 108 + ...93-Hide-unnecessary-itemmeta-from-clients.patch | 107 - ...-modifier-changing-growth-for-other-crops.patch | 108 - ...tainerOpenersCounter-openCount-from-going.patch | 18 + .../0795-Add-PlayerItemFrameChangeEvent.patch | 57 + ...tainerOpenersCounter-openCount-from-going.patch | 18 - .../0796-Add-PlayerItemFrameChangeEvent.patch | 57 - .../server/0796-Add-player-health-update-API.patch | 39 + .../server/0797-Add-player-health-update-API.patch | 39 - patches/server/0797-Optimize-HashMapPalette.patch | 57 + ...798-Allow-delegation-to-vanilla-chunk-gen.patch | 129 + patches/server/0798-Optimize-HashMapPalette.patch | 57 - ...799-Allow-delegation-to-vanilla-chunk-gen.patch | 129 - ...mise-single-and-multi-AABB-VoxelShapes-an.patch | 2122 ++ ...mise-single-and-multi-AABB-VoxelShapes-an.patch | 2122 -- ...llision-checking-in-player-move-packet-ha.patch | 170 + patches/server/0801-Actually-unload-POI-data.patch | 321 + ...llision-checking-in-player-move-packet-ha.patch | 170 - patches/server/0802-Actually-unload-POI-data.patch | 321 - ...apshot-isSectionEmpty-int-and-optimize-Pa.patch | 43 + ...apshot-isSectionEmpty-int-and-optimize-Pa.patch | 43 - patches/server/0803-Update-Log4j.patch | 24 + patches/server/0804-Add-more-Campfire-API.patch | 112 + patches/server/0804-Update-Log4j.patch | 24 - patches/server/0805-Add-more-Campfire-API.patch | 112 - ...chunk-data-to-disk-if-it-serializes-witho.patch | 97 + .../0806-Fix-tripwire-state-inconsistency.patch | 67 + ...chunk-data-to-disk-if-it-serializes-witho.patch | 97 - ...Fix-fluid-logging-on-Block-breakNaturally.patch | 29 + .../0807-Fix-tripwire-state-inconsistency.patch | 67 - ...Fix-fluid-logging-on-Block-breakNaturally.patch | 29 - ...8-Forward-CraftEntity-in-teleport-command.patch | 39 + ...9-Forward-CraftEntity-in-teleport-command.patch | 39 - .../server/0809-Improve-scoreboard-entries.patch | 84 + patches/server/0810-Entity-powdered-snow-API.patch | 37 + .../server/0810-Improve-scoreboard-entries.patch | 84 - .../0811-Add-API-for-item-entity-health.patch | 32 + patches/server/0811-Entity-powdered-snow-API.patch | 37 - .../0812-Add-API-for-item-entity-health.patch | 32 - ...entity-type-tags-suggestions-in-selectors.patch | 135 + ...able-max-block-light-for-monster-spawning.patch | 19 + ...entity-type-tags-suggestions-in-selectors.patch | 135 - ...able-max-block-light-for-monster-spawning.patch | 19 - ...ticky-pistons-and-BlockPistonRetractEvent.patch | 85 + ...ticky-pistons-and-BlockPistonRetractEvent.patch | 85 - ...ect-amplifiers-greater-than-127-correctly.patch | 20 + ...el-and-canSmelt-methods-to-FurnaceInvento.patch | 31 + ...ect-amplifiers-greater-than-127-correctly.patch | 20 - ...el-and-canSmelt-methods-to-FurnaceInvento.patch | 31 - .../server/0817-Fix-bees-aging-inside-hives.patch | 49 + patches/server/0818-Bucketable-API.patch | 69 + .../server/0818-Fix-bees-aging-inside-hives.patch | 49 - patches/server/0819-Bucketable-API.patch | 69 - ...heck-player-world-in-endPortalSoundRadius.patch | 20 + ...heck-player-world-in-endPortalSoundRadius.patch | 20 - patches/server/0820-Validate-usernames.patch | 70 + ...ix-saving-configs-with-more-long-comments.patch | 1702 ++ patches/server/0821-Validate-usernames.patch | 70 - ...ix-saving-configs-with-more-long-comments.patch | 1702 -- ...ke-water-animal-spawn-height-configurable.patch | 21 + ...pose-vanilla-BiomeProvider-from-WorldInfo.patch | 139 + ...ke-water-animal-spawn-height-configurable.patch | 21 - ...ig-option-for-worlds-affected-by-time-cmd.patch | 28 + ...pose-vanilla-BiomeProvider-from-WorldInfo.patch | 139 - ...ig-option-for-worlds-affected-by-time-cmd.patch | 28 - ...w-overload-to-PersistentDataContainer-has.patch | 24 + ...w-overload-to-PersistentDataContainer-has.patch | 24 - .../0826-Multiple-Entries-with-Scoreboards.patch | 126 + .../0827-Multiple-Entries-with-Scoreboards.patch | 126 - .../0827-Reset-placed-block-on-exception.patch | 38 + ...8-Add-configurable-height-for-slime-spawn.patch | 35 + .../0828-Reset-placed-block-on-exception.patch | 38 - ...9-Add-configurable-height-for-slime-spawn.patch | 35 - ...d-getHostname-to-AsyncPlayerPreLoginEvent.patch | 19 + ...d-getHostname-to-AsyncPlayerPreLoginEvent.patch | 19 - .../0830-Fix-xp-reward-for-baby-zombies.patch | 32 + .../0831-Fix-xp-reward-for-baby-zombies.patch | 32 - .../0831-Kick-on-main-for-illegal-chat.patch | 42 + .../0832-Kick-on-main-for-illegal-chat.patch | 42 - ...832-Multi-Block-Change-API-Implementation.patch | 74 + patches/server/0833-Fix-NotePlayEvent.patch | 44 + ...833-Multi-Block-Change-API-Implementation.patch | 74 - patches/server/0834-Fix-NotePlayEvent.patch | 44 - patches/server/0834-Freeze-Tick-Lock-API.patch | 82 + patches/server/0835-Dolphin-API.patch | 45 + patches/server/0835-Freeze-Tick-Lock-API.patch | 82 - patches/server/0836-Dolphin-API.patch | 45 - .../server/0836-More-PotionEffectType-API.patch | 91 + .../server/0837-More-PotionEffectType-API.patch | 91 - ...a-CHM-for-StructureTemplate.Pallete-cache.patch | 20 + ...ating-command-sender-which-forwards-feedb.patch | 178 + ...a-CHM-for-StructureTemplate.Pallete-cache.patch | 20 - ...ating-command-sender-which-forwards-feedb.patch | 178 - .../0839-Add-config-for-stronghold-seed.patch | 47 + .../0840-Add-config-for-stronghold-seed.patch | 47 - .../server/0840-Implement-regenerateChunk.patch | 98 + ...-cancelled-powdered-snow-bucket-placement.patch | 43 + .../server/0841-Implement-regenerateChunk.patch | 98 - ...-Validate-calls-to-CraftServer-getSpawnLi.patch | 20 + ...-cancelled-powdered-snow-bucket-placement.patch | 43 - patches/server/0843-Add-GameEvent-tags.patch | 80 + ...-Validate-calls-to-CraftServer-getSpawnLi.patch | 20 - patches/server/0844-Add-GameEvent-tags.patch | 80 - ...nk-tasks-fairly-for-worlds-while-waiting-.patch | 37 + ...nk-tasks-fairly-for-worlds-while-waiting-.patch | 37 - .../0845-Replace-ticket-level-propagator.patch | 258 + patches/server/0846-Furnace-RecipesUsed-API.patch | 48 + .../0846-Replace-ticket-level-propagator.patch | 258 - ...-Configurable-sculk-sensor-listener-range.patch | 51 + patches/server/0847-Furnace-RecipesUsed-API.patch | 48 - ...848-Add-missing-block-data-mins-and-maxes.patch | 147 + ...-Configurable-sculk-sensor-listener-range.patch | 51 - ...849-Add-missing-block-data-mins-and-maxes.patch | 147 - ...ave-default-CustomSpawners-in-custom-worl.patch | 32 + ...ave-default-CustomSpawners-in-custom-worl.patch | 32 - ...d-into-worldlist-before-initing-the-world.patch | 41 + .../server/0851-Fix-Entity-Position-Desync.patch | 23 + ...d-into-worldlist-before-initing-the-world.patch | 41 - patches/server/0852-Custom-Potion-Mixes.patch | 239 + .../server/0852-Fix-Entity-Position-Desync.patch | 23 - patches/server/0853-Custom-Potion-Mixes.patch | 239 - .../0853-Replace-player-chunk-loader-system.patch | 2270 ++ .../0854-Fix-Fluid-tags-isTagged-method.patch | 32 + .../0854-Replace-player-chunk-loader-system.patch | 2270 -- .../0855-Fix-Fluid-tags-isTagged-method.patch | 32 - .../0855-Force-close-world-loading-screen.patch | 32 + .../0856-Fix-falling-block-spawn-methods.patch | 54 + .../0856-Force-close-world-loading-screen.patch | 32 - .../0857-Expose-furnace-minecart-push-values.patch | 40 + .../0857-Fix-falling-block-spawn-methods.patch | 54 - .../0858-Expose-furnace-minecart-push-values.patch | 40 - ...ing-ProjectileHitEvent-for-piercing-arrow.patch | 37 + ...ing-ProjectileHitEvent-for-piercing-arrow.patch | 37 - .../0859-Fix-save-problems-on-shutdown.patch | 74 + .../0860-Fix-save-problems-on-shutdown.patch | 74 - patches/server/0860-More-Projectile-API.patch | 223 + ...861-Fix-swamp-hut-cat-generation-deadlock.patch | 62 + patches/server/0861-More-Projectile-API.patch | 223 - ...-vehicle-movement-from-players-while-tele.patch | 25 + ...862-Fix-swamp-hut-cat-generation-deadlock.patch | 62 - ...-vehicle-movement-from-players-while-tele.patch | 25 - .../0863-Implement-getComputedBiome-API.patch | 61 + .../0864-Implement-getComputedBiome-API.patch | 61 - .../server/0864-Make-some-itemstacks-nonnull.patch | 28 + ...or-invalid-GameProfiles-on-skull-blocks-i.patch | 63 + .../server/0865-Make-some-itemstacks-nonnull.patch | 28 - ...or-invalid-GameProfiles-on-skull-blocks-i.patch | 63 - .../0866-Implement-enchantWithLevels-API.patch | 32 + .../server/0867-Fix-saving-in-unloadWorld.patch | 20 + .../0867-Implement-enchantWithLevels-API.patch | 32 - .../server/0868-Buffer-OOB-setBlock-calls.patch | 42 + .../server/0868-Fix-saving-in-unloadWorld.patch | 20 - .../0869-Add-TameableDeathMessageEvent.patch | 24 + .../server/0869-Buffer-OOB-setBlock-calls.patch | 42 - .../0870-Add-TameableDeathMessageEvent.patch | 24 - ...ck-data-for-EntityChangeBlockEvent-when-s.patch | 20 + ...ck-data-for-EntityChangeBlockEvent-when-s.patch | 20 - ...loottables-running-when-mob-loot-gamerule.patch | 25 + ...ity-passenger-world-matches-ridden-entity.patch | 24 + ...loottables-running-when-mob-loot-gamerule.patch | 25 - ...ity-passenger-world-matches-ridden-entity.patch | 24 - ...73-Guard-against-invalid-entity-positions.patch | 45 + ...74-Guard-against-invalid-entity-positions.patch | 45 - patches/server/0874-cache-resource-keys.patch | 57 + ...-to-change-the-podium-for-the-EnderDragon.patch | 151 + patches/server/0875-cache-resource-keys.patch | 57 - ...-to-change-the-podium-for-the-EnderDragon.patch | 151 - ...ces-overriding-a-block-entity-during-worl.patch | 40 + ...ces-overriding-a-block-entity-during-worl.patch | 40 - ...ructureGrowEvent-species-for-RED_MUSHROOM.patch | 19 + ...ructureGrowEvent-species-for-RED_MUSHROOM.patch | 19 - ...Prevent-tile-entity-copies-loading-chunks.patch | 24 + ...Prevent-tile-entity-copies-loading-chunks.patch | 24 - ...e-instead-of-display-name-in-PlayerList-g.patch | 20 + ...pawners-not-spawning-outside-slime-chunks.patch | 24 + ...e-instead-of-display-name-in-PlayerList-g.patch | 20 - ...pawners-not-spawning-outside-slime-chunks.patch | 24 - ...1-Pass-ServerLevel-for-gamerule-callbacks.patch | 181 + ...nbreaking-amount-to-PlayerItemDamageEvent.patch | 23 + ...2-Pass-ServerLevel-for-gamerule-callbacks.patch | 181 - ...nbreaking-amount-to-PlayerItemDamageEvent.patch | 23 - .../server/0883-WorldCreator-keepSpawnLoaded.patch | 18 + ...84-Fix-NPE-for-BlockDataMeta-getBlockData.patch | 22 + .../server/0884-WorldCreator-keepSpawnLoaded.patch | 18 - ...85-Fix-NPE-for-BlockDataMeta-getBlockData.patch | 22 - ..._nest_destroyed-trigger-in-the-correct-pl.patch | 54 + ...ityDyeEvent-and-CollarColorable-interface.patch | 42 + ..._nest_destroyed-trigger-in-the-correct-pl.patch | 54 - ...ityDyeEvent-and-CollarColorable-interface.patch | 42 - ...-Fire-CauldronLevelChange-on-initial-fill.patch | 68 + ...-Fire-CauldronLevelChange-on-initial-fill.patch | 68 - ...owder-snow-cauldrons-not-turning-to-water.patch | 56 + .../server/0889-Add-PlayerStopUsingItemEvent.patch | 18 + ...owder-snow-cauldrons-not-turning-to-water.patch | 56 - .../server/0890-Add-PlayerStopUsingItemEvent.patch | 18 - .../0890-FallingBlock-auto-expire-setting.patch | 68 + patches/server/0891-Don-t-tick-markers.patch | 48 + .../0891-FallingBlock-auto-expire-setting.patch | 68 - ...892-Do-not-accept-invalid-client-settings.patch | 24 + patches/server/0892-Don-t-tick-markers.patch | 48 - .../0893-Add-support-for-Proxy-Protocol.patch | 66 + ...893-Do-not-accept-invalid-client-settings.patch | 24 - .../0894-Add-support-for-Proxy-Protocol.patch | 66 - ...894-Fix-OfflinePlayer-getBedSpawnLocation.patch | 46 + ...eInventory-for-smokers-and-blast-furnaces.patch | 49 + ...895-Fix-OfflinePlayer-getBedSpawnLocation.patch | 46 - ...eInventory-for-smokers-and-blast-furnaces.patch | 49 - .../0896-Sanitize-Sent-BlockEntity-NBT.patch | 48 + ...vent-entity-loading-causing-async-lookups.patch | 69 + .../0897-Sanitize-Sent-BlockEntity-NBT.patch | 48 - ...ponent-selector-resolving-in-books-by-def.patch | 19 + ...vent-entity-loading-causing-async-lookups.patch | 69 - ...ponent-selector-resolving-in-books-by-def.patch | 19 - ...eption-on-world-create-while-being-ticked.patch | 56 + ...Alternate-Current-redstone-implementation.patch | 2114 ++ ...eption-on-world-create-while-being-ticked.patch | 56 - ...Alternate-Current-redstone-implementation.patch | 2114 -- .../0901-Dont-resent-entity-on-art-update.patch | 19 + patches/server/0902-Add-missing-spawn-eggs.patch | 57 + .../0902-Dont-resent-entity-on-art-update.patch | 19 - .../server/0903-Add-WardenAngerChangeEvent.patch | 39 + patches/server/0903-Add-missing-spawn-eggs.patch | 57 - .../server/0904-Add-WardenAngerChangeEvent.patch | 39 - ...n-for-strict-advancement-dimension-checks.patch | 29 + ...-important-BlockStateListPopulator-method.patch | 66 + ...n-for-strict-advancement-dimension-checks.patch | 29 - ...-important-BlockStateListPopulator-method.patch | 66 - patches/server/0906-Nameable-Banner-API.patch | 36 + ...on-t-broadcast-messages-to-command-blocks.patch | 34 + patches/server/0907-Nameable-Banner-API.patch | 36 - ...on-t-broadcast-messages-to-command-blocks.patch | 34 - ...ent-empty-items-from-being-added-to-world.patch | 33 + ...-SplashPotion-and-LingeringPotion-spawnin.patch | 21 + ...ent-empty-items-from-being-added-to-world.patch | 33 - ...-component-in-resource-pack-rejection-mes.patch | 19 + ...-SplashPotion-and-LingeringPotion-spawnin.patch | 21 - patches/server/0911-Add-Player-getFishHook.patch | 26 + ...-component-in-resource-pack-rejection-mes.patch | 19 - patches/server/0912-Add-Player-getFishHook.patch | 26 - ...-load-chunk-for-dynamic-game-event-listen.patch | 29 + ...various-missing-EntityDropItemEvent-calls.patch | 73 + ...-load-chunk-for-dynamic-game-event-listen.patch | 29 - ...nimal-debug-information-to-chat-packet-er.patch | 29 + ...various-missing-EntityDropItemEvent-calls.patch | 73 - ...nimal-debug-information-to-chat-packet-er.patch | 29 - patches/server/0915-Fix-Bee-flower-NPE.patch | 19 + patches/server/0916-Fix-Bee-flower-NPE.patch | 19 - ...Config-not-using-commands.spam-exclusions.patch | 19 + ...wnReason-to-Tadpoles-spawned-by-Frogspawn.patch | 19 + ...Config-not-using-commands.spam-exclusions.patch | 19 - ...wnReason-to-Tadpoles-spawned-by-Frogspawn.patch | 19 - patches/server/0918-More-Teleport-API.patch | 195 + .../server/0919-Add-EntityPortalReadyEvent.patch | 32 + patches/server/0919-More-Teleport-API.patch | 195 - .../server/0920-Add-EntityPortalReadyEvent.patch | 32 - ...t-use-level-random-in-entity-constructors.patch | 63 + ...t-use-level-random-in-entity-constructors.patch | 63 - ...d-block-entities-after-destroy-prediction.patch | 91 + ...d-block-entities-after-destroy-prediction.patch | 91 - ...-Warn-on-plugins-accessing-faraway-chunks.patch | 96 + ...23-Custom-Chat-Completion-Suggestions-API.patch | 33 + ...-Warn-on-plugins-accessing-faraway-chunks.patch | 96 - .../server/0924-Add-missing-BlockFadeEvents.patch | 22 + ...24-Custom-Chat-Completion-Suggestions-API.patch | 33 - .../server/0925-Add-missing-BlockFadeEvents.patch | 22 - patches/server/0925-Collision-API.patch | 47 + patches/server/0926-Collision-API.patch | 47 - ...-command-message-for-brigadier-syntax-exc.patch | 20 + ...-preprocess-cancelling-and-command-changi.patch | 40 + ...-command-message-for-brigadier-syntax-exc.patch | 20 - ...-preprocess-cancelling-and-command-changi.patch | 40 - ...Remove-invalid-signature-login-stacktrace.patch | 19 + ...atcher-to-PlayerConnection-internalTelepo.patch | 18 + ...Remove-invalid-signature-login-stacktrace.patch | 19 - ...atcher-to-PlayerConnection-internalTelepo.patch | 18 - patches/server/0930-Block-Ticking-API.patch | 47 + .../0931-Add-Velocity-IP-Forwarding-Support.patch | 211 + patches/server/0931-Block-Ticking-API.patch | 47 - .../0932-Add-Velocity-IP-Forwarding-Support.patch | 211 - ...safe-random-in-ServerLoginPacketListenerI.patch | 19 + .../0933-Add-NamespacedKey-biome-methods.patch | 31 + ...safe-random-in-ServerLoginPacketListenerI.patch | 19 - .../0934-Add-NamespacedKey-biome-methods.patch | 31 - ...934-Fix-plugin-loggers-on-server-shutdown.patch | 67 + ...935-Fix-plugin-loggers-on-server-shutdown.patch | 67 - ...orkaround-for-client-lag-spikes-MC-162253.patch | 114 + ...orkaround-for-client-lag-spikes-MC-162253.patch | 114 - work/Bukkit | 2 +- work/CraftBukkit | 2 +- work/Spigot | 2 +- 1291 files changed, 85993 insertions(+), 85997 deletions(-) delete mode 100644 patches/server/0338-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch create mode 100644 patches/server/0338-Flat-bedrock-generator-settings.patch delete mode 100644 patches/server/0339-Flat-bedrock-generator-settings.patch create mode 100644 patches/server/0339-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch create mode 100644 patches/server/0340-MC-145656-Fix-Follow-Range-Initial-Target.patch delete mode 100644 patches/server/0340-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch create mode 100644 patches/server/0341-Duplicate-UUID-Resolve-Option.patch delete mode 100644 patches/server/0341-MC-145656-Fix-Follow-Range-Initial-Target.patch delete mode 100644 patches/server/0342-Duplicate-UUID-Resolve-Option.patch create mode 100644 patches/server/0342-Optimize-Hoppers.patch delete mode 100644 patches/server/0343-Optimize-Hoppers.patch create mode 100644 patches/server/0343-PlayerDeathEvent-shouldDropExperience.patch delete mode 100644 patches/server/0344-PlayerDeathEvent-shouldDropExperience.patch create mode 100644 patches/server/0344-Prevent-bees-loading-chunks-checking-hive-position.patch create mode 100644 patches/server/0345-Don-t-load-Chunks-from-Hoppers-and-other-things.patch delete mode 100644 patches/server/0345-Prevent-bees-loading-chunks-checking-hive-position.patch delete mode 100644 patches/server/0346-Don-t-load-Chunks-from-Hoppers-and-other-things.patch create mode 100644 patches/server/0346-Guard-against-serializing-mismatching-chunk-coordina.patch delete mode 100644 patches/server/0347-Guard-against-serializing-mismatching-chunk-coordina.patch create mode 100644 patches/server/0347-Optimise-IEntityAccess-getPlayerByUUID.patch create mode 100644 patches/server/0348-Fix-items-not-falling-correctly.patch delete mode 100644 patches/server/0348-Optimise-IEntityAccess-getPlayerByUUID.patch delete mode 100644 patches/server/0349-Fix-items-not-falling-correctly.patch create mode 100644 patches/server/0349-Lag-compensate-eating.patch delete mode 100644 patches/server/0350-Lag-compensate-eating.patch create mode 100644 patches/server/0350-Optimize-call-to-getFluid-for-explosions.patch create mode 100644 patches/server/0351-Fix-last-firework-in-stack-not-having-effects-when-d.patch delete mode 100644 patches/server/0351-Optimize-call-to-getFluid-for-explosions.patch create mode 100644 patches/server/0352-Add-effect-to-block-break-naturally.patch delete mode 100644 patches/server/0352-Fix-last-firework-in-stack-not-having-effects-when-d.patch delete mode 100644 patches/server/0353-Add-effect-to-block-break-naturally.patch create mode 100644 patches/server/0353-Entity-Activation-Range-2.0.patch delete mode 100644 patches/server/0354-Entity-Activation-Range-2.0.patch create mode 100644 patches/server/0354-Increase-Light-Queue-Size.patch create mode 100644 patches/server/0355-Fix-Light-Command.patch delete mode 100644 patches/server/0355-Increase-Light-Queue-Size.patch create mode 100644 patches/server/0356-Anti-Xray.patch delete mode 100644 patches/server/0356-Fix-Light-Command.patch delete mode 100644 patches/server/0357-Anti-Xray.patch create mode 100644 patches/server/0357-Implement-alternative-item-despawn-rate.patch delete mode 100644 patches/server/0358-Implement-alternative-item-despawn-rate.patch create mode 100644 patches/server/0358-Tracking-Range-Improvements.patch create mode 100644 patches/server/0359-Fix-items-vanishing-through-end-portal.patch delete mode 100644 patches/server/0359-Tracking-Range-Improvements.patch delete mode 100644 patches/server/0360-Fix-items-vanishing-through-end-portal.patch create mode 100644 patches/server/0360-implement-optional-per-player-mob-spawns.patch create mode 100644 patches/server/0361-Avoid-hopper-searches-if-there-are-no-items.patch delete mode 100644 patches/server/0361-implement-optional-per-player-mob-spawns.patch delete mode 100644 patches/server/0362-Avoid-hopper-searches-if-there-are-no-items.patch create mode 100644 patches/server/0362-Bees-get-gravity-in-void.-Fixes-MC-167279.patch delete mode 100644 patches/server/0363-Bees-get-gravity-in-void.-Fixes-MC-167279.patch create mode 100644 patches/server/0363-Optimise-getChunkAt-calls-for-loaded-chunks.patch create mode 100644 patches/server/0364-Add-debug-for-sync-chunk-loads.patch delete mode 100644 patches/server/0364-Optimise-getChunkAt-calls-for-loaded-chunks.patch delete mode 100644 patches/server/0365-Add-debug-for-sync-chunk-loads.patch create mode 100644 patches/server/0365-Remove-garbage-Java-version-check.patch create mode 100644 patches/server/0366-Add-ThrownEggHatchEvent.patch delete mode 100644 patches/server/0366-Remove-garbage-Java-version-check.patch delete mode 100644 patches/server/0367-Add-ThrownEggHatchEvent.patch create mode 100644 patches/server/0367-Entity-Jump-API.patch create mode 100644 patches/server/0368-Add-option-to-nerf-pigmen-from-nether-portals.patch delete mode 100644 patches/server/0368-Entity-Jump-API.patch delete mode 100644 patches/server/0369-Add-option-to-nerf-pigmen-from-nether-portals.patch create mode 100644 patches/server/0369-Make-the-GUI-graph-fancier.patch delete mode 100644 patches/server/0370-Make-the-GUI-graph-fancier.patch create mode 100644 patches/server/0370-add-hand-to-BlockMultiPlaceEvent.patch create mode 100644 patches/server/0371-Validate-tripwire-hook-placement-before-update.patch delete mode 100644 patches/server/0371-add-hand-to-BlockMultiPlaceEvent.patch create mode 100644 patches/server/0372-Add-option-to-allow-iron-golems-to-spawn-in-air.patch delete mode 100644 patches/server/0372-Validate-tripwire-hook-placement-before-update.patch delete mode 100644 patches/server/0373-Add-option-to-allow-iron-golems-to-spawn-in-air.patch create mode 100644 patches/server/0373-Configurable-chance-of-villager-zombie-infection.patch delete mode 100644 patches/server/0374-Configurable-chance-of-villager-zombie-infection.patch create mode 100644 patches/server/0374-Optimise-Chunk-getFluid.patch delete mode 100644 patches/server/0375-Optimise-Chunk-getFluid.patch create mode 100644 patches/server/0375-Set-spigots-verbose-world-setting-to-false-by-def.patch create mode 100644 patches/server/0376-Add-tick-times-API-and-mspt-command.patch delete mode 100644 patches/server/0376-Set-spigots-verbose-world-setting-to-false-by-def.patch delete mode 100644 patches/server/0377-Add-tick-times-API-and-mspt-command.patch create mode 100644 patches/server/0377-Expose-MinecraftServer-isRunning.patch create mode 100644 patches/server/0378-Add-Raw-Byte-ItemStack-Serialization.patch delete mode 100644 patches/server/0378-Expose-MinecraftServer-isRunning.patch delete mode 100644 patches/server/0379-Add-Raw-Byte-ItemStack-Serialization.patch create mode 100644 patches/server/0379-Pillager-patrol-spawn-settings-and-per-player-option.patch delete mode 100644 patches/server/0380-Pillager-patrol-spawn-settings-and-per-player-option.patch create mode 100644 patches/server/0380-Remote-Connections-shouldn-t-hold-up-shutdown.patch create mode 100644 patches/server/0381-Do-not-allow-bees-to-load-chunks-for-beehives.patch delete mode 100644 patches/server/0381-Remote-Connections-shouldn-t-hold-up-shutdown.patch delete mode 100644 patches/server/0382-Do-not-allow-bees-to-load-chunks-for-beehives.patch create mode 100644 patches/server/0382-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch create mode 100644 patches/server/0383-Don-t-tick-dead-players.patch delete mode 100644 patches/server/0383-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch create mode 100644 patches/server/0384-Dead-Player-s-shouldn-t-be-able-to-move.patch delete mode 100644 patches/server/0384-Don-t-tick-dead-players.patch delete mode 100644 patches/server/0385-Dead-Player-s-shouldn-t-be-able-to-move.patch create mode 100644 patches/server/0385-Optimize-Collision-to-not-load-chunks.patch create mode 100644 patches/server/0386-Don-t-move-existing-players-to-world-spawn.patch delete mode 100644 patches/server/0386-Optimize-Collision-to-not-load-chunks.patch delete mode 100644 patches/server/0387-Don-t-move-existing-players-to-world-spawn.patch create mode 100644 patches/server/0387-Optimize-GoalSelector-Goal.Flag-Set-operations.patch create mode 100644 patches/server/0388-Improved-Watchdog-Support.patch delete mode 100644 patches/server/0388-Optimize-GoalSelector-Goal.Flag-Set-operations.patch delete mode 100644 patches/server/0389-Improved-Watchdog-Support.patch create mode 100644 patches/server/0389-Optimize-Pathfinding.patch delete mode 100644 patches/server/0390-Optimize-Pathfinding.patch create mode 100644 patches/server/0390-Reduce-Either-Optional-allocation.patch delete mode 100644 patches/server/0391-Reduce-Either-Optional-allocation.patch create mode 100644 patches/server/0391-Reduce-memory-footprint-of-NBTTagCompound.patch create mode 100644 patches/server/0392-Prevent-opening-inventories-when-frozen.patch delete mode 100644 patches/server/0392-Reduce-memory-footprint-of-NBTTagCompound.patch create mode 100644 patches/server/0393-Optimise-ArraySetSorted-removeIf.patch delete mode 100644 patches/server/0393-Prevent-opening-inventories-when-frozen.patch create mode 100644 patches/server/0394-Don-t-run-entity-collision-code-if-not-needed.patch delete mode 100644 patches/server/0394-Optimise-ArraySetSorted-removeIf.patch delete mode 100644 patches/server/0395-Don-t-run-entity-collision-code-if-not-needed.patch create mode 100644 patches/server/0395-Implement-Player-Client-Options-API.patch create mode 100644 patches/server/0396-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch delete mode 100644 patches/server/0396-Implement-Player-Client-Options-API.patch delete mode 100644 patches/server/0397-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch create mode 100644 patches/server/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch delete mode 100644 patches/server/0398-Fix-Chunk-Post-Processing-deadlock-risk.patch create mode 100644 patches/server/0398-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch delete mode 100644 patches/server/0399-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch create mode 100644 patches/server/0399-Load-Chunks-for-Login-Asynchronously.patch delete mode 100644 patches/server/0400-Load-Chunks-for-Login-Asynchronously.patch create mode 100644 patches/server/0400-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch create mode 100644 patches/server/0401-Add-PlayerAttackEntityCooldownResetEvent.patch delete mode 100644 patches/server/0401-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch delete mode 100644 patches/server/0402-Add-PlayerAttackEntityCooldownResetEvent.patch create mode 100644 patches/server/0402-Don-t-fire-BlockFade-on-worldgen-threads.patch create mode 100644 patches/server/0403-Add-phantom-creative-and-insomniac-controls.patch delete mode 100644 patches/server/0403-Don-t-fire-BlockFade-on-worldgen-threads.patch delete mode 100644 patches/server/0404-Add-phantom-creative-and-insomniac-controls.patch create mode 100644 patches/server/0404-Fix-numerous-item-duplication-issues-and-teleport-is.patch delete mode 100644 patches/server/0405-Fix-numerous-item-duplication-issues-and-teleport-is.patch create mode 100644 patches/server/0405-Villager-Restocks-API.patch create mode 100644 patches/server/0406-Validate-PickItem-Packet-and-kick-for-invalid.patch delete mode 100644 patches/server/0406-Villager-Restocks-API.patch create mode 100644 patches/server/0407-Expose-game-version.patch delete mode 100644 patches/server/0407-Validate-PickItem-Packet-and-kick-for-invalid.patch delete mode 100644 patches/server/0408-Expose-game-version.patch create mode 100644 patches/server/0408-Optimize-Voxel-Shape-Merging.patch delete mode 100644 patches/server/0409-Optimize-Voxel-Shape-Merging.patch create mode 100644 patches/server/0409-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch delete mode 100644 patches/server/0410-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch create mode 100644 patches/server/0410-misc-debugging-dumps.patch create mode 100644 patches/server/0411-Prevent-teleporting-dead-entities.patch delete mode 100644 patches/server/0411-misc-debugging-dumps.patch create mode 100644 patches/server/0412-Deobfuscate-stacktraces-in-log-messages-crash-report.patch delete mode 100644 patches/server/0412-Prevent-teleporting-dead-entities.patch delete mode 100644 patches/server/0413-Deobfuscate-stacktraces-in-log-messages-crash-report.patch create mode 100644 patches/server/0413-Implement-Mob-Goal-API.patch create mode 100644 patches/server/0414-Add-villager-reputation-API.patch delete mode 100644 patches/server/0414-Implement-Mob-Goal-API.patch delete mode 100644 patches/server/0415-Add-villager-reputation-API.patch create mode 100644 patches/server/0415-Option-for-maximum-exp-value-when-merging-orbs.patch create mode 100644 patches/server/0416-ExperienceOrbMergeEvent.patch delete mode 100644 patches/server/0416-Option-for-maximum-exp-value-when-merging-orbs.patch delete mode 100644 patches/server/0417-ExperienceOrbMergeEvent.patch create mode 100644 patches/server/0417-Fix-PotionEffect-ignores-icon-flag.patch delete mode 100644 patches/server/0418-Fix-PotionEffect-ignores-icon-flag.patch create mode 100644 patches/server/0418-Optimize-brigadier-child-sorting-performance.patch delete mode 100644 patches/server/0419-Optimize-brigadier-child-sorting-performance.patch create mode 100644 patches/server/0419-Potential-bed-API.patch delete mode 100644 patches/server/0420-Potential-bed-API.patch create mode 100644 patches/server/0420-Wait-for-Async-Tasks-during-shutdown.patch create mode 100644 patches/server/0421-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch delete mode 100644 patches/server/0421-Wait-for-Async-Tasks-during-shutdown.patch delete mode 100644 patches/server/0422-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch create mode 100644 patches/server/0422-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch delete mode 100644 patches/server/0423-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch create mode 100644 patches/server/0423-Reduce-MutableInt-allocations-from-light-engine.patch delete mode 100644 patches/server/0424-Reduce-MutableInt-allocations-from-light-engine.patch create mode 100644 patches/server/0424-Reduce-allocation-of-Vec3D-by-entity-tracker.patch create mode 100644 patches/server/0425-Ensure-safe-gateway-teleport.patch delete mode 100644 patches/server/0425-Reduce-allocation-of-Vec3D-by-entity-tracker.patch create mode 100644 patches/server/0426-Add-option-for-console-having-all-permissions.patch delete mode 100644 patches/server/0426-Ensure-safe-gateway-teleport.patch delete mode 100644 patches/server/0427-Add-option-for-console-having-all-permissions.patch create mode 100644 patches/server/0427-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch delete mode 100644 patches/server/0428-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch create mode 100644 patches/server/0428-Use-distance-map-to-optimise-entity-tracker.patch create mode 100644 patches/server/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch delete mode 100644 patches/server/0429-Use-distance-map-to-optimise-entity-tracker.patch create mode 100644 patches/server/0430-Fix-villager-trading-demand-MC-163962.patch delete mode 100644 patches/server/0430-Optimize-ServerLevels-chunk-level-checking-methods.patch delete mode 100644 patches/server/0431-Fix-villager-trading-demand-MC-163962.patch create mode 100644 patches/server/0431-Maps-shouldn-t-load-chunks.patch delete mode 100644 patches/server/0432-Maps-shouldn-t-load-chunks.patch create mode 100644 patches/server/0432-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch create mode 100644 patches/server/0433-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch delete mode 100644 patches/server/0433-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch delete mode 100644 patches/server/0434-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch create mode 100644 patches/server/0434-Fix-piston-physics-inconsistency-MC-188840.patch delete mode 100644 patches/server/0435-Fix-piston-physics-inconsistency-MC-188840.patch create mode 100644 patches/server/0435-Fix-sand-duping.patch create mode 100644 patches/server/0436-Fix-missing-chunks-due-to-integer-overflow.patch delete mode 100644 patches/server/0436-Fix-sand-duping.patch delete mode 100644 patches/server/0437-Fix-missing-chunks-due-to-integer-overflow.patch create mode 100644 patches/server/0437-Prevent-position-desync-in-playerconnection-causing-.patch create mode 100644 patches/server/0438-Inventory-getHolder-method-without-block-snapshot.patch delete mode 100644 patches/server/0438-Prevent-position-desync-in-playerconnection-causing-.patch create mode 100644 patches/server/0439-Improve-Arrow-API.patch delete mode 100644 patches/server/0439-Inventory-getHolder-method-without-block-snapshot.patch create mode 100644 patches/server/0440-Add-and-implement-PlayerRecipeBookClickEvent.patch delete mode 100644 patches/server/0440-Improve-Arrow-API.patch delete mode 100644 patches/server/0441-Add-and-implement-PlayerRecipeBookClickEvent.patch create mode 100644 patches/server/0441-Hide-sync-chunk-writes-behind-flag.patch create mode 100644 patches/server/0442-Add-permission-for-command-blocks.patch delete mode 100644 patches/server/0442-Hide-sync-chunk-writes-behind-flag.patch delete mode 100644 patches/server/0443-Add-permission-for-command-blocks.patch create mode 100644 patches/server/0443-Ensure-Entity-AABB-s-are-never-invalid.patch delete mode 100644 patches/server/0444-Ensure-Entity-AABB-s-are-never-invalid.patch create mode 100644 patches/server/0444-Fix-Per-World-Difficulty-Remembering-Difficulty.patch delete mode 100644 patches/server/0445-Fix-Per-World-Difficulty-Remembering-Difficulty.patch create mode 100644 patches/server/0445-Paper-dumpitem-command.patch create mode 100644 patches/server/0446-Don-t-allow-null-UUID-s-for-chat.patch delete mode 100644 patches/server/0446-Paper-dumpitem-command.patch delete mode 100644 patches/server/0447-Don-t-allow-null-UUID-s-for-chat.patch create mode 100644 patches/server/0447-Improve-Legacy-Component-serialization-size.patch delete mode 100644 patches/server/0448-Improve-Legacy-Component-serialization-size.patch create mode 100644 patches/server/0448-Optimize-Bit-Operations-by-inlining.patch create mode 100644 patches/server/0449-Add-Plugin-Tickets-to-API-Chunk-Methods.patch delete mode 100644 patches/server/0449-Optimize-Bit-Operations-by-inlining.patch delete mode 100644 patches/server/0450-Add-Plugin-Tickets-to-API-Chunk-Methods.patch create mode 100644 patches/server/0450-incremental-chunk-and-player-saving.patch create mode 100644 patches/server/0451-Stop-copy-on-write-operations-for-updating-light-dat.patch delete mode 100644 patches/server/0451-incremental-chunk-and-player-saving.patch delete mode 100644 patches/server/0452-Stop-copy-on-write-operations-for-updating-light-dat.patch create mode 100644 patches/server/0452-Support-old-UUID-format-for-NBT.patch create mode 100644 patches/server/0453-Clean-up-duplicated-GameProfile-Properties.patch delete mode 100644 patches/server/0453-Support-old-UUID-format-for-NBT.patch delete mode 100644 patches/server/0454-Clean-up-duplicated-GameProfile-Properties.patch create mode 100644 patches/server/0454-Convert-legacy-attributes-in-Item-Meta.patch delete mode 100644 patches/server/0455-Convert-legacy-attributes-in-Item-Meta.patch create mode 100644 patches/server/0455-Remove-some-streams-from-structures.patch delete mode 100644 patches/server/0456-Remove-some-streams-from-structures.patch create mode 100644 patches/server/0456-Remove-streams-from-classes-related-villager-gossip.patch delete mode 100644 patches/server/0457-Remove-streams-from-classes-related-villager-gossip.patch create mode 100644 patches/server/0457-Support-components-in-ItemMeta.patch create mode 100644 patches/server/0458-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch delete mode 100644 patches/server/0458-Support-components-in-ItemMeta.patch create mode 100644 patches/server/0459-Add-entity-liquid-API.patch delete mode 100644 patches/server/0459-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch delete mode 100644 patches/server/0460-Add-entity-liquid-API.patch create mode 100644 patches/server/0460-Update-itemstack-legacy-name-and-lore.patch create mode 100644 patches/server/0461-Spawn-player-in-correct-world-on-login.patch delete mode 100644 patches/server/0461-Update-itemstack-legacy-name-and-lore.patch create mode 100644 patches/server/0462-Add-PrepareResultEvent.patch delete mode 100644 patches/server/0462-Spawn-player-in-correct-world-on-login.patch delete mode 100644 patches/server/0463-Add-PrepareResultEvent.patch create mode 100644 patches/server/0463-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch delete mode 100644 patches/server/0464-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch create mode 100644 patches/server/0464-Optimize-NetworkManager-Exception-Handling.patch delete mode 100644 patches/server/0465-Optimize-NetworkManager-Exception-Handling.patch create mode 100644 patches/server/0465-Optimize-the-advancement-data-player-iteration-to-be.patch create mode 100644 patches/server/0466-Fix-arrows-never-despawning-MC-125757.patch delete mode 100644 patches/server/0466-Optimize-the-advancement-data-player-iteration-to-be.patch delete mode 100644 patches/server/0467-Fix-arrows-never-despawning-MC-125757.patch create mode 100644 patches/server/0467-Thread-Safe-Vanilla-Command-permission-checking.patch create mode 100644 patches/server/0468-Fix-SPIGOT-5989.patch delete mode 100644 patches/server/0468-Thread-Safe-Vanilla-Command-permission-checking.patch create mode 100644 patches/server/0469-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch delete mode 100644 patches/server/0469-Fix-SPIGOT-5989.patch delete mode 100644 patches/server/0470-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch create mode 100644 patches/server/0470-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch create mode 100644 patches/server/0471-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch delete mode 100644 patches/server/0471-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch create mode 100644 patches/server/0472-Add-missing-strikeLighting-call-to-World-spigot-stri.patch delete mode 100644 patches/server/0472-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch delete mode 100644 patches/server/0473-Add-missing-strikeLighting-call-to-World-spigot-stri.patch create mode 100644 patches/server/0473-Fix-some-rails-connecting-improperly.patch create mode 100644 patches/server/0474-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch delete mode 100644 patches/server/0474-Fix-some-rails-connecting-improperly.patch create mode 100644 patches/server/0475-Do-not-let-the-server-load-chunks-from-newer-version.patch delete mode 100644 patches/server/0475-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch create mode 100644 patches/server/0476-Brand-support.patch delete mode 100644 patches/server/0476-Do-not-let-the-server-load-chunks-from-newer-version.patch create mode 100644 patches/server/0477-Add-setMaxPlayers-API.patch delete mode 100644 patches/server/0477-Brand-support.patch create mode 100644 patches/server/0478-Add-playPickupItemAnimation-to-LivingEntity.patch delete mode 100644 patches/server/0478-Add-setMaxPlayers-API.patch delete mode 100644 patches/server/0479-Add-playPickupItemAnimation-to-LivingEntity.patch create mode 100644 patches/server/0479-Don-t-require-FACING-data.patch delete mode 100644 patches/server/0480-Don-t-require-FACING-data.patch create mode 100644 patches/server/0480-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch create mode 100644 patches/server/0481-Add-moon-phase-API.patch delete mode 100644 patches/server/0481-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch delete mode 100644 patches/server/0482-Add-moon-phase-API.patch create mode 100644 patches/server/0482-Improve-Chunk-Status-Transition-Speed.patch delete mode 100644 patches/server/0483-Improve-Chunk-Status-Transition-Speed.patch create mode 100644 patches/server/0483-Prevent-headless-pistons-from-being-created.patch create mode 100644 patches/server/0484-Add-BellRingEvent.patch delete mode 100644 patches/server/0484-Prevent-headless-pistons-from-being-created.patch delete mode 100644 patches/server/0485-Add-BellRingEvent.patch create mode 100644 patches/server/0485-Add-zombie-targets-turtle-egg-config.patch delete mode 100644 patches/server/0486-Add-zombie-targets-turtle-egg-config.patch create mode 100644 patches/server/0486-Buffer-joins-to-world.patch delete mode 100644 patches/server/0487-Buffer-joins-to-world.patch create mode 100644 patches/server/0487-Eigencraft-redstone-implementation.patch delete mode 100644 patches/server/0488-Eigencraft-redstone-implementation.patch create mode 100644 patches/server/0488-Fix-hex-colors-not-working-in-some-kick-messages.patch delete mode 100644 patches/server/0489-Fix-hex-colors-not-working-in-some-kick-messages.patch create mode 100644 patches/server/0489-PortalCreateEvent-needs-to-know-its-entity.patch create mode 100644 patches/server/0490-Fix-CraftTeam-null-check.patch delete mode 100644 patches/server/0490-PortalCreateEvent-needs-to-know-its-entity.patch create mode 100644 patches/server/0491-Add-more-Evoker-API.patch delete mode 100644 patches/server/0491-Fix-CraftTeam-null-check.patch create mode 100644 patches/server/0492-Add-methods-to-get-translation-keys.patch delete mode 100644 patches/server/0492-Add-more-Evoker-API.patch delete mode 100644 patches/server/0493-Add-methods-to-get-translation-keys.patch create mode 100644 patches/server/0493-Create-HoverEvent-from-ItemStack-Entity.patch create mode 100644 patches/server/0494-Cache-block-data-strings.patch delete mode 100644 patches/server/0494-Create-HoverEvent-from-ItemStack-Entity.patch delete mode 100644 patches/server/0495-Cache-block-data-strings.patch create mode 100644 patches/server/0495-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch create mode 100644 patches/server/0496-Add-additional-open-container-api-to-HumanEntity.patch delete mode 100644 patches/server/0496-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch delete mode 100644 patches/server/0497-Add-additional-open-container-api-to-HumanEntity.patch create mode 100644 patches/server/0497-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch delete mode 100644 patches/server/0498-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch create mode 100644 patches/server/0498-Extend-block-drop-capture-to-capture-all-items-added.patch create mode 100644 patches/server/0499-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch delete mode 100644 patches/server/0499-Extend-block-drop-capture-to-capture-all-items-added.patch delete mode 100644 patches/server/0500-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch create mode 100644 patches/server/0500-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch delete mode 100644 patches/server/0501-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch create mode 100644 patches/server/0501-Lazily-track-plugin-scoreboards-by-default.patch create mode 100644 patches/server/0502-Entity-isTicking.patch delete mode 100644 patches/server/0502-Lazily-track-plugin-scoreboards-by-default.patch delete mode 100644 patches/server/0503-Entity-isTicking.patch create mode 100644 patches/server/0503-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch create mode 100644 patches/server/0504-Fix-Concurrency-issue-in-ShufflingList.patch delete mode 100644 patches/server/0504-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch delete mode 100644 patches/server/0505-Fix-Concurrency-issue-in-ShufflingList.patch create mode 100644 patches/server/0505-Reset-Ender-Crystals-on-Dragon-Spawn.patch create mode 100644 patches/server/0506-Fix-for-large-move-vectors-crashing-server.patch delete mode 100644 patches/server/0506-Reset-Ender-Crystals-on-Dragon-Spawn.patch delete mode 100644 patches/server/0507-Fix-for-large-move-vectors-crashing-server.patch create mode 100644 patches/server/0507-Optimise-getType-calls.patch delete mode 100644 patches/server/0508-Optimise-getType-calls.patch create mode 100644 patches/server/0508-Villager-resetOffers.patch create mode 100644 patches/server/0509-Improve-inlinig-for-some-hot-IBlockData-methods.patch delete mode 100644 patches/server/0509-Villager-resetOffers.patch delete mode 100644 patches/server/0510-Improve-inlinig-for-some-hot-IBlockData-methods.patch create mode 100644 patches/server/0510-Retain-block-place-order-when-capturing-blockstates.patch create mode 100644 patches/server/0511-Reduce-blockpos-allocation-from-pathfinding.patch delete mode 100644 patches/server/0511-Retain-block-place-order-when-capturing-blockstates.patch create mode 100644 patches/server/0512-Fix-item-locations-dropped-from-campfires.patch delete mode 100644 patches/server/0512-Reduce-blockpos-allocation-from-pathfinding.patch delete mode 100644 patches/server/0513-Fix-item-locations-dropped-from-campfires.patch create mode 100644 patches/server/0513-Player-elytra-boost-API.patch create mode 100644 patches/server/0514-Fixed-TileEntityBell-memory-leak.patch delete mode 100644 patches/server/0514-Player-elytra-boost-API.patch create mode 100644 patches/server/0515-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch delete mode 100644 patches/server/0515-Fixed-TileEntityBell-memory-leak.patch create mode 100644 patches/server/0516-Add-getOfflinePlayerIfCached-String.patch delete mode 100644 patches/server/0516-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch delete mode 100644 patches/server/0517-Add-getOfflinePlayerIfCached-String.patch create mode 100644 patches/server/0517-Add-ignore-discounts-API.patch delete mode 100644 patches/server/0518-Add-ignore-discounts-API.patch create mode 100644 patches/server/0518-Toggle-for-removing-existing-dragon.patch create mode 100644 patches/server/0519-Fix-client-lag-on-advancement-loading.patch delete mode 100644 patches/server/0519-Toggle-for-removing-existing-dragon.patch delete mode 100644 patches/server/0520-Fix-client-lag-on-advancement-loading.patch create mode 100644 patches/server/0520-Item-no-age-no-player-pickup.patch delete mode 100644 patches/server/0521-Item-no-age-no-player-pickup.patch create mode 100644 patches/server/0521-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch create mode 100644 patches/server/0522-Beacon-API-custom-effect-ranges.patch delete mode 100644 patches/server/0522-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch create mode 100644 patches/server/0523-Add-API-for-quit-reason.patch delete mode 100644 patches/server/0523-Beacon-API-custom-effect-ranges.patch delete mode 100644 patches/server/0524-Add-API-for-quit-reason.patch create mode 100644 patches/server/0524-Add-Wandering-Trader-spawn-rate-config-options.patch delete mode 100644 patches/server/0525-Add-Wandering-Trader-spawn-rate-config-options.patch create mode 100644 patches/server/0525-Expose-world-spawn-angle.patch create mode 100644 patches/server/0526-Add-Destroy-Speed-API.patch delete mode 100644 patches/server/0526-Expose-world-spawn-angle.patch delete mode 100644 patches/server/0527-Add-Destroy-Speed-API.patch create mode 100644 patches/server/0527-Fix-Player-spawnParticle-x-y-z-precision-loss.patch create mode 100644 patches/server/0528-Add-LivingEntity-clearActiveItem.patch delete mode 100644 patches/server/0528-Fix-Player-spawnParticle-x-y-z-precision-loss.patch delete mode 100644 patches/server/0529-Add-LivingEntity-clearActiveItem.patch create mode 100644 patches/server/0529-Add-PlayerItemCooldownEvent.patch delete mode 100644 patches/server/0530-Add-PlayerItemCooldownEvent.patch create mode 100644 patches/server/0530-Significantly-improve-performance-of-the-end-generat.patch create mode 100644 patches/server/0531-More-lightning-API.patch delete mode 100644 patches/server/0531-Significantly-improve-performance-of-the-end-generat.patch create mode 100644 patches/server/0532-Climbing-should-not-bypass-cramming-gamerule.patch delete mode 100644 patches/server/0532-More-lightning-API.patch create mode 100644 patches/server/0533-Added-missing-default-perms-for-commands.patch delete mode 100644 patches/server/0533-Climbing-should-not-bypass-cramming-gamerule.patch create mode 100644 patches/server/0534-Add-PlayerShearBlockEvent.patch delete mode 100644 patches/server/0534-Added-missing-default-perms-for-commands.patch delete mode 100644 patches/server/0535-Add-PlayerShearBlockEvent.patch create mode 100644 patches/server/0535-Fix-curing-zombie-villager-discount-exploit.patch delete mode 100644 patches/server/0536-Fix-curing-zombie-villager-discount-exploit.patch create mode 100644 patches/server/0536-Limit-recipe-packets.patch create mode 100644 patches/server/0537-Fix-CraftSound-backwards-compatibility.patch delete mode 100644 patches/server/0537-Limit-recipe-packets.patch delete mode 100644 patches/server/0538-Fix-CraftSound-backwards-compatibility.patch create mode 100644 patches/server/0538-Player-Chunk-Load-Unload-Events.patch create mode 100644 patches/server/0539-Optimize-Dynamic-get-Missing-Keys.patch delete mode 100644 patches/server/0539-Player-Chunk-Load-Unload-Events.patch create mode 100644 patches/server/0540-Expose-LivingEntity-hurt-direction.patch delete mode 100644 patches/server/0540-Optimize-Dynamic-get-Missing-Keys.patch create mode 100644 patches/server/0541-Add-OBSTRUCTED-reason-to-BedEnterResult.patch delete mode 100644 patches/server/0541-Expose-LivingEntity-hurt-direction.patch delete mode 100644 patches/server/0542-Add-OBSTRUCTED-reason-to-BedEnterResult.patch create mode 100644 patches/server/0542-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch create mode 100644 patches/server/0543-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch delete mode 100644 patches/server/0543-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch delete mode 100644 patches/server/0544-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch create mode 100644 patches/server/0544-Implement-TargetHitEvent.patch delete mode 100644 patches/server/0545-Implement-TargetHitEvent.patch create mode 100644 patches/server/0545-MC-4-Fix-item-position-desync.patch create mode 100644 patches/server/0546-Additional-Block-Material-API-s.patch delete mode 100644 patches/server/0546-MC-4-Fix-item-position-desync.patch delete mode 100644 patches/server/0547-Additional-Block-Material-API-s.patch create mode 100644 patches/server/0547-Fix-harming-potion-dupe.patch delete mode 100644 patches/server/0548-Fix-harming-potion-dupe.patch create mode 100644 patches/server/0548-Implement-API-to-get-Material-from-Boats-and-Minecar.patch create mode 100644 patches/server/0549-Cache-burn-durations.patch delete mode 100644 patches/server/0549-Implement-API-to-get-Material-from-Boats-and-Minecar.patch create mode 100644 patches/server/0550-Allow-disabling-mob-spawner-spawn-egg-transformation.patch delete mode 100644 patches/server/0550-Cache-burn-durations.patch delete mode 100644 patches/server/0551-Allow-disabling-mob-spawner-spawn-egg-transformation.patch create mode 100644 patches/server/0551-Fix-Not-a-string-Map-Conversion-spam.patch delete mode 100644 patches/server/0552-Fix-Not-a-string-Map-Conversion-spam.patch create mode 100644 patches/server/0552-Implement-PlayerFlowerPotManipulateEvent.patch create mode 100644 patches/server/0553-Fix-interact-event-not-being-called-in-adventure.patch delete mode 100644 patches/server/0553-Implement-PlayerFlowerPotManipulateEvent.patch delete mode 100644 patches/server/0554-Fix-interact-event-not-being-called-in-adventure.patch create mode 100644 patches/server/0554-Zombie-API-breaking-doors.patch create mode 100644 patches/server/0555-Fix-nerfed-slime-when-splitting.patch delete mode 100644 patches/server/0555-Zombie-API-breaking-doors.patch create mode 100644 patches/server/0556-Add-EntityLoadCrossbowEvent.patch delete mode 100644 patches/server/0556-Fix-nerfed-slime-when-splitting.patch delete mode 100644 patches/server/0557-Add-EntityLoadCrossbowEvent.patch create mode 100644 patches/server/0557-Guardian-beam-workaround.patch create mode 100644 patches/server/0558-Added-WorldGameRuleChangeEvent.patch delete mode 100644 patches/server/0558-Guardian-beam-workaround.patch create mode 100644 patches/server/0559-Added-ServerResourcesReloadedEvent.patch delete mode 100644 patches/server/0559-Added-WorldGameRuleChangeEvent.patch delete mode 100644 patches/server/0560-Added-ServerResourcesReloadedEvent.patch create mode 100644 patches/server/0560-Added-world-settings-for-mobs-picking-up-loot.patch delete mode 100644 patches/server/0561-Added-world-settings-for-mobs-picking-up-loot.patch create mode 100644 patches/server/0561-Implemented-BlockFailedDispenseEvent.patch create mode 100644 patches/server/0562-Added-PlayerLecternPageChangeEvent.patch delete mode 100644 patches/server/0562-Implemented-BlockFailedDispenseEvent.patch delete mode 100644 patches/server/0563-Added-PlayerLecternPageChangeEvent.patch create mode 100644 patches/server/0563-Added-PlayerLoomPatternSelectEvent.patch delete mode 100644 patches/server/0564-Added-PlayerLoomPatternSelectEvent.patch create mode 100644 patches/server/0564-Configurable-door-breaking-difficulty.patch delete mode 100644 patches/server/0565-Configurable-door-breaking-difficulty.patch create mode 100644 patches/server/0565-Empty-commands-shall-not-be-dispatched.patch delete mode 100644 patches/server/0566-Empty-commands-shall-not-be-dispatched.patch create mode 100644 patches/server/0566-Implement-API-to-expose-exact-interaction-point.patch delete mode 100644 patches/server/0567-Implement-API-to-expose-exact-interaction-point.patch create mode 100644 patches/server/0567-Remove-stale-POIs.patch create mode 100644 patches/server/0568-Fix-villager-boat-exploit.patch delete mode 100644 patches/server/0568-Remove-stale-POIs.patch create mode 100644 patches/server/0569-Add-sendOpLevel-API.patch delete mode 100644 patches/server/0569-Fix-villager-boat-exploit.patch create mode 100644 patches/server/0570-Add-PaperRegistry.patch delete mode 100644 patches/server/0570-Add-sendOpLevel-API.patch delete mode 100644 patches/server/0571-Add-PaperRegistry.patch create mode 100644 patches/server/0571-Add-StructuresLocateEvent.patch delete mode 100644 patches/server/0572-Add-StructuresLocateEvent.patch create mode 100644 patches/server/0572-Collision-option-for-requiring-a-player-participant.patch delete mode 100644 patches/server/0573-Collision-option-for-requiring-a-player-participant.patch create mode 100644 patches/server/0573-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch delete mode 100644 patches/server/0574-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch create mode 100644 patches/server/0574-Return-chat-component-with-empty-text-instead-of-thr.patch create mode 100644 patches/server/0575-Make-schedule-command-per-world.patch delete mode 100644 patches/server/0575-Return-chat-component-with-empty-text-instead-of-thr.patch create mode 100644 patches/server/0576-Configurable-max-leash-distance.patch delete mode 100644 patches/server/0576-Make-schedule-command-per-world.patch delete mode 100644 patches/server/0577-Configurable-max-leash-distance.patch create mode 100644 patches/server/0577-Implement-BlockPreDispenseEvent.patch create mode 100644 patches/server/0578-Added-firing-of-PlayerChangeBeaconEffectEvent.patch delete mode 100644 patches/server/0578-Implement-BlockPreDispenseEvent.patch create mode 100644 patches/server/0579-Add-toggle-for-always-placing-the-dragon-egg.patch delete mode 100644 patches/server/0579-Added-firing-of-PlayerChangeBeaconEffectEvent.patch delete mode 100644 patches/server/0580-Add-toggle-for-always-placing-the-dragon-egg.patch create mode 100644 patches/server/0580-Added-PlayerStonecutterRecipeSelectEvent.patch create mode 100644 patches/server/0581-Add-dropLeash-variable-to-EntityUnleashEvent.patch delete mode 100644 patches/server/0581-Added-PlayerStonecutterRecipeSelectEvent.patch delete mode 100644 patches/server/0582-Add-dropLeash-variable-to-EntityUnleashEvent.patch create mode 100644 patches/server/0582-Reset-shield-blocking-on-dimension-change.patch delete mode 100644 patches/server/0583-Reset-shield-blocking-on-dimension-change.patch create mode 100644 patches/server/0583-add-DragonEggFormEvent.patch create mode 100644 patches/server/0584-EntityMoveEvent.patch delete mode 100644 patches/server/0584-add-DragonEggFormEvent.patch delete mode 100644 patches/server/0585-EntityMoveEvent.patch create mode 100644 patches/server/0585-added-option-to-disable-pathfinding-updates-on-block.patch create mode 100644 patches/server/0586-Inline-shift-direction-fields.patch delete mode 100644 patches/server/0586-added-option-to-disable-pathfinding-updates-on-block.patch create mode 100644 patches/server/0587-Allow-adding-items-to-BlockDropItemEvent.patch delete mode 100644 patches/server/0587-Inline-shift-direction-fields.patch create mode 100644 patches/server/0588-Add-getMainThreadExecutor-to-BukkitScheduler.patch delete mode 100644 patches/server/0588-Allow-adding-items-to-BlockDropItemEvent.patch delete mode 100644 patches/server/0589-Add-getMainThreadExecutor-to-BukkitScheduler.patch create mode 100644 patches/server/0589-living-entity-allow-attribute-registration.patch create mode 100644 patches/server/0590-fix-dead-slime-setSize-invincibility.patch delete mode 100644 patches/server/0590-living-entity-allow-attribute-registration.patch create mode 100644 patches/server/0591-Merchant-getRecipes-should-return-an-immutable-list.patch delete mode 100644 patches/server/0591-fix-dead-slime-setSize-invincibility.patch create mode 100644 patches/server/0592-Add-support-for-hex-color-codes-in-console.patch delete mode 100644 patches/server/0592-Merchant-getRecipes-should-return-an-immutable-list.patch delete mode 100644 patches/server/0593-Add-support-for-hex-color-codes-in-console.patch create mode 100644 patches/server/0593-Expose-Tracked-Players.patch delete mode 100644 patches/server/0594-Expose-Tracked-Players.patch create mode 100644 patches/server/0594-Remove-streams-from-SensorNearest.patch delete mode 100644 patches/server/0595-Remove-streams-from-SensorNearest.patch create mode 100644 patches/server/0595-Throw-proper-exception-on-empty-JsonList-file.patch create mode 100644 patches/server/0596-Improve-ServerGUI.patch delete mode 100644 patches/server/0596-Throw-proper-exception-on-empty-JsonList-file.patch delete mode 100644 patches/server/0597-Improve-ServerGUI.patch create mode 100644 patches/server/0597-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch create mode 100644 patches/server/0598-fix-converting-txt-to-json-file.patch delete mode 100644 patches/server/0598-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch create mode 100644 patches/server/0599-Add-worldborder-events.patch delete mode 100644 patches/server/0599-fix-converting-txt-to-json-file.patch delete mode 100644 patches/server/0600-Add-worldborder-events.patch create mode 100644 patches/server/0600-added-PlayerNameEntityEvent.patch create mode 100644 patches/server/0601-Prevent-grindstones-from-overstacking-items.patch delete mode 100644 patches/server/0601-added-PlayerNameEntityEvent.patch create mode 100644 patches/server/0602-Add-recipe-to-cook-events.patch delete mode 100644 patches/server/0602-Prevent-grindstones-from-overstacking-items.patch create mode 100644 patches/server/0603-Add-Block-isValidTool.patch delete mode 100644 patches/server/0603-Add-recipe-to-cook-events.patch delete mode 100644 patches/server/0604-Add-Block-isValidTool.patch create mode 100644 patches/server/0604-Allow-using-signs-inside-spawn-protection.patch delete mode 100644 patches/server/0605-Allow-using-signs-inside-spawn-protection.patch create mode 100644 patches/server/0605-Expand-world-key-API.patch create mode 100644 patches/server/0606-Add-fast-alternative-constructor-for-Rotations.patch delete mode 100644 patches/server/0606-Expand-world-key-API.patch delete mode 100644 patches/server/0607-Add-fast-alternative-constructor-for-Rotations.patch create mode 100644 patches/server/0607-Item-Rarity-API.patch delete mode 100644 patches/server/0608-Item-Rarity-API.patch create mode 100644 patches/server/0608-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch delete mode 100644 patches/server/0609-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch create mode 100644 patches/server/0609-copy-TESign-isEditable-from-snapshots.patch create mode 100644 patches/server/0610-Drop-carried-item-when-player-has-disconnected.patch delete mode 100644 patches/server/0610-copy-TESign-isEditable-from-snapshots.patch delete mode 100644 patches/server/0611-Drop-carried-item-when-player-has-disconnected.patch create mode 100644 patches/server/0611-forced-whitelist-use-configurable-kick-message.patch create mode 100644 patches/server/0612-Don-t-ignore-result-of-PlayerEditBookEvent.patch delete mode 100644 patches/server/0612-forced-whitelist-use-configurable-kick-message.patch delete mode 100644 patches/server/0613-Don-t-ignore-result-of-PlayerEditBookEvent.patch create mode 100644 patches/server/0613-Entity-load-save-limit-per-chunk.patch delete mode 100644 patches/server/0614-Entity-load-save-limit-per-chunk.patch create mode 100644 patches/server/0614-Expose-protocol-version.patch create mode 100644 patches/server/0615-Enhance-console-tab-completions-for-brigadier-comman.patch delete mode 100644 patches/server/0615-Expose-protocol-version.patch delete mode 100644 patches/server/0616-Enhance-console-tab-completions-for-brigadier-comman.patch create mode 100644 patches/server/0616-Fix-PlayerItemConsumeEvent-cancelling-properly.patch create mode 100644 patches/server/0617-Add-bypass-host-check.patch delete mode 100644 patches/server/0617-Fix-PlayerItemConsumeEvent-cancelling-properly.patch delete mode 100644 patches/server/0618-Add-bypass-host-check.patch create mode 100644 patches/server/0618-Set-area-affect-cloud-rotation.patch delete mode 100644 patches/server/0619-Set-area-affect-cloud-rotation.patch create mode 100644 patches/server/0619-add-isDeeplySleeping-to-HumanEntity.patch create mode 100644 patches/server/0620-add-consumeFuel-to-FurnaceBurnEvent.patch delete mode 100644 patches/server/0620-add-isDeeplySleeping-to-HumanEntity.patch delete mode 100644 patches/server/0621-add-consumeFuel-to-FurnaceBurnEvent.patch create mode 100644 patches/server/0621-add-get-set-drop-chance-to-EntityEquipment.patch delete mode 100644 patches/server/0622-add-get-set-drop-chance-to-EntityEquipment.patch create mode 100644 patches/server/0622-fix-PigZombieAngerEvent-cancellation.patch create mode 100644 patches/server/0623-Fix-checkReach-check-for-Shulker-boxes.patch delete mode 100644 patches/server/0623-fix-PigZombieAngerEvent-cancellation.patch delete mode 100644 patches/server/0624-Fix-checkReach-check-for-Shulker-boxes.patch create mode 100644 patches/server/0624-fix-PlayerItemHeldEvent-firing-twice.patch create mode 100644 patches/server/0625-Added-PlayerDeepSleepEvent.patch delete mode 100644 patches/server/0625-fix-PlayerItemHeldEvent-firing-twice.patch delete mode 100644 patches/server/0626-Added-PlayerDeepSleepEvent.patch create mode 100644 patches/server/0626-More-World-API.patch create mode 100644 patches/server/0627-Added-PlayerBedFailEnterEvent.patch delete mode 100644 patches/server/0627-More-World-API.patch delete mode 100644 patches/server/0628-Added-PlayerBedFailEnterEvent.patch create mode 100644 patches/server/0628-Implement-methods-to-convert-between-Component-and-B.patch create mode 100644 patches/server/0629-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch delete mode 100644 patches/server/0629-Implement-methods-to-convert-between-Component-and-B.patch delete mode 100644 patches/server/0630-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch create mode 100644 patches/server/0630-Introduce-beacon-activation-deactivation-events.patch delete mode 100644 patches/server/0631-Introduce-beacon-activation-deactivation-events.patch create mode 100644 patches/server/0631-add-RespawnFlags-to-PlayerRespawnEvent.patch create mode 100644 patches/server/0632-Add-Channel-initialization-listeners.patch delete mode 100644 patches/server/0632-add-RespawnFlags-to-PlayerRespawnEvent.patch delete mode 100644 patches/server/0633-Add-Channel-initialization-listeners.patch create mode 100644 patches/server/0633-Send-empty-commands-if-tab-completion-is-disabled.patch create mode 100644 patches/server/0634-Add-more-WanderingTrader-API.patch delete mode 100644 patches/server/0634-Send-empty-commands-if-tab-completion-is-disabled.patch create mode 100644 patches/server/0635-Add-EntityBlockStorage-clearEntities.patch delete mode 100644 patches/server/0635-Add-more-WanderingTrader-API.patch create mode 100644 patches/server/0636-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch delete mode 100644 patches/server/0636-Add-EntityBlockStorage-clearEntities.patch delete mode 100644 patches/server/0637-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch create mode 100644 patches/server/0637-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch delete mode 100644 patches/server/0638-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch create mode 100644 patches/server/0638-Inventory-close.patch delete mode 100644 patches/server/0639-Inventory-close.patch create mode 100644 patches/server/0639-call-PortalCreateEvent-players-and-end-platform.patch create mode 100644 patches/server/0640-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch delete mode 100644 patches/server/0640-call-PortalCreateEvent-players-and-end-platform.patch delete mode 100644 patches/server/0641-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch create mode 100644 patches/server/0641-Fix-CraftPotionBrewer-cache.patch create mode 100644 patches/server/0642-Add-basic-Datapack-API.patch delete mode 100644 patches/server/0642-Fix-CraftPotionBrewer-cache.patch delete mode 100644 patches/server/0643-Add-basic-Datapack-API.patch create mode 100644 patches/server/0643-Add-environment-variable-to-disable-server-gui.patch delete mode 100644 patches/server/0644-Add-environment-variable-to-disable-server-gui.patch create mode 100644 patches/server/0644-additions-to-PlayerGameModeChangeEvent.patch create mode 100644 patches/server/0645-ItemStack-repair-check-API.patch delete mode 100644 patches/server/0645-additions-to-PlayerGameModeChangeEvent.patch delete mode 100644 patches/server/0646-ItemStack-repair-check-API.patch create mode 100644 patches/server/0646-More-Enchantment-API.patch delete mode 100644 patches/server/0647-More-Enchantment-API.patch create mode 100644 patches/server/0647-Move-range-check-for-block-placing-up.patch create mode 100644 patches/server/0648-Fix-and-optimise-world-force-upgrading.patch delete mode 100644 patches/server/0648-Move-range-check-for-block-placing-up.patch create mode 100644 patches/server/0649-Add-Mob-lookAt-API.patch delete mode 100644 patches/server/0649-Fix-and-optimise-world-force-upgrading.patch delete mode 100644 patches/server/0650-Add-Mob-lookAt-API.patch create mode 100644 patches/server/0650-Add-Unix-domain-socket-support.patch create mode 100644 patches/server/0651-Add-EntityInsideBlockEvent.patch delete mode 100644 patches/server/0651-Add-Unix-domain-socket-support.patch delete mode 100644 patches/server/0652-Add-EntityInsideBlockEvent.patch create mode 100644 patches/server/0652-Attributes-API-for-item-defaults.patch create mode 100644 patches/server/0653-Add-cause-to-Weather-ThunderChangeEvents.patch delete mode 100644 patches/server/0653-Attributes-API-for-item-defaults.patch delete mode 100644 patches/server/0654-Add-cause-to-Weather-ThunderChangeEvents.patch create mode 100644 patches/server/0654-More-Lidded-Block-API.patch create mode 100644 patches/server/0655-Limit-item-frame-cursors-on-maps.patch delete mode 100644 patches/server/0655-More-Lidded-Block-API.patch create mode 100644 patches/server/0656-Add-PlayerKickEvent-causes.patch delete mode 100644 patches/server/0656-Limit-item-frame-cursors-on-maps.patch delete mode 100644 patches/server/0657-Add-PlayerKickEvent-causes.patch create mode 100644 patches/server/0657-Add-PufferFishStateChangeEvent.patch delete mode 100644 patches/server/0658-Add-PufferFishStateChangeEvent.patch create mode 100644 patches/server/0658-Fix-PlayerBucketEmptyEvent-result-itemstack.patch delete mode 100644 patches/server/0659-Fix-PlayerBucketEmptyEvent-result-itemstack.patch create mode 100644 patches/server/0659-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch create mode 100644 patches/server/0660-Add-option-to-fix-items-merging-through-walls.patch delete mode 100644 patches/server/0660-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch create mode 100644 patches/server/0661-Add-BellRevealRaiderEvent.patch delete mode 100644 patches/server/0661-Add-option-to-fix-items-merging-through-walls.patch delete mode 100644 patches/server/0662-Add-BellRevealRaiderEvent.patch create mode 100644 patches/server/0662-Fix-invulnerable-end-crystals.patch create mode 100644 patches/server/0663-Add-ElderGuardianAppearanceEvent.patch delete mode 100644 patches/server/0663-Fix-invulnerable-end-crystals.patch delete mode 100644 patches/server/0664-Add-ElderGuardianAppearanceEvent.patch create mode 100644 patches/server/0664-Fix-dangerous-end-portal-logic.patch delete mode 100644 patches/server/0665-Fix-dangerous-end-portal-logic.patch create mode 100644 patches/server/0665-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch create mode 100644 patches/server/0666-Make-item-validations-configurable.patch delete mode 100644 patches/server/0666-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch create mode 100644 patches/server/0667-Line-Of-Sight-Changes.patch delete mode 100644 patches/server/0667-Make-item-validations-configurable.patch delete mode 100644 patches/server/0668-Line-Of-Sight-Changes.patch create mode 100644 patches/server/0668-add-per-world-spawn-limits.patch create mode 100644 patches/server/0669-Fix-PotionSplashEvent-for-water-splash-potions.patch delete mode 100644 patches/server/0669-add-per-world-spawn-limits.patch create mode 100644 patches/server/0670-Add-more-LimitedRegion-API.patch delete mode 100644 patches/server/0670-Fix-PotionSplashEvent-for-water-splash-potions.patch delete mode 100644 patches/server/0671-Add-more-LimitedRegion-API.patch create mode 100644 patches/server/0671-Fix-PlayerDropItemEvent-using-wrong-item.patch delete mode 100644 patches/server/0672-Fix-PlayerDropItemEvent-using-wrong-item.patch create mode 100644 patches/server/0672-Missing-Entity-Behavior-API.patch create mode 100644 patches/server/0673-Ensure-disconnect-for-book-edit-is-called-on-main.patch delete mode 100644 patches/server/0673-Missing-Entity-Behavior-API.patch delete mode 100644 patches/server/0674-Ensure-disconnect-for-book-edit-is-called-on-main.patch create mode 100644 patches/server/0674-Fix-return-value-of-Block-applyBoneMeal-always-being.patch delete mode 100644 patches/server/0675-Fix-return-value-of-Block-applyBoneMeal-always-being.patch create mode 100644 patches/server/0675-Use-getChunkIfLoadedImmediately-in-places.patch create mode 100644 patches/server/0676-Fix-commands-from-signs-not-firing-command-events.patch delete mode 100644 patches/server/0676-Use-getChunkIfLoadedImmediately-in-places.patch create mode 100644 patches/server/0677-Adds-PlayerArmSwingEvent.patch delete mode 100644 patches/server/0677-Fix-commands-from-signs-not-firing-command-events.patch delete mode 100644 patches/server/0678-Adds-PlayerArmSwingEvent.patch create mode 100644 patches/server/0678-Fixes-kick-event-leave-message-not-being-sent.patch create mode 100644 patches/server/0679-Add-config-for-mobs-immune-to-default-effects.patch delete mode 100644 patches/server/0679-Fixes-kick-event-leave-message-not-being-sent.patch delete mode 100644 patches/server/0680-Add-config-for-mobs-immune-to-default-effects.patch create mode 100644 patches/server/0680-Fix-incorrect-message-for-outdated-client.patch create mode 100644 patches/server/0681-Don-t-apply-cramming-damage-to-players.patch delete mode 100644 patches/server/0681-Fix-incorrect-message-for-outdated-client.patch delete mode 100644 patches/server/0682-Don-t-apply-cramming-damage-to-players.patch create mode 100644 patches/server/0682-Rate-options-and-timings-for-sensors-and-behaviors.patch create mode 100644 patches/server/0683-Add-a-bunch-of-missing-forceDrop-toggles.patch delete mode 100644 patches/server/0683-Rate-options-and-timings-for-sensors-and-behaviors.patch delete mode 100644 patches/server/0684-Add-a-bunch-of-missing-forceDrop-toggles.patch create mode 100644 patches/server/0684-Stinger-API.patch create mode 100644 patches/server/0685-Fix-incosistency-issue-with-empty-map-items-in-CB.patch delete mode 100644 patches/server/0685-Stinger-API.patch create mode 100644 patches/server/0686-Add-System.out-err-catcher.patch delete mode 100644 patches/server/0686-Fix-incosistency-issue-with-empty-map-items-in-CB.patch delete mode 100644 patches/server/0687-Add-System.out-err-catcher.patch create mode 100644 patches/server/0687-Fix-test-not-bootstrapping.patch delete mode 100644 patches/server/0688-Fix-test-not-bootstrapping.patch create mode 100644 patches/server/0688-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch create mode 100644 patches/server/0689-Improve-boat-collision-performance.patch delete mode 100644 patches/server/0689-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch delete mode 100644 patches/server/0690-Improve-boat-collision-performance.patch create mode 100644 patches/server/0690-Prevent-AFK-kick-while-watching-end-credits.patch create mode 100644 patches/server/0691-Allow-skipping-writing-of-comments-to-server.propert.patch delete mode 100644 patches/server/0691-Prevent-AFK-kick-while-watching-end-credits.patch create mode 100644 patches/server/0692-Add-PlayerSetSpawnEvent.patch delete mode 100644 patches/server/0692-Allow-skipping-writing-of-comments-to-server.propert.patch delete mode 100644 patches/server/0693-Add-PlayerSetSpawnEvent.patch create mode 100644 patches/server/0693-Make-hoppers-respect-inventory-max-stack-size.patch delete mode 100644 patches/server/0694-Make-hoppers-respect-inventory-max-stack-size.patch create mode 100644 patches/server/0694-Optimize-entity-tracker-passenger-checks.patch create mode 100644 patches/server/0695-Config-option-for-Piglins-guarding-chests.patch delete mode 100644 patches/server/0695-Optimize-entity-tracker-passenger-checks.patch create mode 100644 patches/server/0696-Added-EntityDamageItemEvent.patch delete mode 100644 patches/server/0696-Config-option-for-Piglins-guarding-chests.patch delete mode 100644 patches/server/0697-Added-EntityDamageItemEvent.patch create mode 100644 patches/server/0697-Optimize-indirect-passenger-iteration.patch create mode 100644 patches/server/0698-Fix-block-drops-position-losing-precision-millions-o.patch delete mode 100644 patches/server/0698-Optimize-indirect-passenger-iteration.patch create mode 100644 patches/server/0699-Configurable-item-frame-map-cursor-update-interval.patch delete mode 100644 patches/server/0699-Fix-block-drops-position-losing-precision-millions-o.patch delete mode 100644 patches/server/0700-Configurable-item-frame-map-cursor-update-interval.patch create mode 100644 patches/server/0700-Make-EntityUnleashEvent-cancellable.patch create mode 100644 patches/server/0701-Clear-bucket-NBT-after-dispense.patch delete mode 100644 patches/server/0701-Make-EntityUnleashEvent-cancellable.patch create mode 100644 patches/server/0702-Change-EnderEye-target-without-changing-other-things.patch delete mode 100644 patches/server/0702-Clear-bucket-NBT-after-dispense.patch create mode 100644 patches/server/0703-Add-BlockBreakBlockEvent.patch delete mode 100644 patches/server/0703-Change-EnderEye-target-without-changing-other-things.patch delete mode 100644 patches/server/0704-Add-BlockBreakBlockEvent.patch create mode 100644 patches/server/0704-Option-to-prevent-NBT-copy-in-smithing-recipes.patch create mode 100644 patches/server/0705-More-CommandBlock-API.patch delete mode 100644 patches/server/0705-Option-to-prevent-NBT-copy-in-smithing-recipes.patch create mode 100644 patches/server/0706-Add-missing-team-sidebar-display-slots.patch delete mode 100644 patches/server/0706-More-CommandBlock-API.patch create mode 100644 patches/server/0707-Add-back-EntityPortalExitEvent.patch delete mode 100644 patches/server/0707-Add-missing-team-sidebar-display-slots.patch delete mode 100644 patches/server/0708-Add-back-EntityPortalExitEvent.patch create mode 100644 patches/server/0708-Add-methods-to-find-targets-for-lightning-strikes.patch delete mode 100644 patches/server/0709-Add-methods-to-find-targets-for-lightning-strikes.patch create mode 100644 patches/server/0709-Get-entity-default-attributes.patch delete mode 100644 patches/server/0710-Get-entity-default-attributes.patch create mode 100644 patches/server/0710-Left-handed-API.patch create mode 100644 patches/server/0711-Add-advancement-display-API.patch delete mode 100644 patches/server/0711-Left-handed-API.patch create mode 100644 patches/server/0712-Add-ItemFactory-getMonsterEgg-API.patch delete mode 100644 patches/server/0712-Add-advancement-display-API.patch delete mode 100644 patches/server/0713-Add-ItemFactory-getMonsterEgg-API.patch create mode 100644 patches/server/0713-Add-critical-damage-API.patch delete mode 100644 patches/server/0714-Add-critical-damage-API.patch create mode 100644 patches/server/0714-Fix-issues-with-mob-conversion.patch create mode 100644 patches/server/0715-Add-isCollidable-methods-to-various-places.patch delete mode 100644 patches/server/0715-Fix-issues-with-mob-conversion.patch delete mode 100644 patches/server/0716-Add-isCollidable-methods-to-various-places.patch create mode 100644 patches/server/0716-Goat-ram-API.patch create mode 100644 patches/server/0717-Add-API-for-resetting-a-single-score.patch delete mode 100644 patches/server/0717-Goat-ram-API.patch delete mode 100644 patches/server/0718-Add-API-for-resetting-a-single-score.patch create mode 100644 patches/server/0718-Add-Raw-Byte-Entity-Serialization.patch delete mode 100644 patches/server/0719-Add-Raw-Byte-Entity-Serialization.patch create mode 100644 patches/server/0719-Vanilla-command-permission-fixes.patch create mode 100644 patches/server/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch delete mode 100644 patches/server/0720-Vanilla-command-permission-fixes.patch delete mode 100644 patches/server/0721-Do-not-allow-the-server-to-unload-chunks-at-request-.patch create mode 100644 patches/server/0721-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch create mode 100644 patches/server/0722-Correctly-handle-recursion-for-chunkholder-updates.patch delete mode 100644 patches/server/0722-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch delete mode 100644 patches/server/0723-Correctly-handle-recursion-for-chunkholder-updates.patch create mode 100644 patches/server/0723-Fix-GameProfileCache-concurrency.patch delete mode 100644 patches/server/0724-Fix-GameProfileCache-concurrency.patch create mode 100644 patches/server/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch create mode 100644 patches/server/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch delete mode 100644 patches/server/0725-Fix-chunks-refusing-to-unload-at-low-TPS.patch create mode 100644 patches/server/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch delete mode 100644 patches/server/0726-Do-not-allow-ticket-level-changes-while-unloading-pl.patch delete mode 100644 patches/server/0727-Do-not-allow-ticket-level-changes-when-updating-chun.patch create mode 100644 patches/server/0727-Log-when-the-async-catcher-is-tripped.patch create mode 100644 patches/server/0728-Add-paper-mobcaps-and-paper-playermobcaps.patch delete mode 100644 patches/server/0728-Log-when-the-async-catcher-is-tripped.patch delete mode 100644 patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch create mode 100644 patches/server/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch delete mode 100644 patches/server/0730-Prevent-unload-calls-removing-tickets-for-sync-loads.patch create mode 100644 patches/server/0730-Sanitize-ResourceLocation-error-logging.patch create mode 100644 patches/server/0731-Allow-controlled-flushing-for-network-manager.patch delete mode 100644 patches/server/0731-Sanitize-ResourceLocation-error-logging.patch delete mode 100644 patches/server/0732-Allow-controlled-flushing-for-network-manager.patch create mode 100644 patches/server/0732-Optimise-general-POI-access.patch create mode 100644 patches/server/0733-Add-more-async-catchers.patch delete mode 100644 patches/server/0733-Optimise-general-POI-access.patch delete mode 100644 patches/server/0734-Add-more-async-catchers.patch create mode 100644 patches/server/0734-Rewrite-entity-bounding-box-lookup-calls.patch create mode 100644 patches/server/0735-Optimise-chunk-tick-iteration.patch delete mode 100644 patches/server/0735-Rewrite-entity-bounding-box-lookup-calls.patch create mode 100644 patches/server/0736-Execute-chunk-tasks-mid-tick.patch delete mode 100644 patches/server/0736-Optimise-chunk-tick-iteration.patch create mode 100644 patches/server/0737-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch delete mode 100644 patches/server/0737-Execute-chunk-tasks-mid-tick.patch delete mode 100644 patches/server/0738-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch create mode 100644 patches/server/0738-Custom-table-implementation-for-blockstate-state-loo.patch delete mode 100644 patches/server/0739-Custom-table-implementation-for-blockstate-state-loo.patch create mode 100644 patches/server/0739-Detail-more-information-in-watchdog-dumps.patch delete mode 100644 patches/server/0740-Detail-more-information-in-watchdog-dumps.patch create mode 100644 patches/server/0740-Manually-inline-methods-in-BlockPosition.patch create mode 100644 patches/server/0741-Distance-manager-tick-timings.patch delete mode 100644 patches/server/0741-Manually-inline-methods-in-BlockPosition.patch delete mode 100644 patches/server/0742-Distance-manager-tick-timings.patch create mode 100644 patches/server/0742-Name-craft-scheduler-threads-according-to-the-plugin.patch create mode 100644 patches/server/0743-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch delete mode 100644 patches/server/0743-Name-craft-scheduler-threads-according-to-the-plugin.patch create mode 100644 patches/server/0744-Add-packet-limiter-config.patch delete mode 100644 patches/server/0744-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch delete mode 100644 patches/server/0745-Add-packet-limiter-config.patch create mode 100644 patches/server/0745-Use-correct-LevelStem-registry-when-loading-default-.patch create mode 100644 patches/server/0746-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch delete mode 100644 patches/server/0746-Use-correct-LevelStem-registry-when-loading-default-.patch create mode 100644 patches/server/0747-Consolidate-flush-calls-for-entity-tracker-packets.patch delete mode 100644 patches/server/0747-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch delete mode 100644 patches/server/0748-Consolidate-flush-calls-for-entity-tracker-packets.patch create mode 100644 patches/server/0748-Don-t-lookup-fluid-state-when-raytracing.patch delete mode 100644 patches/server/0749-Don-t-lookup-fluid-state-when-raytracing.patch create mode 100644 patches/server/0749-Time-scoreboard-search.patch create mode 100644 patches/server/0750-Send-full-pos-packets-for-hard-colliding-entities.patch delete mode 100644 patches/server/0750-Time-scoreboard-search.patch create mode 100644 patches/server/0751-Do-not-run-raytrace-logic-for-AIR.patch delete mode 100644 patches/server/0751-Send-full-pos-packets-for-hard-colliding-entities.patch delete mode 100644 patches/server/0752-Do-not-run-raytrace-logic-for-AIR.patch create mode 100644 patches/server/0752-Oprimise-map-impl-for-tracked-players.patch delete mode 100644 patches/server/0753-Oprimise-map-impl-for-tracked-players.patch create mode 100644 patches/server/0753-Optimise-BlockSoil-nearby-water-lookup.patch create mode 100644 patches/server/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch delete mode 100644 patches/server/0754-Optimise-BlockSoil-nearby-water-lookup.patch delete mode 100644 patches/server/0755-Allow-removal-addition-of-entities-to-entity-ticklis.patch create mode 100644 patches/server/0755-Optimise-random-block-ticking.patch create mode 100644 patches/server/0756-Optimise-non-flush-packet-sending.patch delete mode 100644 patches/server/0756-Optimise-random-block-ticking.patch create mode 100644 patches/server/0757-Optimise-nearby-player-lookups.patch delete mode 100644 patches/server/0757-Optimise-non-flush-packet-sending.patch create mode 100644 patches/server/0758-Optimise-WorldServer-notify.patch delete mode 100644 patches/server/0758-Optimise-nearby-player-lookups.patch delete mode 100644 patches/server/0759-Optimise-WorldServer-notify.patch create mode 100644 patches/server/0759-Remove-streams-for-villager-AI.patch delete mode 100644 patches/server/0760-Remove-streams-for-villager-AI.patch create mode 100644 patches/server/0760-Rewrite-dataconverter-system.patch delete mode 100644 patches/server/0761-Rewrite-dataconverter-system.patch create mode 100644 patches/server/0761-Use-Velocity-compression-and-cipher-natives.patch create mode 100644 patches/server/0762-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch delete mode 100644 patches/server/0762-Use-Velocity-compression-and-cipher-natives.patch create mode 100644 patches/server/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch delete mode 100644 patches/server/0763-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch create mode 100644 patches/server/0764-Async-catch-modifications-to-critical-entity-state.patch delete mode 100644 patches/server/0764-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch delete mode 100644 patches/server/0765-Async-catch-modifications-to-critical-entity-state.patch create mode 100644 patches/server/0765-Fix-Bukkit-NamespacedKey-shenanigans.patch delete mode 100644 patches/server/0766-Fix-Bukkit-NamespacedKey-shenanigans.patch create mode 100644 patches/server/0766-Fix-merchant-inventory-not-closing-on-entity-removal.patch create mode 100644 patches/server/0767-Check-requirement-before-suggesting-root-nodes.patch delete mode 100644 patches/server/0767-Fix-merchant-inventory-not-closing-on-entity-removal.patch delete mode 100644 patches/server/0768-Check-requirement-before-suggesting-root-nodes.patch create mode 100644 patches/server/0768-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch delete mode 100644 patches/server/0769-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch create mode 100644 patches/server/0769-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch create mode 100644 patches/server/0770-Ensure-valid-vehicle-status.patch delete mode 100644 patches/server/0770-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch delete mode 100644 patches/server/0771-Ensure-valid-vehicle-status.patch create mode 100644 patches/server/0771-Prevent-softlocked-end-exit-portal-generation.patch create mode 100644 patches/server/0772-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch delete mode 100644 patches/server/0772-Prevent-softlocked-end-exit-portal-generation.patch create mode 100644 patches/server/0773-Don-t-log-debug-logging-being-disabled.patch delete mode 100644 patches/server/0773-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch delete mode 100644 patches/server/0774-Don-t-log-debug-logging-being-disabled.patch create mode 100644 patches/server/0774-fix-various-menus-with-empty-level-accesses.patch create mode 100644 patches/server/0775-Preserve-overstacked-loot.patch delete mode 100644 patches/server/0775-fix-various-menus-with-empty-level-accesses.patch delete mode 100644 patches/server/0776-Preserve-overstacked-loot.patch create mode 100644 patches/server/0776-Update-head-rotation-in-missing-places.patch delete mode 100644 patches/server/0777-Update-head-rotation-in-missing-places.patch create mode 100644 patches/server/0777-prevent-unintended-light-block-manipulation.patch create mode 100644 patches/server/0778-Fix-CraftCriteria-defaults-map.patch delete mode 100644 patches/server/0778-prevent-unintended-light-block-manipulation.patch delete mode 100644 patches/server/0779-Fix-CraftCriteria-defaults-map.patch create mode 100644 patches/server/0779-Fix-upstreams-block-state-factories.patch create mode 100644 patches/server/0780-Add-config-option-for-logging-player-ip-addresses.patch delete mode 100644 patches/server/0780-Fix-upstreams-block-state-factories.patch delete mode 100644 patches/server/0781-Add-config-option-for-logging-player-ip-addresses.patch create mode 100644 patches/server/0781-Configurable-feature-seeds.patch delete mode 100644 patches/server/0782-Configurable-feature-seeds.patch create mode 100644 patches/server/0782-VanillaCommandWrapper-didnt-account-for-entity-sende.patch create mode 100644 patches/server/0783-Add-root-admin-user-detection.patch delete mode 100644 patches/server/0783-VanillaCommandWrapper-didnt-account-for-entity-sende.patch delete mode 100644 patches/server/0784-Add-root-admin-user-detection.patch create mode 100644 patches/server/0784-Always-allow-item-changing-in-Fireball.patch delete mode 100644 patches/server/0785-Always-allow-item-changing-in-Fireball.patch create mode 100644 patches/server/0785-don-t-attempt-to-teleport-dead-entities.patch create mode 100644 patches/server/0786-Prevent-excessive-velocity-through-repeated-crits.patch delete mode 100644 patches/server/0786-don-t-attempt-to-teleport-dead-entities.patch delete mode 100644 patches/server/0787-Prevent-excessive-velocity-through-repeated-crits.patch create mode 100644 patches/server/0787-Remove-client-side-code-using-deprecated-for-removal.patch delete mode 100644 patches/server/0788-Remove-client-side-code-using-deprecated-for-removal.patch create mode 100644 patches/server/0788-Rewrite-the-light-engine.patch create mode 100644 patches/server/0789-Always-parse-protochunk-light-sources-unless-it-is-m.patch delete mode 100644 patches/server/0789-Rewrite-the-light-engine.patch delete mode 100644 patches/server/0790-Always-parse-protochunk-light-sources-unless-it-is-m.patch create mode 100644 patches/server/0790-Fix-removing-recipes-from-RecipeIterator.patch delete mode 100644 patches/server/0791-Fix-removing-recipes-from-RecipeIterator.patch create mode 100644 patches/server/0791-Prevent-sending-oversized-item-data-in-equipment-and.patch create mode 100644 patches/server/0792-Hide-unnecessary-itemmeta-from-clients.patch delete mode 100644 patches/server/0792-Prevent-sending-oversized-item-data-in-equipment-and.patch create mode 100644 patches/server/0793-Fix-kelp-modifier-changing-growth-for-other-crops.patch delete mode 100644 patches/server/0793-Hide-unnecessary-itemmeta-from-clients.patch delete mode 100644 patches/server/0794-Fix-kelp-modifier-changing-growth-for-other-crops.patch create mode 100644 patches/server/0794-Prevent-ContainerOpenersCounter-openCount-from-going.patch create mode 100644 patches/server/0795-Add-PlayerItemFrameChangeEvent.patch delete mode 100644 patches/server/0795-Prevent-ContainerOpenersCounter-openCount-from-going.patch delete mode 100644 patches/server/0796-Add-PlayerItemFrameChangeEvent.patch create mode 100644 patches/server/0796-Add-player-health-update-API.patch delete mode 100644 patches/server/0797-Add-player-health-update-API.patch create mode 100644 patches/server/0797-Optimize-HashMapPalette.patch create mode 100644 patches/server/0798-Allow-delegation-to-vanilla-chunk-gen.patch delete mode 100644 patches/server/0798-Optimize-HashMapPalette.patch delete mode 100644 patches/server/0799-Allow-delegation-to-vanilla-chunk-gen.patch create mode 100644 patches/server/0799-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch delete mode 100644 patches/server/0800-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch create mode 100644 patches/server/0800-Optimise-collision-checking-in-player-move-packet-ha.patch create mode 100644 patches/server/0801-Actually-unload-POI-data.patch delete mode 100644 patches/server/0801-Optimise-collision-checking-in-player-move-packet-ha.patch delete mode 100644 patches/server/0802-Actually-unload-POI-data.patch create mode 100644 patches/server/0802-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch delete mode 100644 patches/server/0803-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch create mode 100644 patches/server/0803-Update-Log4j.patch create mode 100644 patches/server/0804-Add-more-Campfire-API.patch delete mode 100644 patches/server/0804-Update-Log4j.patch delete mode 100644 patches/server/0805-Add-more-Campfire-API.patch create mode 100644 patches/server/0805-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch create mode 100644 patches/server/0806-Fix-tripwire-state-inconsistency.patch delete mode 100644 patches/server/0806-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch create mode 100644 patches/server/0807-Fix-fluid-logging-on-Block-breakNaturally.patch delete mode 100644 patches/server/0807-Fix-tripwire-state-inconsistency.patch delete mode 100644 patches/server/0808-Fix-fluid-logging-on-Block-breakNaturally.patch create mode 100644 patches/server/0808-Forward-CraftEntity-in-teleport-command.patch delete mode 100644 patches/server/0809-Forward-CraftEntity-in-teleport-command.patch create mode 100644 patches/server/0809-Improve-scoreboard-entries.patch create mode 100644 patches/server/0810-Entity-powdered-snow-API.patch delete mode 100644 patches/server/0810-Improve-scoreboard-entries.patch create mode 100644 patches/server/0811-Add-API-for-item-entity-health.patch delete mode 100644 patches/server/0811-Entity-powdered-snow-API.patch delete mode 100644 patches/server/0812-Add-API-for-item-entity-health.patch create mode 100644 patches/server/0812-Fix-entity-type-tags-suggestions-in-selectors.patch create mode 100644 patches/server/0813-Configurable-max-block-light-for-monster-spawning.patch delete mode 100644 patches/server/0813-Fix-entity-type-tags-suggestions-in-selectors.patch delete mode 100644 patches/server/0814-Configurable-max-block-light-for-monster-spawning.patch create mode 100644 patches/server/0814-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch delete mode 100644 patches/server/0815-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch create mode 100644 patches/server/0815-Load-effect-amplifiers-greater-than-127-correctly.patch create mode 100644 patches/server/0816-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch delete mode 100644 patches/server/0816-Load-effect-amplifiers-greater-than-127-correctly.patch delete mode 100644 patches/server/0817-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch create mode 100644 patches/server/0817-Fix-bees-aging-inside-hives.patch create mode 100644 patches/server/0818-Bucketable-API.patch delete mode 100644 patches/server/0818-Fix-bees-aging-inside-hives.patch delete mode 100644 patches/server/0819-Bucketable-API.patch create mode 100644 patches/server/0819-Check-player-world-in-endPortalSoundRadius.patch delete mode 100644 patches/server/0820-Check-player-world-in-endPortalSoundRadius.patch create mode 100644 patches/server/0820-Validate-usernames.patch create mode 100644 patches/server/0821-Fix-saving-configs-with-more-long-comments.patch delete mode 100644 patches/server/0821-Validate-usernames.patch delete mode 100644 patches/server/0822-Fix-saving-configs-with-more-long-comments.patch create mode 100644 patches/server/0822-Make-water-animal-spawn-height-configurable.patch create mode 100644 patches/server/0823-Expose-vanilla-BiomeProvider-from-WorldInfo.patch delete mode 100644 patches/server/0823-Make-water-animal-spawn-height-configurable.patch create mode 100644 patches/server/0824-Add-config-option-for-worlds-affected-by-time-cmd.patch delete mode 100644 patches/server/0824-Expose-vanilla-BiomeProvider-from-WorldInfo.patch delete mode 100644 patches/server/0825-Add-config-option-for-worlds-affected-by-time-cmd.patch create mode 100644 patches/server/0825-Add-new-overload-to-PersistentDataContainer-has.patch delete mode 100644 patches/server/0826-Add-new-overload-to-PersistentDataContainer-has.patch create mode 100644 patches/server/0826-Multiple-Entries-with-Scoreboards.patch delete mode 100644 patches/server/0827-Multiple-Entries-with-Scoreboards.patch create mode 100644 patches/server/0827-Reset-placed-block-on-exception.patch create mode 100644 patches/server/0828-Add-configurable-height-for-slime-spawn.patch delete mode 100644 patches/server/0828-Reset-placed-block-on-exception.patch delete mode 100644 patches/server/0829-Add-configurable-height-for-slime-spawn.patch create mode 100644 patches/server/0829-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch delete mode 100644 patches/server/0830-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch create mode 100644 patches/server/0830-Fix-xp-reward-for-baby-zombies.patch delete mode 100644 patches/server/0831-Fix-xp-reward-for-baby-zombies.patch create mode 100644 patches/server/0831-Kick-on-main-for-illegal-chat.patch delete mode 100644 patches/server/0832-Kick-on-main-for-illegal-chat.patch create mode 100644 patches/server/0832-Multi-Block-Change-API-Implementation.patch create mode 100644 patches/server/0833-Fix-NotePlayEvent.patch delete mode 100644 patches/server/0833-Multi-Block-Change-API-Implementation.patch delete mode 100644 patches/server/0834-Fix-NotePlayEvent.patch create mode 100644 patches/server/0834-Freeze-Tick-Lock-API.patch create mode 100644 patches/server/0835-Dolphin-API.patch delete mode 100644 patches/server/0835-Freeze-Tick-Lock-API.patch delete mode 100644 patches/server/0836-Dolphin-API.patch create mode 100644 patches/server/0836-More-PotionEffectType-API.patch delete mode 100644 patches/server/0837-More-PotionEffectType-API.patch create mode 100644 patches/server/0837-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch create mode 100644 patches/server/0838-API-for-creating-command-sender-which-forwards-feedb.patch delete mode 100644 patches/server/0838-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch delete mode 100644 patches/server/0839-API-for-creating-command-sender-which-forwards-feedb.patch create mode 100644 patches/server/0839-Add-config-for-stronghold-seed.patch delete mode 100644 patches/server/0840-Add-config-for-stronghold-seed.patch create mode 100644 patches/server/0840-Implement-regenerateChunk.patch create mode 100644 patches/server/0841-Fix-cancelled-powdered-snow-bucket-placement.patch delete mode 100644 patches/server/0841-Implement-regenerateChunk.patch create mode 100644 patches/server/0842-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch delete mode 100644 patches/server/0842-Fix-cancelled-powdered-snow-bucket-placement.patch create mode 100644 patches/server/0843-Add-GameEvent-tags.patch delete mode 100644 patches/server/0843-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch delete mode 100644 patches/server/0844-Add-GameEvent-tags.patch create mode 100644 patches/server/0844-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch delete mode 100644 patches/server/0845-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch create mode 100644 patches/server/0845-Replace-ticket-level-propagator.patch create mode 100644 patches/server/0846-Furnace-RecipesUsed-API.patch delete mode 100644 patches/server/0846-Replace-ticket-level-propagator.patch create mode 100644 patches/server/0847-Configurable-sculk-sensor-listener-range.patch delete mode 100644 patches/server/0847-Furnace-RecipesUsed-API.patch create mode 100644 patches/server/0848-Add-missing-block-data-mins-and-maxes.patch delete mode 100644 patches/server/0848-Configurable-sculk-sensor-listener-range.patch delete mode 100644 patches/server/0849-Add-missing-block-data-mins-and-maxes.patch create mode 100644 patches/server/0849-Option-to-have-default-CustomSpawners-in-custom-worl.patch delete mode 100644 patches/server/0850-Option-to-have-default-CustomSpawners-in-custom-worl.patch create mode 100644 patches/server/0850-Put-world-into-worldlist-before-initing-the-world.patch create mode 100644 patches/server/0851-Fix-Entity-Position-Desync.patch delete mode 100644 patches/server/0851-Put-world-into-worldlist-before-initing-the-world.patch create mode 100644 patches/server/0852-Custom-Potion-Mixes.patch delete mode 100644 patches/server/0852-Fix-Entity-Position-Desync.patch delete mode 100644 patches/server/0853-Custom-Potion-Mixes.patch create mode 100644 patches/server/0853-Replace-player-chunk-loader-system.patch create mode 100644 patches/server/0854-Fix-Fluid-tags-isTagged-method.patch delete mode 100644 patches/server/0854-Replace-player-chunk-loader-system.patch delete mode 100644 patches/server/0855-Fix-Fluid-tags-isTagged-method.patch create mode 100644 patches/server/0855-Force-close-world-loading-screen.patch create mode 100644 patches/server/0856-Fix-falling-block-spawn-methods.patch delete mode 100644 patches/server/0856-Force-close-world-loading-screen.patch create mode 100644 patches/server/0857-Expose-furnace-minecart-push-values.patch delete mode 100644 patches/server/0857-Fix-falling-block-spawn-methods.patch delete mode 100644 patches/server/0858-Expose-furnace-minecart-push-values.patch create mode 100644 patches/server/0858-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch delete mode 100644 patches/server/0859-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch create mode 100644 patches/server/0859-Fix-save-problems-on-shutdown.patch delete mode 100644 patches/server/0860-Fix-save-problems-on-shutdown.patch create mode 100644 patches/server/0860-More-Projectile-API.patch create mode 100644 patches/server/0861-Fix-swamp-hut-cat-generation-deadlock.patch delete mode 100644 patches/server/0861-More-Projectile-API.patch create mode 100644 patches/server/0862-Don-t-allow-vehicle-movement-from-players-while-tele.patch delete mode 100644 patches/server/0862-Fix-swamp-hut-cat-generation-deadlock.patch delete mode 100644 patches/server/0863-Don-t-allow-vehicle-movement-from-players-while-tele.patch create mode 100644 patches/server/0863-Implement-getComputedBiome-API.patch delete mode 100644 patches/server/0864-Implement-getComputedBiome-API.patch create mode 100644 patches/server/0864-Make-some-itemstacks-nonnull.patch create mode 100644 patches/server/0865-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch delete mode 100644 patches/server/0865-Make-some-itemstacks-nonnull.patch delete mode 100644 patches/server/0866-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch create mode 100644 patches/server/0866-Implement-enchantWithLevels-API.patch create mode 100644 patches/server/0867-Fix-saving-in-unloadWorld.patch delete mode 100644 patches/server/0867-Implement-enchantWithLevels-API.patch create mode 100644 patches/server/0868-Buffer-OOB-setBlock-calls.patch delete mode 100644 patches/server/0868-Fix-saving-in-unloadWorld.patch create mode 100644 patches/server/0869-Add-TameableDeathMessageEvent.patch delete mode 100644 patches/server/0869-Buffer-OOB-setBlock-calls.patch delete mode 100644 patches/server/0870-Add-TameableDeathMessageEvent.patch create mode 100644 patches/server/0870-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch delete mode 100644 patches/server/0871-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch create mode 100644 patches/server/0871-fix-player-loottables-running-when-mob-loot-gamerule.patch create mode 100644 patches/server/0872-Ensure-entity-passenger-world-matches-ridden-entity.patch delete mode 100644 patches/server/0872-fix-player-loottables-running-when-mob-loot-gamerule.patch delete mode 100644 patches/server/0873-Ensure-entity-passenger-world-matches-ridden-entity.patch create mode 100644 patches/server/0873-Guard-against-invalid-entity-positions.patch delete mode 100644 patches/server/0874-Guard-against-invalid-entity-positions.patch create mode 100644 patches/server/0874-cache-resource-keys.patch create mode 100644 patches/server/0875-Allow-to-change-the-podium-for-the-EnderDragon.patch delete mode 100644 patches/server/0875-cache-resource-keys.patch delete mode 100644 patches/server/0876-Allow-to-change-the-podium-for-the-EnderDragon.patch create mode 100644 patches/server/0876-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch delete mode 100644 patches/server/0877-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch create mode 100644 patches/server/0877-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch delete mode 100644 patches/server/0878-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch create mode 100644 patches/server/0878-Prevent-tile-entity-copies-loading-chunks.patch delete mode 100644 patches/server/0879-Prevent-tile-entity-copies-loading-chunks.patch create mode 100644 patches/server/0879-Use-username-instead-of-display-name-in-PlayerList-g.patch create mode 100644 patches/server/0880-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch delete mode 100644 patches/server/0880-Use-username-instead-of-display-name-in-PlayerList-g.patch delete mode 100644 patches/server/0881-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch create mode 100644 patches/server/0881-Pass-ServerLevel-for-gamerule-callbacks.patch create mode 100644 patches/server/0882-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch delete mode 100644 patches/server/0882-Pass-ServerLevel-for-gamerule-callbacks.patch delete mode 100644 patches/server/0883-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch create mode 100644 patches/server/0883-WorldCreator-keepSpawnLoaded.patch create mode 100644 patches/server/0884-Fix-NPE-for-BlockDataMeta-getBlockData.patch delete mode 100644 patches/server/0884-WorldCreator-keepSpawnLoaded.patch delete mode 100644 patches/server/0885-Fix-NPE-for-BlockDataMeta-getBlockData.patch create mode 100644 patches/server/0885-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch create mode 100644 patches/server/0886-Add-EntityDyeEvent-and-CollarColorable-interface.patch delete mode 100644 patches/server/0886-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch delete mode 100644 patches/server/0887-Add-EntityDyeEvent-and-CollarColorable-interface.patch create mode 100644 patches/server/0887-Fire-CauldronLevelChange-on-initial-fill.patch delete mode 100644 patches/server/0888-Fire-CauldronLevelChange-on-initial-fill.patch create mode 100644 patches/server/0888-fix-powder-snow-cauldrons-not-turning-to-water.patch create mode 100644 patches/server/0889-Add-PlayerStopUsingItemEvent.patch delete mode 100644 patches/server/0889-fix-powder-snow-cauldrons-not-turning-to-water.patch delete mode 100644 patches/server/0890-Add-PlayerStopUsingItemEvent.patch create mode 100644 patches/server/0890-FallingBlock-auto-expire-setting.patch create mode 100644 patches/server/0891-Don-t-tick-markers.patch delete mode 100644 patches/server/0891-FallingBlock-auto-expire-setting.patch create mode 100644 patches/server/0892-Do-not-accept-invalid-client-settings.patch delete mode 100644 patches/server/0892-Don-t-tick-markers.patch create mode 100644 patches/server/0893-Add-support-for-Proxy-Protocol.patch delete mode 100644 patches/server/0893-Do-not-accept-invalid-client-settings.patch delete mode 100644 patches/server/0894-Add-support-for-Proxy-Protocol.patch create mode 100644 patches/server/0894-Fix-OfflinePlayer-getBedSpawnLocation.patch create mode 100644 patches/server/0895-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch delete mode 100644 patches/server/0895-Fix-OfflinePlayer-getBedSpawnLocation.patch delete mode 100644 patches/server/0896-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch create mode 100644 patches/server/0896-Sanitize-Sent-BlockEntity-NBT.patch create mode 100644 patches/server/0897-Prevent-entity-loading-causing-async-lookups.patch delete mode 100644 patches/server/0897-Sanitize-Sent-BlockEntity-NBT.patch create mode 100644 patches/server/0898-Disable-component-selector-resolving-in-books-by-def.patch delete mode 100644 patches/server/0898-Prevent-entity-loading-causing-async-lookups.patch delete mode 100644 patches/server/0899-Disable-component-selector-resolving-in-books-by-def.patch create mode 100644 patches/server/0899-Throw-exception-on-world-create-while-being-ticked.patch create mode 100644 patches/server/0900-Add-Alternate-Current-redstone-implementation.patch delete mode 100644 patches/server/0900-Throw-exception-on-world-create-while-being-ticked.patch delete mode 100644 patches/server/0901-Add-Alternate-Current-redstone-implementation.patch create mode 100644 patches/server/0901-Dont-resent-entity-on-art-update.patch create mode 100644 patches/server/0902-Add-missing-spawn-eggs.patch delete mode 100644 patches/server/0902-Dont-resent-entity-on-art-update.patch create mode 100644 patches/server/0903-Add-WardenAngerChangeEvent.patch delete mode 100644 patches/server/0903-Add-missing-spawn-eggs.patch delete mode 100644 patches/server/0904-Add-WardenAngerChangeEvent.patch create mode 100644 patches/server/0904-Add-option-for-strict-advancement-dimension-checks.patch create mode 100644 patches/server/0905-Add-missing-important-BlockStateListPopulator-method.patch delete mode 100644 patches/server/0905-Add-option-for-strict-advancement-dimension-checks.patch delete mode 100644 patches/server/0906-Add-missing-important-BlockStateListPopulator-method.patch create mode 100644 patches/server/0906-Nameable-Banner-API.patch create mode 100644 patches/server/0907-Don-t-broadcast-messages-to-command-blocks.patch delete mode 100644 patches/server/0907-Nameable-Banner-API.patch delete mode 100644 patches/server/0908-Don-t-broadcast-messages-to-command-blocks.patch create mode 100644 patches/server/0908-Prevent-empty-items-from-being-added-to-world.patch create mode 100644 patches/server/0909-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch delete mode 100644 patches/server/0909-Prevent-empty-items-from-being-added-to-world.patch create mode 100644 patches/server/0910-Don-t-print-component-in-resource-pack-rejection-mes.patch delete mode 100644 patches/server/0910-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch create mode 100644 patches/server/0911-Add-Player-getFishHook.patch delete mode 100644 patches/server/0911-Don-t-print-component-in-resource-pack-rejection-mes.patch delete mode 100644 patches/server/0912-Add-Player-getFishHook.patch create mode 100644 patches/server/0912-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch create mode 100644 patches/server/0913-Add-various-missing-EntityDropItemEvent-calls.patch delete mode 100644 patches/server/0913-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch create mode 100644 patches/server/0914-Add-some-minimal-debug-information-to-chat-packet-er.patch delete mode 100644 patches/server/0914-Add-various-missing-EntityDropItemEvent-calls.patch delete mode 100644 patches/server/0915-Add-some-minimal-debug-information-to-chat-packet-er.patch create mode 100644 patches/server/0915-Fix-Bee-flower-NPE.patch delete mode 100644 patches/server/0916-Fix-Bee-flower-NPE.patch create mode 100644 patches/server/0916-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch create mode 100644 patches/server/0917-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch delete mode 100644 patches/server/0917-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch delete mode 100644 patches/server/0918-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch create mode 100644 patches/server/0918-More-Teleport-API.patch create mode 100644 patches/server/0919-Add-EntityPortalReadyEvent.patch delete mode 100644 patches/server/0919-More-Teleport-API.patch delete mode 100644 patches/server/0920-Add-EntityPortalReadyEvent.patch create mode 100644 patches/server/0920-Don-t-use-level-random-in-entity-constructors.patch delete mode 100644 patches/server/0921-Don-t-use-level-random-in-entity-constructors.patch create mode 100644 patches/server/0921-Send-block-entities-after-destroy-prediction.patch delete mode 100644 patches/server/0922-Send-block-entities-after-destroy-prediction.patch create mode 100644 patches/server/0922-Warn-on-plugins-accessing-faraway-chunks.patch create mode 100644 patches/server/0923-Custom-Chat-Completion-Suggestions-API.patch delete mode 100644 patches/server/0923-Warn-on-plugins-accessing-faraway-chunks.patch create mode 100644 patches/server/0924-Add-missing-BlockFadeEvents.patch delete mode 100644 patches/server/0924-Custom-Chat-Completion-Suggestions-API.patch delete mode 100644 patches/server/0925-Add-missing-BlockFadeEvents.patch create mode 100644 patches/server/0925-Collision-API.patch delete mode 100644 patches/server/0926-Collision-API.patch create mode 100644 patches/server/0926-Fix-suggest-command-message-for-brigadier-syntax-exc.patch create mode 100644 patches/server/0927-Fix-command-preprocess-cancelling-and-command-changi.patch delete mode 100644 patches/server/0927-Fix-suggest-command-message-for-brigadier-syntax-exc.patch delete mode 100644 patches/server/0928-Fix-command-preprocess-cancelling-and-command-changi.patch create mode 100644 patches/server/0928-Remove-invalid-signature-login-stacktrace.patch create mode 100644 patches/server/0929-Add-async-catcher-to-PlayerConnection-internalTelepo.patch delete mode 100644 patches/server/0929-Remove-invalid-signature-login-stacktrace.patch delete mode 100644 patches/server/0930-Add-async-catcher-to-PlayerConnection-internalTelepo.patch create mode 100644 patches/server/0930-Block-Ticking-API.patch create mode 100644 patches/server/0931-Add-Velocity-IP-Forwarding-Support.patch delete mode 100644 patches/server/0931-Block-Ticking-API.patch delete mode 100644 patches/server/0932-Add-Velocity-IP-Forwarding-Support.patch create mode 100644 patches/server/0932-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch create mode 100644 patches/server/0933-Add-NamespacedKey-biome-methods.patch delete mode 100644 patches/server/0933-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch delete mode 100644 patches/server/0934-Add-NamespacedKey-biome-methods.patch create mode 100644 patches/server/0934-Fix-plugin-loggers-on-server-shutdown.patch delete mode 100644 patches/server/0935-Fix-plugin-loggers-on-server-shutdown.patch create mode 100644 patches/server/0935-Workaround-for-client-lag-spikes-MC-162253.patch delete mode 100644 patches/server/0936-Workaround-for-client-lag-spikes-MC-162253.patch diff --git a/patches/api/0005-Adventure.patch b/patches/api/0005-Adventure.patch index 800a471c61..def9677043 100644 --- a/patches/api/0005-Adventure.patch +++ b/patches/api/0005-Adventure.patch @@ -1805,10 +1805,10 @@ index 9566e4306ada5e82dede0f002aa06da12c44996b..4d5f0837bd0e02a30c943d8969fb6b13 + // Paper end } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 922d33ff4fa9d901d3c5c0a9f8399ad8aef62c37..cd287978c34873c7122794e4f3e762915978a1f0 100644 +index 259d878ddd4e4e2b289c0de0325ca8fd6203c484..a829779ac56a271cad463806984991b4713a27be 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -25,7 +25,7 @@ import org.jetbrains.annotations.Nullable; +@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a base entity in the world */ @@ -1817,7 +1817,7 @@ index 922d33ff4fa9d901d3c5c0a9f8399ad8aef62c37..cd287978c34873c7122794e4f3e76291 /** * Gets the entity's current position -@@ -656,4 +656,20 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -683,4 +683,20 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent @Override Spigot spigot(); // Spigot end diff --git a/patches/api/0012-Entity-Origin-API.patch b/patches/api/0012-Entity-Origin-API.patch index c3a843eb0f..38b4bb97a1 100644 --- a/patches/api/0012-Entity-Origin-API.patch +++ b/patches/api/0012-Entity-Origin-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity Origin API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index cd287978c34873c7122794e4f3e762915978a1f0..c315d2494969190f8b53236f905ad5c5cf1bfc39 100644 +index a829779ac56a271cad463806984991b4713a27be..20c529bdd94a6bac09d9f8222f33dfc9e8d53fa9 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -671,5 +671,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -698,5 +698,15 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent default net.kyori.adventure.text.event.HoverEvent asHoverEvent(final @NotNull java.util.function.UnaryOperator op) { return net.kyori.adventure.text.event.HoverEvent.showEntity(op.apply(net.kyori.adventure.text.event.HoverEvent.ShowEntity.of(this.getType().getKey(), this.getUniqueId(), this.customName()))); } diff --git a/patches/api/0024-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/api/0024-Add-methods-for-working-with-arrows-stuck-in-living-.patch index c03be3f259..e8a86018cb 100644 --- a/patches/api/0024-Add-methods-for-working-with-arrows-stuck-in-living-.patch +++ b/patches/api/0024-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add methods for working with arrows stuck in living entities diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 9f876af2324ebb1f299fbebc8c66d551df7463f0..ba64745a77821ecb8626aca4c8df264c93835270 100644 +index 2b816f0e6bdb912ceeff82c0043272b3970fe243..f00502b59f15c3a92ce18e7d1aa4e546fd45b16a 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -620,4 +620,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -689,4 +689,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource * @return Whether the entity is invisible */ public boolean isInvisible(); diff --git a/patches/api/0048-Fireworks-API-s.patch b/patches/api/0048-Fireworks-API-s.patch index a0ed753ac6..126f0b838d 100644 --- a/patches/api/0048-Fireworks-API-s.patch +++ b/patches/api/0048-Fireworks-API-s.patch @@ -7,10 +7,10 @@ Get the Entity being boosted Get the firework launcher diff --git a/src/main/java/org/bukkit/entity/Firework.java b/src/main/java/org/bukkit/entity/Firework.java -index 05e86cb9d826cdf14490fa649348d46c51adbfdb..d616d5941b3c7b85e350e845901da798601b9a3c 100644 +index e750b34d7d067a5f2f5587853274b6f479cc4fd6..0d31aa0b22cf1e849572294e2cfe38b48c9210af 100644 --- a/src/main/java/org/bukkit/entity/Firework.java +++ b/src/main/java/org/bukkit/entity/Firework.java -@@ -43,4 +43,15 @@ public interface Firework extends Projectile { +@@ -111,4 +111,20 @@ public interface Firework extends Projectile { * @param shotAtAngle the new shotAtAngle */ void setShotAtAngle(boolean shotAtAngle); @@ -20,9 +20,14 @@ index 05e86cb9d826cdf14490fa649348d46c51adbfdb..d616d5941b3c7b85e350e845901da798 + public java.util.UUID getSpawningEntity(); + /** + * If this firework is boosting an entity, return it ++ * @deprecated use {@link #getAttachedTo()} ++ * @see #setAttachedTo(LivingEntity) + * @return The entity being boosted + */ + @org.jetbrains.annotations.Nullable -+ public LivingEntity getBoostedEntity(); ++ @Deprecated ++ default LivingEntity getBoostedEntity() { ++ return getAttachedTo(); ++ } + // Paper end } diff --git a/patches/api/0054-Fix-upstream-javadocs.patch b/patches/api/0054-Fix-upstream-javadocs.patch index fcc322238c..15c2c3e68d 100644 --- a/patches/api/0054-Fix-upstream-javadocs.patch +++ b/patches/api/0054-Fix-upstream-javadocs.patch @@ -90,10 +90,10 @@ index 91fc11dda99de506be83d40df8929bf7cd8e8d85..7dc631ebd009f5f5c3ac1699c3f3515c // Paper end } diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 2cf43eac30187a43a01c81b3021b2cfec0d4ba8a..864941be2d07de08f63e740ad2becf1dc5790433 100644 +index 652238659e0a6e0df11f2798773aea4fe7712360..9c57eda3b7af7026639afda9959bc5e5a720861a 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -21,6 +21,11 @@ import org.jetbrains.annotations.Nullable; +@@ -22,6 +22,11 @@ import org.jetbrains.annotations.Nullable; */ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder { @@ -106,10 +106,10 @@ index 2cf43eac30187a43a01c81b3021b2cfec0d4ba8a..864941be2d07de08f63e740ad2becf1d * Returns the name of this player * diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java -index be9334a8b5fba9181ad63c211697e798be63da25..0514a141cb93a650be38c63d4336d46e4304f4b6 100644 +index 6f117995c3ccf9182946c01a663cd8bef1804eb3..00266bf81b022a0f4b4f71061b82a8713b773320 100644 --- a/src/main/java/org/bukkit/entity/Mob.java +++ b/src/main/java/org/bukkit/entity/Mob.java -@@ -8,6 +8,10 @@ import org.jetbrains.annotations.Nullable; +@@ -9,6 +9,10 @@ import org.jetbrains.annotations.Nullable; */ public interface Mob extends LivingEntity, Lootable { diff --git a/patches/api/0060-Shoulder-Entities-Release-API.patch b/patches/api/0060-Shoulder-Entities-Release-API.patch index bcac62e0ca..a7f5fc1a48 100644 --- a/patches/api/0060-Shoulder-Entities-Release-API.patch +++ b/patches/api/0060-Shoulder-Entities-Release-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Shoulder Entities Release API diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 864941be2d07de08f63e740ad2becf1dc5790433..bcdf267485f1d68ccc7ea105d5d40bc9bc9db2a2 100644 +index 9c57eda3b7af7026639afda9959bc5e5a720861a..be3eaadbc768a306da68a15abcaa7a5d3ec760c7 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -318,6 +318,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -319,6 +319,26 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public int getExpToLevel(); diff --git a/patches/api/0061-Entity-fromMobSpawner.patch b/patches/api/0061-Entity-fromMobSpawner.patch index 03b16193df..b0560b7aad 100644 --- a/patches/api/0061-Entity-fromMobSpawner.patch +++ b/patches/api/0061-Entity-fromMobSpawner.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity#fromMobSpawner() diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index c315d2494969190f8b53236f905ad5c5cf1bfc39..b9a61d06d72831dc0c591e129553453a537d3785 100644 +index 20c529bdd94a6bac09d9f8222f33dfc9e8d53fa9..e598c7c90d625313b8a935418bb68e0e6cb6bc6e 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -681,5 +681,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -708,5 +708,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @Nullable Location getOrigin(); diff --git a/patches/api/0067-LivingEntity-setKiller.patch b/patches/api/0067-LivingEntity-setKiller.patch index 37cb713a6e..706e674ebd 100644 --- a/patches/api/0067-LivingEntity-setKiller.patch +++ b/patches/api/0067-LivingEntity-setKiller.patch @@ -5,10 +5,10 @@ Subject: [PATCH] LivingEntity#setKiller diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index b41133f23d25f90fc0993499056c4eeaf003a701..bfc90a3569abc717f37c064e3068c55ef323edab 100644 +index f00502b59f15c3a92ce18e7d1aa4e546fd45b16a..036936671d816fc553ad2fdf8324609ab610b7f5 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -279,6 +279,15 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -281,6 +281,15 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource @Nullable public Player getKiller(); diff --git a/patches/api/0094-Add-openSign-method-to-HumanEntity.patch b/patches/api/0094-Add-openSign-method-to-HumanEntity.patch index f28444bf3e..0ea0d12794 100644 --- a/patches/api/0094-Add-openSign-method-to-HumanEntity.patch +++ b/patches/api/0094-Add-openSign-method-to-HumanEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add openSign method to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index bcdf267485f1d68ccc7ea105d5d40bc9bc9db2a2..bd9222b9b5e7ec1f3aebe37838775f345e868150 100644 +index be3eaadbc768a306da68a15abcaa7a5d3ec760c7..13b74e942012169611f2791f8b4493d04710e4c0 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -479,6 +479,14 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -480,6 +480,14 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ @Deprecated public void setShoulderEntityRight(@Nullable Entity entity); diff --git a/patches/api/0110-Make-shield-blocking-delay-configurable.patch b/patches/api/0110-Make-shield-blocking-delay-configurable.patch index 48d3e8c766..a6061071b5 100644 --- a/patches/api/0110-Make-shield-blocking-delay-configurable.patch +++ b/patches/api/0110-Make-shield-blocking-delay-configurable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Make shield blocking delay configurable diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 08ce9cb5a44f62dc3138c45aab947a52a24d3e37..dbe8511d97bfd6361177f66ae638af0c3be19a04 100644 +index 036936671d816fc553ad2fdf8324609ab610b7f5..75629874849e4cdcf0465b653f27baaca5247fea 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -643,5 +643,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -712,5 +712,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource * @param arrows Number of arrows to stick in this entity */ void setArrowsStuck(int arrows); diff --git a/patches/api/0117-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/api/0117-LivingEntity-Hand-Raised-Item-Use-API.patch index a7add5e441..12e017a64e 100644 --- a/patches/api/0117-LivingEntity-Hand-Raised-Item-Use-API.patch +++ b/patches/api/0117-LivingEntity-Hand-Raised-Item-Use-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] LivingEntity Hand Raised/Item Use API How long an entity has raised hands to charge an attack or use an item diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index bd9222b9b5e7ec1f3aebe37838775f345e868150..34c2ae10e2a230ef88a756cf2024edcda2429fbf 100644 +index 13b74e942012169611f2791f8b4493d04710e4c0..9c711d0b2c2f7b0c0603847590e8a1a94f091ff0 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -307,7 +307,9 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -308,7 +308,9 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder * * @return the item being used by the player, or null if they are not using * an item @@ -20,10 +20,10 @@ index bd9222b9b5e7ec1f3aebe37838775f345e868150..34c2ae10e2a230ef88a756cf2024edcd public ItemStack getItemInUse(); diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index dbe8511d97bfd6361177f66ae638af0c3be19a04..06896b9690a939ade2167da1a19239df2dcdba8f 100644 +index 75629874849e4cdcf0465b653f27baaca5247fea..c80e75b72ac863db19e3d234e349876dd8797924 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -657,5 +657,42 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -726,5 +726,42 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource * @param delay Delay in ticks */ void setShieldBlockingDelay(int delay); diff --git a/patches/api/0120-InventoryCloseEvent-Reason-API.patch b/patches/api/0120-InventoryCloseEvent-Reason-API.patch index e4402af9d0..45e9ab3f1f 100644 --- a/patches/api/0120-InventoryCloseEvent-Reason-API.patch +++ b/patches/api/0120-InventoryCloseEvent-Reason-API.patch @@ -7,10 +7,10 @@ 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/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 34c2ae10e2a230ef88a756cf2024edcda2429fbf..f97521acad823ffce08faefc81e3b6a9a374410e 100644 +index 9c711d0b2c2f7b0c0603847590e8a1a94f091ff0..8f489abbb7e80d869ca0f6e8626cecbd453b625a 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -158,6 +158,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -159,6 +159,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public void closeInventory(); diff --git a/patches/api/0122-Entity-getChunk-API.patch b/patches/api/0122-Entity-getChunk-API.patch index 78f10f0732..68c747ae93 100644 --- a/patches/api/0122-Entity-getChunk-API.patch +++ b/patches/api/0122-Entity-getChunk-API.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Entity#getChunk API Get the chunk the entity is currently registered to diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index b9a61d06d72831dc0c591e129553453a537d3785..df07eb07896790a09d1022daef5cffc6a435f739 100644 +index e598c7c90d625313b8a935418bb68e0e6cb6bc6e..c6f14159067a14afd44378aaa9ac840aba2e2f9c 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java @@ -3,6 +3,7 @@ package org.bukkit.entity; @@ -17,7 +17,7 @@ index b9a61d06d72831dc0c591e129553453a537d3785..df07eb07896790a09d1022daef5cffc6 import org.bukkit.EntityEffect; import org.bukkit.Location; import org.bukkit.Nameable; -@@ -688,5 +689,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -715,5 +716,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return True if entity spawned from a mob spawner */ boolean fromMobSpawner(); diff --git a/patches/api/0146-Async-Chunks-API.patch b/patches/api/0146-Async-Chunks-API.patch index 699def16c0..67dcb55f9c 100644 --- a/patches/api/0146-Async-Chunks-API.patch +++ b/patches/api/0146-Async-Chunks-API.patch @@ -495,10 +495,10 @@ index 324fca7bf480a463adb30842fa169052534f5252..3e3682be2a1afe92ccdc9a1d97469a69 /** diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index df07eb07896790a09d1022daef5cffc6a435f739..7a05615ec7678338801bcae2ec9a029b13d323d2 100644 +index c6f14159067a14afd44378aaa9ac840aba2e2f9c..8751ba50a0e5d00839885ad5a905eb727445b749 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -163,6 +163,33 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -164,6 +164,33 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ public boolean teleport(@NotNull Entity destination, @NotNull TeleportCause cause); diff --git a/patches/api/0147-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/api/0147-Add-ray-tracing-methods-to-LivingEntity.patch index b3732ab33f..88b6298ac2 100644 --- a/patches/api/0147-Add-ray-tracing-methods-to-LivingEntity.patch +++ b/patches/api/0147-Add-ray-tracing-methods-to-LivingEntity.patch @@ -65,10 +65,10 @@ index 0000000000000000000000000000000000000000..18a96dbb01d3b34476652264b2d6be37 + } +} diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 1dd9f7ac1f26c253b8181519aa1873784bc54a07..a4b1ae0caea94882f601a0420354838c6a52ef28 100644 +index c80e75b72ac863db19e3d234e349876dd8797924..c29846df0469535870e1743eee25325a4f092a6d 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -81,6 +81,77 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -83,6 +83,77 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource @NotNull public Block getTargetBlock(@Nullable Set transparent, int maxDistance); diff --git a/patches/api/0151-Mob-Pathfinding-API.patch b/patches/api/0151-Mob-Pathfinding-API.patch index 9d4bbc82aa..8c3f3a59d2 100644 --- a/patches/api/0151-Mob-Pathfinding-API.patch +++ b/patches/api/0151-Mob-Pathfinding-API.patch @@ -230,18 +230,18 @@ index 0000000000000000000000000000000000000000..43f062257472a06e9e64c2feef6c3b10 + } +} diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java -index 0514a141cb93a650be38c63d4336d46e4304f4b6..cc30b4e22ee238de13f031398fc566f4123694ff 100644 +index 00266bf81b022a0f4b4f71061b82a8713b773320..dcaddf4a1e3d954565f2110224be8e7d5c597a0a 100644 --- a/src/main/java/org/bukkit/entity/Mob.java +++ b/src/main/java/org/bukkit/entity/Mob.java -@@ -1,6 +1,7 @@ - package org.bukkit.entity; +@@ -2,6 +2,7 @@ package org.bukkit.entity; + import org.bukkit.Sound; import org.bukkit.loot.Lootable; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** -@@ -11,6 +12,13 @@ public interface Mob extends LivingEntity, Lootable { +@@ -12,6 +13,13 @@ public interface Mob extends LivingEntity, Lootable { // Paper start @Override org.bukkit.inventory.@org.jetbrains.annotations.NotNull EntityEquipment getEquipment(); diff --git a/patches/api/0159-Add-LivingEntity-getTargetEntity.patch b/patches/api/0159-Add-LivingEntity-getTargetEntity.patch index 5e2bb493a6..bdd4575033 100644 --- a/patches/api/0159-Add-LivingEntity-getTargetEntity.patch +++ b/patches/api/0159-Add-LivingEntity-getTargetEntity.patch @@ -49,10 +49,10 @@ index 0000000000000000000000000000000000000000..f52644fab1522bdf83ff4f489e9805b2 + } +} diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index a4b1ae0caea94882f601a0420354838c6a52ef28..f479e8c26e88520a47f7beeec753b3af9978bde1 100644 +index c29846df0469535870e1743eee25325a4f092a6d..63b39fe0f2c8c529e289c69969588a98dd7fc758 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -150,6 +150,50 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -152,6 +152,50 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource */ @Nullable public com.destroystokyo.paper.block.TargetBlockInfo getTargetBlockInfo(int maxDistance, @NotNull com.destroystokyo.paper.block.TargetBlockInfo.FluidMode fluidMode); diff --git a/patches/api/0160-Add-sun-related-API.patch b/patches/api/0160-Add-sun-related-API.patch index 2e610f8a1a..ce6031e8d0 100644 --- a/patches/api/0160-Add-sun-related-API.patch +++ b/patches/api/0160-Add-sun-related-API.patch @@ -26,10 +26,10 @@ index 3e3682be2a1afe92ccdc9a1d97469a69f952a9ed..8f7536e5ef73328cb69f7214956aac58 * Gets the full in-game time on this world since the world generation * diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java -index cc30b4e22ee238de13f031398fc566f4123694ff..55c5227a340e34621160afc9fae3ea843492881d 100644 +index dcaddf4a1e3d954565f2110224be8e7d5c597a0a..4a7ffd617c2a660a4d2e9d10f54add2391f832b9 100644 --- a/src/main/java/org/bukkit/entity/Mob.java +++ b/src/main/java/org/bukkit/entity/Mob.java -@@ -19,6 +19,13 @@ public interface Mob extends LivingEntity, Lootable { +@@ -20,6 +20,13 @@ public interface Mob extends LivingEntity, Lootable { */ @NotNull com.destroystokyo.paper.entity.Pathfinder getPathfinder(); diff --git a/patches/api/0175-Entity-getEntitySpawnReason.patch b/patches/api/0175-Entity-getEntitySpawnReason.patch index ad78118bc3..ca09ffacbc 100644 --- a/patches/api/0175-Entity-getEntitySpawnReason.patch +++ b/patches/api/0175-Entity-getEntitySpawnReason.patch @@ -10,10 +10,10 @@ persistenting Living Entity, SPAWNER for spawners, or DEFAULT since data was not stored. diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 7a05615ec7678338801bcae2ec9a029b13d323d2..634f3b5dd22bf439aaec7c3ecfb3477b66e994e8 100644 +index 8751ba50a0e5d00839885ad5a905eb727445b749..f17f30a9969e52313387c0d92edd980f72687a11 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -727,5 +727,11 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -754,5 +754,11 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent // TODO remove impl here return getLocation().getChunk(); } diff --git a/patches/api/0188-Entity-Jump-API.patch b/patches/api/0188-Entity-Jump-API.patch index c3060920af..8c2aa6bf87 100644 --- a/patches/api/0188-Entity-Jump-API.patch +++ b/patches/api/0188-Entity-Jump-API.patch @@ -57,10 +57,10 @@ index 0000000000000000000000000000000000000000..f0067c2e953d18e1a33536980071ba3f + } +} diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 356e516790c4e38d8dac6a106f824787a0ee19ba..33e58114c98a75c14f576db29fe0b658fcec6e15 100644 +index 63b39fe0f2c8c529e289c69969588a98dd7fc758..54046c5810f9f6f3f28e815a210617683139509d 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -809,5 +809,25 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -878,5 +878,25 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource */ @NotNull org.bukkit.inventory.EquipmentSlot getHandRaised(); diff --git a/patches/api/0204-Potential-bed-API.patch b/patches/api/0204-Potential-bed-API.patch index 9434a1da69..102eb79e40 100644 --- a/patches/api/0204-Potential-bed-API.patch +++ b/patches/api/0204-Potential-bed-API.patch @@ -8,10 +8,10 @@ Adds a new method to fetch the location of a player's bed without generating any 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/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index f97521acad823ffce08faefc81e3b6a9a374410e..876215c84cf6915f5af131da38d97c20580c0292 100644 +index 8f489abbb7e80d869ca0f6e8626cecbd453b625a..0775c1061a72af66344ec45248b50072c394da38 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -248,6 +248,19 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -249,6 +249,19 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public int getSleepTicks(); diff --git a/patches/api/0212-Add-entity-liquid-API.patch b/patches/api/0212-Add-entity-liquid-API.patch index 6c8a843dee..19f023a488 100644 --- a/patches/api/0212-Add-entity-liquid-API.patch +++ b/patches/api/0212-Add-entity-liquid-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add entity liquid API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 634f3b5dd22bf439aaec7c3ecfb3477b66e994e8..1c9d0e6541d41972f9966b83cbba02f6b230a72c 100644 +index f17f30a9969e52313387c0d92edd980f72687a11..587bf31ed2b2edc7d467d226c91c2bccdd1e4044 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -733,5 +733,35 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -760,5 +760,35 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason getEntitySpawnReason(); diff --git a/patches/api/0219-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/api/0219-Add-playPickupItemAnimation-to-LivingEntity.patch index ab75024215..a2b38a0e8b 100644 --- a/patches/api/0219-Add-playPickupItemAnimation-to-LivingEntity.patch +++ b/patches/api/0219-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add playPickupItemAnimation to LivingEntity diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 33e58114c98a75c14f576db29fe0b658fcec6e15..5192d2472bece141b0990d48a3373a6fb4e1fea6 100644 +index 54046c5810f9f6f3f28e815a210617683139509d..2c559c590b96753aa5dc2db2bacca32d8afad352 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -829,5 +829,28 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -898,5 +898,28 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource * @param jumping entity jump state */ void setJumping(boolean jumping); diff --git a/patches/api/0223-Add-additional-open-container-api-to-HumanEntity.patch b/patches/api/0223-Add-additional-open-container-api-to-HumanEntity.patch index 62a543fba6..2d427236be 100644 --- a/patches/api/0223-Add-additional-open-container-api-to-HumanEntity.patch +++ b/patches/api/0223-Add-additional-open-container-api-to-HumanEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add additional open container api to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index 876215c84cf6915f5af131da38d97c20580c0292..cbeb9d1b99759cf3cd65895ff54fa7eabf511f3a 100644 +index 0775c1061a72af66344ec45248b50072c394da38..7e23e157eb70e6bf94d0ac4a0196cc5c943dcac4 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -153,6 +153,92 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -154,6 +154,92 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder @Nullable public InventoryView openMerchant(@NotNull Merchant merchant, boolean force); diff --git a/patches/api/0225-Entity-isTicking.patch b/patches/api/0225-Entity-isTicking.patch index ec39bc8a50..ec9d55e28c 100644 --- a/patches/api/0225-Entity-isTicking.patch +++ b/patches/api/0225-Entity-isTicking.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity#isTicking diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 1c9d0e6541d41972f9966b83cbba02f6b230a72c..718af7c49ab8cc232bf72cecdef8a90e2595e835 100644 +index 587bf31ed2b2edc7d467d226c91c2bccdd1e4044..895ccd46d6792210ac9e53093111bd9439e1a41c 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -763,5 +763,10 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -790,5 +790,10 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * Check if entity is in lava */ public boolean isInLava(); diff --git a/patches/api/0226-Clarify-the-Javadocs-for-Entity.getEntitySpawnReason.patch b/patches/api/0226-Clarify-the-Javadocs-for-Entity.getEntitySpawnReason.patch index 2ca22c2951..5bc3c30ecd 100644 --- a/patches/api/0226-Clarify-the-Javadocs-for-Entity.getEntitySpawnReason.patch +++ b/patches/api/0226-Clarify-the-Javadocs-for-Entity.getEntitySpawnReason.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Clarify the Javadocs for Entity.getEntitySpawnReason() diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 718af7c49ab8cc232bf72cecdef8a90e2595e835..e3de56ffa7b3a554755a7401988945eca655d816 100644 +index 895ccd46d6792210ac9e53093111bd9439e1a41c..b9701e29d4bf2fbcc08bfacf3ebfb275fc0ae8b0 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -729,7 +729,7 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -756,7 +756,7 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent } /** diff --git a/patches/api/0235-Add-LivingEntity-clearActiveItem.patch b/patches/api/0235-Add-LivingEntity-clearActiveItem.patch index 81c9b4ce9b..66a33785b7 100644 --- a/patches/api/0235-Add-LivingEntity-clearActiveItem.patch +++ b/patches/api/0235-Add-LivingEntity-clearActiveItem.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add LivingEntity#clearActiveItem diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 5192d2472bece141b0990d48a3373a6fb4e1fea6..aa1b76a0633e223fbae0897cb0690fdd7e9f4c40 100644 +index 2c559c590b96753aa5dc2db2bacca32d8afad352..8375cbdb79e82afebbcc18f85e874238f18cda50 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -780,6 +780,13 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -849,6 +849,13 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource @NotNull org.bukkit.inventory.ItemStack getActiveItem(); diff --git a/patches/api/0241-Expose-LivingEntity-hurt-direction.patch b/patches/api/0241-Expose-LivingEntity-hurt-direction.patch index de5038be8f..3d78532a21 100644 --- a/patches/api/0241-Expose-LivingEntity-hurt-direction.patch +++ b/patches/api/0241-Expose-LivingEntity-hurt-direction.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose LivingEntity hurt direction diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index aa1b76a0633e223fbae0897cb0690fdd7e9f4c40..a8ccf7f0a74a8295bbb921e24eed626c15104b8c 100644 +index 8375cbdb79e82afebbcc18f85e874238f18cda50..a6c0b5e55adadd78619d251d979e9e46068a637e 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -859,5 +859,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -928,5 +928,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource * @param quantity quantity of item */ void playPickupItemAnimation(@NotNull Item item, int quantity); diff --git a/patches/api/0271-Expose-Tracked-Players.patch b/patches/api/0271-Expose-Tracked-Players.patch index a66875419d..09aaa196fb 100644 --- a/patches/api/0271-Expose-Tracked-Players.patch +++ b/patches/api/0271-Expose-Tracked-Players.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Expose Tracked Players diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index e3de56ffa7b3a554755a7401988945eca655d816..898c005cb715235df8d7ed6a98faa8191af2fd91 100644 +index b9701e29d4bf2fbcc08bfacf3ebfb275fc0ae8b0..fd9bc146a06e06d0967d9c45421aec460e49bab6 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -768,5 +768,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -795,5 +795,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * Check if entity is inside a ticking chunk */ public boolean isTicking(); diff --git a/patches/api/0280-add-isDeeplySleeping-to-HumanEntity.patch b/patches/api/0280-add-isDeeplySleeping-to-HumanEntity.patch index 07a547d72d..85ca92bd05 100644 --- a/patches/api/0280-add-isDeeplySleeping-to-HumanEntity.patch +++ b/patches/api/0280-add-isDeeplySleeping-to-HumanEntity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] add isDeeplySleeping to HumanEntity diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index cbeb9d1b99759cf3cd65895ff54fa7eabf511f3a..f9531c0f909c7caeddfb8f06ef9a11469ba7d434 100644 +index 7e23e157eb70e6bf94d0ac4a0196cc5c943dcac4..29885c7184dbec82a6de69c39878276392779d71 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -327,6 +327,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -328,6 +328,15 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder */ public void setCooldown(@NotNull Material material, int ticks); diff --git a/patches/api/0300-Add-Mob-lookAt-API.patch b/patches/api/0300-Add-Mob-lookAt-API.patch index 26177f2369..80f5c58df3 100644 --- a/patches/api/0300-Add-Mob-lookAt-API.patch +++ b/patches/api/0300-Add-Mob-lookAt-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Mob#lookAt API diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java -index 55c5227a340e34621160afc9fae3ea843492881d..07bedbc15ba2463d3c629ae68d229286d4033f79 100644 +index 4a7ffd617c2a660a4d2e9d10f54add2391f832b9..c6d8622fba3397cf3434726f1d7c49e95b887e46 100644 --- a/src/main/java/org/bukkit/entity/Mob.java +++ b/src/main/java/org/bukkit/entity/Mob.java -@@ -26,6 +26,88 @@ public interface Mob extends LivingEntity, Lootable { +@@ -27,6 +27,88 @@ public interface Mob extends LivingEntity, Lootable { * @return True if mob is exposed to daylight */ boolean isInDaylight(); diff --git a/patches/api/0310-Add-more-line-of-sight-methods.patch b/patches/api/0310-Add-more-line-of-sight-methods.patch index adfd70e676..3a53cfffbb 100644 --- a/patches/api/0310-Add-more-line-of-sight-methods.patch +++ b/patches/api/0310-Add-more-line-of-sight-methods.patch @@ -23,10 +23,10 @@ index aa534b1a9a1fb84a2fbd4b372f313bb4b63325fa..43b53c21af01e0f496c8aaacff82dfdf // Paper end } diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 087a9c54bc11d5d87aa90bf9d8e66fdac2c44457..5238d83788ef39db1f86c22a0b27648cc47a215b 100644 +index a6c0b5e55adadd78619d251d979e9e46068a637e..d47706a915ae1bfaa3187a83cb6d35ea202c03d0 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -482,6 +482,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -484,6 +484,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource */ public boolean hasLineOfSight(@NotNull Entity other); diff --git a/patches/api/0312-Missing-Entity-Behavior-API.patch b/patches/api/0312-Missing-Entity-Behavior-API.patch index 4d2a95da06..78378f8e51 100644 --- a/patches/api/0312-Missing-Entity-Behavior-API.patch +++ b/patches/api/0312-Missing-Entity-Behavior-API.patch @@ -420,13 +420,13 @@ index 479f7a7c54c85cb685f56e60906650d1989c03ff..60267ee382de80fab86b440ff72a2455 +} +// Paper end diff --git a/src/main/java/org/bukkit/entity/Raider.java b/src/main/java/org/bukkit/entity/Raider.java -index 9a99b8ca1ec9c3c88b29275c88b1221e1b22bcef..756b4a7794ea0905abd4e4fe777f69ffe36658f5 100644 +index 987f9b0866b213450b4de1310600161c8587a545..144fdcfd1f35b6346b672006905aedb8a3773018 100644 --- a/src/main/java/org/bukkit/entity/Raider.java +++ b/src/main/java/org/bukkit/entity/Raider.java -@@ -47,4 +47,20 @@ public interface Raider extends Monster { - * @param join CanJoinRaid status +@@ -57,4 +57,20 @@ public interface Raider extends Monster { */ - void setCanJoinRaid(boolean join); + @NotNull + Sound getCelebrationSound(); + + // Paper start + /** diff --git a/patches/api/0316-Stinger-API.patch b/patches/api/0316-Stinger-API.patch index fdd5a623a9..29eea0d3f0 100644 --- a/patches/api/0316-Stinger-API.patch +++ b/patches/api/0316-Stinger-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Stinger API diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java -index 5238d83788ef39db1f86c22a0b27648cc47a215b..8fa8922ad2fb0ea8f770368faff61e56e9761df9 100644 +index d47706a915ae1bfaa3187a83cb6d35ea202c03d0..4af5e8d0cba6555f7615e4e809d9aff221c0dc4d 100644 --- a/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -335,6 +335,36 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource +@@ -337,6 +337,36 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource */ public void setArrowsInBody(int count); diff --git a/patches/api/0331-Left-handed-API.patch b/patches/api/0331-Left-handed-API.patch index c6c9f1b39f..f22529b07f 100644 --- a/patches/api/0331-Left-handed-API.patch +++ b/patches/api/0331-Left-handed-API.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Left handed API diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java -index 07bedbc15ba2463d3c629ae68d229286d4033f79..984ad873f36c3dcc73703eb6902c4eab5f1e72b6 100644 +index c6d8622fba3397cf3434726f1d7c49e95b887e46..12ebceec3eb6f4c3325e6c6a592676795a988136 100644 --- a/src/main/java/org/bukkit/entity/Mob.java +++ b/src/main/java/org/bukkit/entity/Mob.java -@@ -148,4 +148,20 @@ public interface Mob extends LivingEntity, Lootable { - * @return whether the mob is aware +@@ -162,4 +162,20 @@ public interface Mob extends LivingEntity, Lootable { */ - public boolean isAware(); + @Nullable + public Sound getAmbientSound(); + + // Paper start + /** diff --git a/patches/api/0339-Add-Raw-Byte-Entity-Serialization.patch b/patches/api/0339-Add-Raw-Byte-Entity-Serialization.patch index 422f59252e..3d0663f68d 100644 --- a/patches/api/0339-Add-Raw-Byte-Entity-Serialization.patch +++ b/patches/api/0339-Add-Raw-Byte-Entity-Serialization.patch @@ -24,10 +24,10 @@ index be8d5c172b0a300648f21e2163ccf0a9cd7915ee..4fcafddf3792b66c618f91e04d102f37 * Return the translation key for the Material, so the client can translate it into the active * locale when using a {@link net.kyori.adventure.text.TranslatableComponent}. diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 898c005cb715235df8d7ed6a98faa8191af2fd91..9b46e42fcd803c2f0fb46b220ed79d69b1d16fc4 100644 +index fd9bc146a06e06d0967d9c45421aec460e49bab6..f1cb02f7891e47b7c8328bfd62437deed93226b3 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -775,5 +775,32 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -802,5 +802,32 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return players in tracking range */ @NotNull Set getTrackedPlayers(); diff --git a/patches/api/0346-Entity-powdered-snow-API.patch b/patches/api/0346-Entity-powdered-snow-API.patch index 88414bd4ef..a223c993a8 100644 --- a/patches/api/0346-Entity-powdered-snow-API.patch +++ b/patches/api/0346-Entity-powdered-snow-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity powdered snow API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 9b46e42fcd803c2f0fb46b220ed79d69b1d16fc4..9c31424a297b9b727ac4ad13040eb9e5674b716b 100644 +index f1cb02f7891e47b7c8328bfd62437deed93226b3..80140c8636c10553c1be741b7e4e5084007279e5 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -802,5 +802,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -829,5 +829,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * @return Whether the entity was successfully spawned. */ public boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason); diff --git a/patches/api/0359-Freeze-Tick-Lock-API.patch b/patches/api/0359-Freeze-Tick-Lock-API.patch index 9f42a812f8..5e471565ab 100644 --- a/patches/api/0359-Freeze-Tick-Lock-API.patch +++ b/patches/api/0359-Freeze-Tick-Lock-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Freeze Tick Lock API diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 9c31424a297b9b727ac4ad13040eb9e5674b716b..8bc6876c82935988436597161fa0ec94c032174b 100644 +index 80140c8636c10553c1be741b7e4e5084007279e5..461e29a0abbb021c886bffec71c948e1edc3d222 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -278,6 +278,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -279,6 +279,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ boolean isFrozen(); diff --git a/patches/api/0371-More-Projectile-API.patch b/patches/api/0371-More-Projectile-API.patch index 6a949d5721..80f2397953 100644 --- a/patches/api/0371-More-Projectile-API.patch +++ b/patches/api/0371-More-Projectile-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] More Projectile API Co-authored-by: Nassim Jahnke diff --git a/src/main/java/org/bukkit/entity/Firework.java b/src/main/java/org/bukkit/entity/Firework.java -index d616d5941b3c7b85e350e845901da798601b9a3c..b7a6e3b1ac327c4e03f9d73952c1ce4d54967cf4 100644 +index 0d31aa0b22cf1e849572294e2cfe38b48c9210af..571712e17551f24b369044b8215b722f7183ae7d 100644 --- a/src/main/java/org/bukkit/entity/Firework.java +++ b/src/main/java/org/bukkit/entity/Firework.java -@@ -15,6 +15,8 @@ public interface Firework extends Projectile { +@@ -16,6 +16,8 @@ public interface Firework extends Projectile { /** * Apply the provided meta to the fireworks @@ -18,9 +18,49 @@ index d616d5941b3c7b85e350e845901da798601b9a3c..b7a6e3b1ac327c4e03f9d73952c1ce4d * * @param meta The FireworkMeta to apply */ -@@ -54,4 +56,52 @@ public interface Firework extends Projectile { - @org.jetbrains.annotations.Nullable - public LivingEntity getBoostedEntity(); +@@ -54,31 +56,39 @@ public interface Firework extends Projectile { + * {@link #getMaxLife()}, the firework will detonate. + * + * @param ticks the ticks to set. Must be greater than or equal to 0 ++ * @deprecated use {@link #setTicksFlown(int)} + * @return true if the life was set, false if this firework has already detonated + */ ++ @Deprecated(forRemoval = true) // Paper + boolean setLife(int ticks); + + /** + * Get the ticks that this firework has been alive. When this value reaches + * {@link #getMaxLife()}, the firework will detonate. + * ++ * @deprecated use {@link #getTicksFlown()} + * @return the life ticks + */ ++ @Deprecated(forRemoval = true) // Paper + int getLife(); + + /** + * Set the time in ticks this firework will exist until it is detonated. + * + * @param ticks the ticks to set. Must be greater than 0 ++ * @deprecated use {@link #setTicksToDetonate(int)} + * @return true if the time was set, false if this firework has already detonated + */ ++ @Deprecated(forRemoval = true) // Paper + boolean setMaxLife(int ticks); + + /** + * Get the time in ticks this firework will exist until it is detonated. + * ++ * @deprecated use {@link #getTicksToDetonate()} + * @return the maximum life in ticks + */ ++ @Deprecated(forRemoval = true) // Paper + int getMaxLife(); + + /** +@@ -127,4 +137,52 @@ public interface Firework extends Projectile { + return getAttachedTo(); + } // Paper end + + // Paper start - Firework API diff --git a/patches/api/0385-Add-Player-getFishHook.patch b/patches/api/0385-Add-Player-getFishHook.patch index c58145c58b..9c96c813ae 100644 --- a/patches/api/0385-Add-Player-getFishHook.patch +++ b/patches/api/0385-Add-Player-getFishHook.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add Player#getFishHook diff --git a/src/main/java/org/bukkit/entity/HumanEntity.java b/src/main/java/org/bukkit/entity/HumanEntity.java -index f9531c0f909c7caeddfb8f06ef9a11469ba7d434..f854c1252f42ac02ad4eb84f7b5734b4cec88e53 100644 +index 29885c7184dbec82a6de69c39878276392779d71..3b204144e2d245098b3dc23b8779f9ed817bb6d1 100644 --- a/src/main/java/org/bukkit/entity/HumanEntity.java +++ b/src/main/java/org/bukkit/entity/HumanEntity.java -@@ -355,6 +355,13 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder +@@ -356,6 +356,13 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder @Nullable public Location getPotentialBedLocation(); // Paper end diff --git a/patches/api/0386-More-Teleport-API.patch b/patches/api/0386-More-Teleport-API.patch index d0cfa4990f..0c960c2de7 100644 --- a/patches/api/0386-More-Teleport-API.patch +++ b/patches/api/0386-More-Teleport-API.patch @@ -76,10 +76,10 @@ index 0000000000000000000000000000000000000000..0426ee8bd71142b6f933a479c0f2e5ef + +} diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 8bc6876c82935988436597161fa0ec94c032174b..03b35d3ba8ba00c0fa0272450f19355244a014ea 100644 +index 461e29a0abbb021c886bffec71c948e1edc3d222..321f3ec6c7020ada38143832aea1c545850b9b21 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -121,10 +121,77 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -122,10 +122,77 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent * * @param yaw the yaw * @param pitch the pitch diff --git a/patches/api/0389-Collision-API.patch b/patches/api/0389-Collision-API.patch index 8ab9ca750f..84c0fd74ca 100644 --- a/patches/api/0389-Collision-API.patch +++ b/patches/api/0389-Collision-API.patch @@ -25,10 +25,10 @@ index 3f7e860de4e28745fcdf8d2f41f4a8c210f48909..39fa4c65e0f61450901662ff5c08d54a // Paper end } diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java -index 03b35d3ba8ba00c0fa0272450f19355244a014ea..4d4a0d15876cc48c9c0456b9f11a5dda37fd56ce 100644 +index 321f3ec6c7020ada38143832aea1c545850b9b21..6dd81f07ef4d6993187987d3352a01771ffb715e 100644 --- a/src/main/java/org/bukkit/entity/Entity.java +++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -897,4 +897,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent +@@ -924,4 +924,26 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent */ boolean isInPowderedSnow(); // Paper end diff --git a/patches/server/0008-MC-Utils.patch b/patches/server/0008-MC-Utils.patch index 23e7f4e394..cc478ca443 100644 --- a/patches/server/0008-MC-Utils.patch +++ b/patches/server/0008-MC-Utils.patch @@ -6415,7 +6415,7 @@ index 288fdbef407d11ab430d5d7026dfad148c3c1065..6fefa619299d3202158490630d62c16a @Override public void tell(R runnable) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 6e6d33fcf2786fb995696cf69d66fc10b11a1dfa..331686c382fa88c0fd32056e2c68c3078341f4b7 100644 +index 9fdfeab462e5f5c5e09c5fee2dfe1fca89330086..18d56618a1e8ff5ba408523f620333dbdf48a257 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -315,6 +315,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -6431,7 +6431,7 @@ index 6e6d33fcf2786fb995696cf69d66fc10b11a1dfa..331686c382fa88c0fd32056e2c68c307 public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8fd9667776dc9822cb2e4f54b77c33f5e2bfd3f2..c4f91b80add5d79d999aa49c5da6dab094a24694 100644 +index ad34d8af3e5bba7ec4f41a10c423ed1262c58f6d..2bad9717ec4ec16309856a83d8e19735ae1fcbec 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -260,6 +260,7 @@ public abstract class LivingEntity extends Entity { @@ -6443,7 +6443,7 @@ index 8fd9667776dc9822cb2e4f54b77c33f5e2bfd3f2..c4f91b80add5d79d999aa49c5da6dab0 @Override public float getBukkitYaw() { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 77a91fe6fd028291e9c66163714ab53f9cf031ed..decf753d088983ef6bbf32a32a6ee8d3cca3ee69 100644 +index 77c6f4ff7131b7317a0c0193ead21eabe38517cb..738c54ce0a0fb7fee6b584a6d96a8b74931222e5 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -242,6 +242,7 @@ public abstract class Mob extends LivingEntity { @@ -7150,10 +7150,10 @@ index 93308369f0bbd1e95569d9d573b8b6f42c8ae5a7..6d9469d577dcbb9d5b5b703cf47c8863 + // 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 ef27c0f94355ec7be4a314a0f93bc1c146012210..71c1595b59b757441304a158367d750ed509e4d1 100644 +index 080c0ab395a457d528c34417c102c2db2feec20f..08f6be760cc2f0a6f9c6a3e165e4554ac01654e0 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1128,4 +1128,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1145,4 +1145,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return this.spigot; } // Spigot end diff --git a/patches/server/0009-Adventure.patch b/patches/server/0009-Adventure.patch index 524bef79f3..8a3fb8a80c 100644 --- a/patches/server/0009-Adventure.patch +++ b/patches/server/0009-Adventure.patch @@ -2048,7 +2048,7 @@ index 84564ca128d2dfc79c0b5a13b699cf6fc80bdea7..9ab4588e4e512176b881ad4c252e400f // CraftBukkit end this.chatVisibility = packet.chatVisibility(); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de24d9b6414 100644 +index f63ade8d99295ce9d001aae6f5228a7374e16438..93d02b5de0721e3c5903e80bbf8b3b56ec3ab45d 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -188,6 +188,8 @@ import org.apache.commons.lang3.StringUtils; @@ -2114,7 +2114,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 // CraftBukkit end this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { -@@ -1788,9 +1793,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1793,9 +1798,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic */ this.player.disconnect(); @@ -2129,7 +2129,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 } // CraftBukkit end this.player.getTextFilter().leave(); -@@ -1880,7 +1887,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1885,7 +1892,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (this.verifyChatMessage(playerchatmessage)) { this.chatMessageChain.append(() -> { CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent().plain()); @@ -2138,7 +2138,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 return CompletableFuture.allOf(completablefuture, completablefuture1).thenAcceptAsync((ovoid) -> { FilterMask filtermask = ((FilteredText) completablefuture.join()).mask(); -@@ -2042,7 +2049,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2047,7 +2054,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.handleCommand(s); } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Do nothing, this is coming from a plugin @@ -2152,7 +2152,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 Player player = this.getCraftPlayer(); AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(this.server)); String originalFormat = event.getFormat(), originalMessage = event.getMessage(); -@@ -2175,9 +2187,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2180,9 +2192,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } private ChatMessageContent getSignedContent(ServerboundChatPacket packet) { @@ -2167,7 +2167,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 } private void broadcastChatMessage(PlayerChatMessage message) { -@@ -2280,14 +2295,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2288,14 +2303,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private CompletableFuture queryChatPreview(String query) { MutableComponent ichatmutablecomponent = Component.literal(query); @@ -2189,7 +2189,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 } private CompletableFuture queryCommandPreview(String query) { -@@ -2296,7 +2314,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2304,7 +2322,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic CompletableFuture completablefuture = this.getPreviewedArgument(commandlistenerwrapper, PreviewableCommand.of(parseresults)); completablefuture.thenAcceptAsync((ichatbasecomponent) -> { @@ -2198,7 +2198,7 @@ index a768fe68db9cf1fedc2e4a2ef7b58fd2673be078..77f169e6d2d9899316c6a38dd7ef8de2 }, this.server); return completablefuture; } -@@ -3086,30 +3104,30 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3094,30 +3112,30 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic return; } @@ -3089,10 +3089,10 @@ index 53b5af4179cc4bc4d5646f183da5e327a45237ac..a859a675b4bc543e139358223cc92ad5 public net.minecraft.world.item.enchantment.Enchantment getHandle() { return this.target; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 71c1595b59b757441304a158367d750ed509e4d1..7c9dcf62f85bb3ddffb4eadb3961d7b356c503f8 100644 +index 08f6be760cc2f0a6f9c6a3e165e4554ac01654e0..dfae0888684cbf3e6b2fc3201c78fa10c67628a1 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -196,6 +196,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -198,6 +198,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { protected Entity entity; private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); @@ -3100,7 +3100,7 @@ index 71c1595b59b757441304a158367d750ed509e4d1..7c9dcf62f85bb3ddffb4eadb3961d7b3 public CraftEntity(final CraftServer server, final Entity entity) { this.server = server; -@@ -830,6 +831,32 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -847,6 +848,32 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return this.getHandle().getVehicle().getBukkitEntity(); } @@ -3133,7 +3133,7 @@ index 71c1595b59b757441304a158367d750ed509e4d1..7c9dcf62f85bb3ddffb4eadb3961d7b3 @Override public void setCustomName(String name) { // sane limit for name length -@@ -885,6 +912,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -902,6 +929,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public String getName() { return CraftChatMessage.fromComponent(this.getHandle().getName()); } @@ -3152,10 +3152,10 @@ index 71c1595b59b757441304a158367d750ed509e4d1..7c9dcf62f85bb3ddffb4eadb3961d7b3 @Override public boolean isPermissionSet(String name) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 56151652fdeb98f54358cfefc3d8773c1a39fcf6..2c934b7a2142a4d1ae21feeb95d23f22cfda3be0 100644 +index ef3d6450b2ae2274b7e40c621aa30da279313669..19549dda26c24388bd13a5a2579789e2d1e3ad88 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -318,9 +318,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -321,9 +321,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { container = CraftEventFactory.callInventoryOpenEvent(player, container); if (container == null) return; @@ -3170,7 +3170,7 @@ index 56151652fdeb98f54358cfefc3d8773c1a39fcf6..2c934b7a2142a4d1ae21feeb95d23f22 player.containerMenu = container; player.initMenu(container); } -@@ -389,8 +392,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -392,8 +395,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { // Now open the window MenuType windowType = CraftContainer.getNotchInventoryType(inventory.getTopInventory()); @@ -3834,7 +3834,7 @@ index a0334ec0a80dfc4f1e68c2e338aa486faaefb29e..257776a12ca26c1e75be22a67c94b0aa @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 ca359cb1ac5f48d4f75d33946fcddedb270407c2..fefa4d83c5699be0b55794cd28d13d27b66ef108 100644 +index 4154b4489be172f1ef1693b54368b7ffc8629c31..e8413ad360e9b6c4eef13edf9dd0095e7e64bce2 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 @@ @@ -3848,19 +3848,7 @@ index ca359cb1ac5f48d4f75d33946fcddedb270407c2..fefa4d83c5699be0b55794cd28d13d27 import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@@ -17,9 +18,11 @@ import org.bukkit.craftbukkit.util.CraftChatMessage; - import org.bukkit.craftbukkit.util.CraftMagicNumbers; - import org.bukkit.inventory.meta.BookMeta; - import org.bukkit.inventory.meta.BookMeta.Generation; -+import org.checkerframework.checker.nullness.qual.NonNull; - - // Spigot start - import static org.spigotmc.ValidateUtils.*; -+ - import java.util.AbstractList; - import net.md_5.bungee.api.chat.BaseComponent; - import net.md_5.bungee.chat.ComponentSerializer; -@@ -269,6 +272,145 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { +@@ -261,6 +262,145 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { this.generation = (generation == null) ? null : generation.ordinal(); } @@ -4006,7 +3994,7 @@ index ca359cb1ac5f48d4f75d33946fcddedb270407c2..fefa4d83c5699be0b55794cd28d13d27 @Override public String getPage(final int page) { Validate.isTrue(this.isValidPage(page), "Invalid page number"); -@@ -413,7 +555,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { +@@ -405,7 +545,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { } @Override @@ -4016,7 +4004,7 @@ index ca359cb1ac5f48d4f75d33946fcddedb270407c2..fefa4d83c5699be0b55794cd28d13d27 if (this.hasTitle()) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBookSigned.java -index 00445fc7373c70f4cecc4114f9bcfb4b6f27c0e8..67a198fe1ba930836b82fcc22ab25eb1810be0cf 100644 +index 507fa96a3fb904b74429df5756c9a6378ec8c5b7..abb9e88abc74135284b941e040d4058690a82b27 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 @@ @@ -4027,7 +4015,7 @@ index 00445fc7373c70f4cecc4114f9bcfb4b6f27c0e8..67a198fe1ba930836b82fcc22ab25eb1 import java.util.Map; import net.minecraft.nbt.CompoundTag; import org.bukkit.Material; -@@ -84,8 +84,29 @@ class CraftMetaBookSigned extends CraftMetaBook implements BookMeta { +@@ -78,8 +78,29 @@ class CraftMetaBookSigned extends CraftMetaBook implements BookMeta { } @Override diff --git a/patches/server/0012-Timings-v2.patch b/patches/server/0012-Timings-v2.patch index 3795c7882e..a9679a9489 100644 --- a/patches/server/0012-Timings-v2.patch +++ b/patches/server/0012-Timings-v2.patch @@ -1262,7 +1262,7 @@ index b2f79a0c9caa6783816afc36531c94378e832cb7..99d44faab5b5da244fdc170c73d73723 this.entityManager.saveAll(); } else { diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a258c965a4a0352f9d77def6748b176f3bdab106..cdc24defe649644ceade1c6cfcfe20c29ca936c1 100644 +index 93d02b5de0721e3c5903e80bbf8b3b56ec3ab45d..4e7db441f68019d6e5d3359605b76bc4b258e87e 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -342,7 +342,6 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @@ -1281,7 +1281,7 @@ index a258c965a4a0352f9d77def6748b176f3bdab106..cdc24defe649644ceade1c6cfcfe20c2 this.chatPreviewThrottler.tick(); } -@@ -2149,7 +2147,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2154,7 +2152,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } private void handleCommand(String s) { @@ -1290,7 +1290,7 @@ index a258c965a4a0352f9d77def6748b176f3bdab106..cdc24defe649644ceade1c6cfcfe20c2 if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); -@@ -2159,7 +2157,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2164,7 +2162,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.cserver.getPluginManager().callEvent(event); if (event.isCancelled()) { @@ -1299,7 +1299,7 @@ index a258c965a4a0352f9d77def6748b176f3bdab106..cdc24defe649644ceade1c6cfcfe20c2 return; } -@@ -2172,7 +2170,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2177,7 +2175,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); return; } finally { @@ -1333,7 +1333,7 @@ index 521f485366c65527ac3289dd27d8f2e311706a10..5833cc3d5014dad82607afc4d643b6be public UserWhiteList getWhiteList() { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 331686c382fa88c0fd32056e2c68c3078341f4b7..47a05aa42739f4cfce828c0de42b4f1da467093e 100644 +index 18d56618a1e8ff5ba408523f620333dbdf48a257..1a44c98b69398ba5dcb4115f0e8fdcf3f62fd920 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -132,7 +132,6 @@ import org.bukkit.craftbukkit.event.CraftPortalEvent; @@ -1425,7 +1425,7 @@ index cdf8020194f2ec1fe7b65b22c8e1f5b1c23eaefa..2db27f5e3e3c1bb0502c055f78c4a81e } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a30cab3b4 100644 +index 2bad9717ec4ec16309856a83d8e19735ae1fcbec..bffaa7397e931b7b15a1780989e69aae8b29345f 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -140,7 +140,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; @@ -1437,7 +1437,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a public abstract class LivingEntity extends Entity { -@@ -2795,7 +2795,6 @@ public abstract class LivingEntity extends Entity { +@@ -2817,7 +2817,6 @@ public abstract class LivingEntity extends Entity { @Override public void tick() { @@ -1445,7 +1445,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a super.tick(); this.updatingUsingItem(); this.updateSwimAmount(); -@@ -2837,9 +2836,7 @@ public abstract class LivingEntity extends Entity { +@@ -2859,9 +2858,7 @@ public abstract class LivingEntity extends Entity { } if (!this.isRemoved()) { @@ -1455,7 +1455,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a } double d0 = this.getX() - this.xo; -@@ -2921,8 +2918,6 @@ public abstract class LivingEntity extends Entity { +@@ -2943,8 +2940,6 @@ public abstract class LivingEntity extends Entity { if (this.isSleeping()) { this.setXRot(0.0F); } @@ -1464,7 +1464,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a } public void detectEquipmentUpdates() { -@@ -3104,7 +3099,6 @@ public abstract class LivingEntity extends Entity { +@@ -3126,7 +3121,6 @@ public abstract class LivingEntity extends Entity { this.setDeltaMovement(d4, d5, d6); this.level.getProfiler().push("ai"); @@ -1472,7 +1472,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a if (this.isImmobile()) { this.jumping = false; this.xxa = 0.0F; -@@ -3114,7 +3108,6 @@ public abstract class LivingEntity extends Entity { +@@ -3136,7 +3130,6 @@ public abstract class LivingEntity extends Entity { this.serverAiStep(); this.level.getProfiler().pop(); } @@ -1480,7 +1480,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a this.level.getProfiler().pop(); this.level.getProfiler().push("jump"); -@@ -3149,9 +3142,9 @@ public abstract class LivingEntity extends Entity { +@@ -3171,9 +3164,9 @@ public abstract class LivingEntity extends Entity { this.updateFallFlying(); AABB axisalignedbb = this.getBoundingBox(); @@ -1492,7 +1492,7 @@ index c4f91b80add5d79d999aa49c5da6dab094a24694..e2d93d7a9c421f896e98e6dd6b318f9a this.level.getProfiler().pop(); this.level.getProfiler().push("freezing"); boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES); -@@ -3180,9 +3173,7 @@ public abstract class LivingEntity extends Entity { +@@ -3202,9 +3195,7 @@ public abstract class LivingEntity extends Entity { this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); } diff --git a/patches/server/0025-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch b/patches/server/0025-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch index c142430702..621e6370ae 100644 --- a/patches/server/0025-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch +++ b/patches/server/0025-Allow-nerfed-mobs-to-jump-and-take-water-damage.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Allow nerfed mobs to jump and take water damage diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index decf753d088983ef6bbf32a32a6ee8d3cca3ee69..4f58fae1b3738e7e0507a46df275a258c94fcec4 100644 +index 738c54ce0a0fb7fee6b584a6d96a8b74931222e5..38d3749104742f63f1f4f4c9595e83a4fa445cd1 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -110,6 +110,7 @@ public abstract class Mob extends LivingEntity { @@ -16,7 +16,7 @@ index decf753d088983ef6bbf32a32a6ee8d3cca3ee69..4f58fae1b3738e7e0507a46df275a258 public GoalSelector targetSelector; @Nullable private LivingEntity target; -@@ -812,7 +813,17 @@ public abstract class Mob extends LivingEntity { +@@ -818,7 +819,17 @@ public abstract class Mob extends LivingEntity { @Override protected final void serverAiStep() { ++this.noActionTime; diff --git a/patches/server/0026-Add-configurable-despawn-distances-for-living-entiti.patch b/patches/server/0026-Add-configurable-despawn-distances-for-living-entiti.patch index 2724d12408..a88ee8f743 100644 --- a/patches/server/0026-Add-configurable-despawn-distances-for-living-entiti.patch +++ b/patches/server/0026-Add-configurable-despawn-distances-for-living-entiti.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add configurable despawn distances for living entities diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 4f58fae1b3738e7e0507a46df275a258c94fcec4..854653b2481ebe3a04f671ef0af2aecf3093fc0b 100644 +index 38d3749104742f63f1f4f4c9595e83a4fa445cd1..ce04fa5ae8c539fd6f6aa7648a4cdce6b680463e 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -788,14 +788,14 @@ public abstract class Mob extends LivingEntity { +@@ -794,14 +794,14 @@ public abstract class Mob extends LivingEntity { if (entityhuman != null) { double d0 = entityhuman.distanceToSqr((Entity) this); diff --git a/patches/server/0032-Player-affects-spawning-API.patch b/patches/server/0032-Player-affects-spawning-API.patch index 749e5e7291..160b9c9ca5 100644 --- a/patches/server/0032-Player-affects-spawning-API.patch +++ b/patches/server/0032-Player-affects-spawning-API.patch @@ -21,10 +21,10 @@ index 5c3b11f738c1ea19981cc878aa6c2323497391a0..b91a61be7c4829fce0ff8da290eab580 public static Predicate withinDistance(double x, double y, double z, double max) { double d4 = max * max; diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 854653b2481ebe3a04f671ef0af2aecf3093fc0b..c09f1ac470c4055897f8d6c6201bd8dc421cdbfe 100644 +index ce04fa5ae8c539fd6f6aa7648a4cdce6b680463e..0b056e65b2efe0f96a6beecfc41709bfa18983ca 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -784,7 +784,7 @@ public abstract class Mob extends LivingEntity { +@@ -790,7 +790,7 @@ public abstract class Mob extends LivingEntity { if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { this.discard(); } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { diff --git a/patches/server/0035-Entity-Origin-API.patch b/patches/server/0035-Entity-Origin-API.patch index c0911d7e0e..f8e6475651 100644 --- a/patches/server/0035-Entity-Origin-API.patch +++ b/patches/server/0035-Entity-Origin-API.patch @@ -25,7 +25,7 @@ index 2f7646e2bcc9622d8579eec25b56615da5a84d06..f5ded21e15ca425d23af90f0e339a961 public void onTrackingEnd(Entity entity) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 70c338f5bf605d2f51a21670634c716177cd6f97..ef7320d69d058ea260e48dd1e0d4fc2d69aec1c2 100644 +index 9878aded49d0049b066fa608c7eaf25a55fcf12e..385c81c9e0faf7a51d24b3542713e0d57e5398dd 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -304,7 +304,27 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -56,7 +56,7 @@ index 70c338f5bf605d2f51a21670634c716177cd6f97..ef7320d69d058ea260e48dd1e0d4fc2d public float getBukkitYaw() { return this.yRot; } -@@ -1847,6 +1867,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -1861,6 +1881,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.bukkitEntity.storeBukkitValues(nbt); } // CraftBukkit end @@ -72,7 +72,7 @@ index 70c338f5bf605d2f51a21670634c716177cd6f97..ef7320d69d058ea260e48dd1e0d4fc2d return nbt; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -1973,6 +2002,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -1987,6 +2016,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } // CraftBukkit end @@ -132,10 +132,10 @@ index 2d4a990da2402a6c24c03e8be7e518e33db99c8f..10f8b5ff56e4c1d8300835e045abdce7 @Nullable diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 7c9dcf62f85bb3ddffb4eadb3961d7b356c503f8..fba18e3c6903d4ed8233b4d32ed07bf08311ca27 100644 +index dfae0888684cbf3e6b2fc3201c78fa10c67628a1..ddd0fde5c9065cc35b3bcf81defb119f5b0608d6 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1198,5 +1198,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1215,5 +1215,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return ret; } diff --git a/patches/server/0055-Ensure-commands-are-not-ran-async.patch b/patches/server/0055-Ensure-commands-are-not-ran-async.patch index 3b81d4e6a7..31bbe6739d 100644 --- a/patches/server/0055-Ensure-commands-are-not-ran-async.patch +++ b/patches/server/0055-Ensure-commands-are-not-ran-async.patch @@ -21,10 +21,10 @@ character. Co-authored-by: Jake Potrebic diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5072d4dc1f7f77c61e3cc72c1101cb95f6596ce7..451dd594a2acc6fbc7112b9ecfa737c942f10a3c 100644 +index 22c095539425a6667b8e7f5c5f0a8ff2e87adfb5..e21a6961bab606036440f2a6bd90998b4129ae10 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2033,7 +2033,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2038,7 +2038,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic return true; } @@ -33,7 +33,7 @@ index 5072d4dc1f7f77c61e3cc72c1101cb95f6596ce7..451dd594a2acc6fbc7112b9ecfa737c9 for (int i = 0; i < message.length(); ++i) { if (!SharedConstants.isAllowedChatCharacter(message.charAt(i))) { return true; -@@ -2050,7 +2050,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2055,7 +2055,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } OutgoingPlayerChatMessage outgoing = OutgoingPlayerChatMessage.create(original); @@ -42,7 +42,7 @@ index 5072d4dc1f7f77c61e3cc72c1101cb95f6596ce7..451dd594a2acc6fbc7112b9ecfa737c9 this.handleCommand(s); } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Do nothing, this is coming from a plugin -@@ -2153,7 +2153,29 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2158,7 +2158,29 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } } diff --git a/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch b/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch index edd16859a7..72602b87ec 100644 --- a/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch +++ b/patches/server/0058-Be-a-bit-more-informative-in-maxHealth-exception.patch @@ -5,10 +5,10 @@ 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 a6192a7edd94e63c568ca8196e0ed9fa993da6bb..1d3cd79634366718cd354cd2f55c6fc974ad3525 100644 +index 489153b3d22b37f01a027b0effc091d4715e7f7b..c2407224e8bc5e872e153de14090d60e66bb07bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -99,7 +99,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -102,7 +102,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { public void setHealth(double health) { health = (float) health; if ((health < 0) || (health > this.getMaxHealth())) { diff --git a/patches/server/0061-Add-configurable-portal-search-radius.patch b/patches/server/0061-Add-configurable-portal-search-radius.patch index 911f221d1d..14632c6848 100644 --- a/patches/server/0061-Add-configurable-portal-search-radius.patch +++ b/patches/server/0061-Add-configurable-portal-search-radius.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add configurable portal search radius diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a761c5b3551c7a87b8d04b03f5640805007fbcd3..3bf89827afc515ffae0f79532b38c5f31ba014f6 100644 +index f9f0cb28811e3a14bf4b5005050920b4992f868b..4b5babb9fa755ba2897fc633c8eba30cfd220ea0 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2937,7 +2937,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2951,7 +2951,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { double d0 = DimensionType.getTeleportationScale(this.level.dimensionType(), destination.dimensionType()); BlockPos blockposition = worldborder.clampToBounds(this.getX() * d0, this.getY(), this.getZ() * d0); // CraftBukkit start diff --git a/patches/server/0062-Add-velocity-warnings.patch b/patches/server/0062-Add-velocity-warnings.patch index 1f75e318ba..e7ff871665 100644 --- a/patches/server/0062-Add-velocity-warnings.patch +++ b/patches/server/0062-Add-velocity-warnings.patch @@ -17,10 +17,10 @@ index fd3ccc1726cd73e9f5be3f936115e2431c77183c..c1c86dc95d610bb391191317fa0c0e4c 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 fba18e3c6903d4ed8233b4d32ed07bf08311ca27..8b4f1ef248d0b13927cbf634a0e2a97eb93c8ae4 100644 +index ddd0fde5c9065cc35b3bcf81defb119f5b0608d6..b9599f4f431098d63be98b5175890371103f8813 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -450,10 +450,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -452,10 +452,40 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public void setVelocity(Vector velocity) { Preconditions.checkArgument(velocity != null, "velocity"); velocity.checkFinite(); diff --git a/patches/server/0066-Disable-Scoreboards-for-non-players-by-default.patch b/patches/server/0066-Disable-Scoreboards-for-non-players-by-default.patch index f95a3237bf..34c1e75d69 100644 --- a/patches/server/0066-Disable-Scoreboards-for-non-players-by-default.patch +++ b/patches/server/0066-Disable-Scoreboards-for-non-players-by-default.patch @@ -11,10 +11,10 @@ So avoid looking up scoreboards and short circuit to the "not on a team" logic which is most likely to be true. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 3bf89827afc515ffae0f79532b38c5f31ba014f6..4218d5aa5b4ba77d304b91c6c6cd9fa9cdaaa532 100644 +index 4b5babb9fa755ba2897fc633c8eba30cfd220ea0..6e8811a6a4b41d38c99ac40a2d4f0bef4713b762 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2575,6 +2575,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2589,6 +2589,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @Nullable public Team getTeam() { @@ -23,7 +23,7 @@ index 3bf89827afc515ffae0f79532b38c5f31ba014f6..4218d5aa5b4ba77d304b91c6c6cd9fa9 } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 0d61cb2b7af48bbc60b04be6a4767ccba2b29ffb..3023e714961d1b565d9e8659e07ccc4dab729f2f 100644 +index 87b8a9bb1493d4ff048f0283151cfc5c8ea7d7ef..4e192c98b7beb23ac75b7442883eba7bb98dc475 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -823,6 +823,7 @@ public abstract class LivingEntity extends Entity { diff --git a/patches/server/0067-Add-methods-for-working-with-arrows-stuck-in-living-.patch b/patches/server/0067-Add-methods-for-working-with-arrows-stuck-in-living-.patch index 37ede5cbc9..4b1d9fd2cb 100644 --- a/patches/server/0067-Add-methods-for-working-with-arrows-stuck-in-living-.patch +++ b/patches/server/0067-Add-methods-for-working-with-arrows-stuck-in-living-.patch @@ -5,10 +5,10 @@ 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 13906f0dfd7169b2110c83b7cb51c4979668cb13..652f0e3429efba1326f0e4cae7096a6cfe4b2c85 100644 +index c2407224e8bc5e872e153de14090d60e66bb07bc..4f4ee7071183e7eef918741e38c2bc2e522c72df 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -722,4 +722,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -764,4 +764,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { this.getHandle().persistentInvisibility = invisible; this.getHandle().setSharedFlag(5, invisible); } diff --git a/patches/server/0069-Complete-resource-pack-API.patch b/patches/server/0069-Complete-resource-pack-API.patch index de2eef11b5..ee380568c3 100644 --- a/patches/server/0069-Complete-resource-pack-API.patch +++ b/patches/server/0069-Complete-resource-pack-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Complete resource pack API diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 451dd594a2acc6fbc7112b9ecfa737c942f10a3c..e4f1b7fca8046df11f7e212c316385f82ce45322 100644 +index e21a6961bab606036440f2a6bd90998b4129ae10..9f0a5b950a022aa2a3d3d60837fdb9023f041a9b 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1763,8 +1763,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1768,8 +1768,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName()); this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); } diff --git a/patches/server/0076-Custom-replacement-for-eaten-items.patch b/patches/server/0076-Custom-replacement-for-eaten-items.patch index fc5200a6d4..f241818165 100644 --- a/patches/server/0076-Custom-replacement-for-eaten-items.patch +++ b/patches/server/0076-Custom-replacement-for-eaten-items.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Custom replacement for eaten items diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 3023e714961d1b565d9e8659e07ccc4dab729f2f..1dcf41a4105c05c3182afa8585dee20723d2c136 100644 +index 4e192c98b7beb23ac75b7442883eba7bb98dc475..5678461976a07f9afecccb1d34ea3eec24fba80e 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3589,9 +3589,10 @@ public abstract class LivingEntity extends Entity { +@@ -3611,9 +3611,10 @@ public abstract class LivingEntity extends Entity { this.triggerItemUseEffects(this.useItem, 16); // CraftBukkit start - fire PlayerItemConsumeEvent ItemStack itemstack; @@ -20,7 +20,7 @@ index 3023e714961d1b565d9e8659e07ccc4dab729f2f..1dcf41a4105c05c3182afa8585dee207 level.getCraftServer().getPluginManager().callEvent(event); if (event.isCancelled()) { -@@ -3605,6 +3606,13 @@ public abstract class LivingEntity extends Entity { +@@ -3627,6 +3628,13 @@ public abstract class LivingEntity extends Entity { } else { itemstack = this.useItem.finishUsingItem(this.level, this); } @@ -34,7 +34,7 @@ index 3023e714961d1b565d9e8659e07ccc4dab729f2f..1dcf41a4105c05c3182afa8585dee207 // CraftBukkit end if (itemstack != this.useItem) { -@@ -3612,6 +3620,11 @@ public abstract class LivingEntity extends Entity { +@@ -3634,6 +3642,11 @@ public abstract class LivingEntity extends Entity { } this.stopUsingItem(); diff --git a/patches/server/0077-handle-NaN-health-absorb-values-and-repair-bad-data.patch b/patches/server/0077-handle-NaN-health-absorb-values-and-repair-bad-data.patch index 9e4b4b1cbb..0abf12d2aa 100644 --- a/patches/server/0077-handle-NaN-health-absorb-values-and-repair-bad-data.patch +++ b/patches/server/0077-handle-NaN-health-absorb-values-and-repair-bad-data.patch @@ -5,7 +5,7 @@ Subject: [PATCH] handle NaN health/absorb values and repair bad data diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 1dcf41a4105c05c3182afa8585dee20723d2c136..54e7fa7a483e15c440f562559601292c2a064208 100644 +index 5678461976a07f9afecccb1d34ea3eec24fba80e..8dc6fbbdfaecf0eb38a876d87d77f111541f766d 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -784,7 +784,13 @@ public abstract class LivingEntity extends Entity { @@ -34,7 +34,7 @@ index 1dcf41a4105c05c3182afa8585dee20723d2c136..54e7fa7a483e15c440f562559601292c // CraftBukkit start - Handle scaled health if (this instanceof ServerPlayer) { org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity(); -@@ -3422,7 +3432,7 @@ public abstract class LivingEntity extends Entity { +@@ -3444,7 +3454,7 @@ public abstract class LivingEntity extends Entity { } public void setAbsorptionAmount(float amount) { diff --git a/patches/server/0087-Add-PlayerUseUnknownEntityEvent.patch b/patches/server/0087-Add-PlayerUseUnknownEntityEvent.patch index 12ef7f289e..fb434cc940 100644 --- a/patches/server/0087-Add-PlayerUseUnknownEntityEvent.patch +++ b/patches/server/0087-Add-PlayerUseUnknownEntityEvent.patch @@ -20,10 +20,10 @@ index 8834ed411a7db86b4d2b88183a1315317107d719..c45b5ab6776f3ac79f856c3a6467c510 static final ServerboundInteractPacket.Action ATTACK_ACTION = new ServerboundInteractPacket.Action() { @Override diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6c0eac7f4fbf57ae1a777de651ef93f577dc9c3a..972f31be7c91aaaa3e959527ca28c706b9ab3028 100644 +index 9f0a5b950a022aa2a3d3d60837fdb9023f041a9b..b86f742c4a7500f0bb241d5c07a077aa1be1c7e5 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2636,8 +2636,37 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2644,8 +2644,37 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic }); } } diff --git a/patches/server/0127-Properly-fix-item-duplication-bug.patch b/patches/server/0127-Properly-fix-item-duplication-bug.patch index 741501f77d..07234fb009 100644 --- a/patches/server/0127-Properly-fix-item-duplication-bug.patch +++ b/patches/server/0127-Properly-fix-item-duplication-bug.patch @@ -19,10 +19,10 @@ index 4d8dfe375f5b3b9e5cfc12a6af0b87ae78f9b764..5d214b7dd4f6d7feff0a1904ce6573cf @Override diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index bfbd6a23a39cf96371ebabbe4a79036acfd3f4b2..846ca6e4437cc827584459511c3e119b35f9ba72 100644 +index 75bc0d9e8ec46d1fcfd6d5886d5ab03537e575d2..9df3561fca35b1649de1545e924bd96ec5f80089 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3275,7 +3275,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3283,7 +3283,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } public final boolean isDisconnected() { diff --git a/patches/server/0128-Firework-API-s.patch b/patches/server/0128-Firework-API-s.patch index cde42af88b..ebac775a19 100644 --- a/patches/server/0128-Firework-API-s.patch +++ b/patches/server/0128-Firework-API-s.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Firework API's diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index 4abcdc515411372006ff5d33510bdd64092c186a..5406925cd66f46ab8744123c670d72cea7bfc3a1 100644 +index ba13507263d78d34eb8d3038c437229c0469d476..61252ef8a79bac0ea2d3d231ec6b12166f39d072 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java @@ -39,6 +39,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { public int lifetime; @Nullable - public LivingEntity attachedToEntity; + public LivingEntity attachedToEntity; // PAIL private -> public + public java.util.UUID spawningEntity; // Paper public FireworkRocketEntity(EntityType type, Level world) { @@ -74,10 +74,10 @@ index cdf35c5873f68245891241c0efa3bcf5658c3f6d..766af1f45b14654d3655a06ae0bfb0d4 if (!user.getAbilities().instabuild) { itemStack.shrink(1); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -index 3a1c3d20ecc3612421e346edbbb74ab47f16a137..be86114eac3975b82ca74d4d6ed3f0402a642e8a 100644 +index 59d5c314711b4ec239e5bcb4272b11ed72705c5a..c242f654c88ca1773429348939d3bb2ffae3768c 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -78,4 +78,17 @@ public class CraftFirework extends CraftProjectile implements Firework { +@@ -135,4 +135,11 @@ public class CraftFirework extends CraftProjectile implements Firework { public void setShotAtAngle(boolean shotAtAngle) { this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_SHOT_AT_ANGLE, shotAtAngle); } @@ -87,11 +87,5 @@ index 3a1c3d20ecc3612421e346edbbb74ab47f16a137..be86114eac3975b82ca74d4d6ed3f040 + public java.util.UUID getSpawningEntity() { + return getHandle().spawningEntity; + } -+ -+ @Override -+ public org.bukkit.entity.LivingEntity getBoostedEntity() { -+ net.minecraft.world.entity.LivingEntity boostedEntity = getHandle().attachedToEntity; -+ return boostedEntity != null ? (org.bukkit.entity.LivingEntity) boostedEntity.getBukkitEntity() : null; -+ } + // Paper end } diff --git a/patches/server/0132-Don-t-allow-entities-to-ride-themselves-572.patch b/patches/server/0132-Don-t-allow-entities-to-ride-themselves-572.patch index 5f23a31a41..39dd67be9b 100644 --- a/patches/server/0132-Don-t-allow-entities-to-ride-themselves-572.patch +++ b/patches/server/0132-Don-t-allow-entities-to-ride-themselves-572.patch @@ -5,10 +5,10 @@ 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 32d230dc6968306dd6e322ffd6a820380e3c88b1..f7a8c947ef639d0d9cf8e527f8a0072432cba0c5 100644 +index ac2b7e86afc21607564ddea62e39ec484e91bbf2..2d6d393ccdb6ed3aadc1e00a7a1a8dcb78277771 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2319,6 +2319,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2333,6 +2333,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } protected boolean addPassenger(Entity entity) { // CraftBukkit diff --git a/patches/server/0134-Cap-Entity-Collisions.patch b/patches/server/0134-Cap-Entity-Collisions.patch index 9fc626bec9..ccec87a847 100644 --- a/patches/server/0134-Cap-Entity-Collisions.patch +++ b/patches/server/0134-Cap-Entity-Collisions.patch @@ -12,7 +12,7 @@ just as it does in Vanilla, but entity pushing logic will be capped. You can set this to 0 to disable collisions. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f7a8c947ef639d0d9cf8e527f8a0072432cba0c5..a676fa481ad3e3ac60cf5ba9d86acd61bea329ca 100644 +index 2d6d393ccdb6ed3aadc1e00a7a1a8dcb78277771..55f1886761c7a528f0f1e371de546aff7707e60b 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -379,6 +379,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -24,10 +24,10 @@ index f7a8c947ef639d0d9cf8e527f8a0072432cba0c5..a676fa481ad3e3ac60cf5ba9d86acd61 private org.bukkit.util.Vector origin; @javax.annotation.Nullable diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 9f6629189a126c12a2d45098e12f41d8e45147a4..841149ffcdb2228fac3ac2bdc56c02ed26f219f6 100644 +index 45b76070d6a178ffb5cd378cbeb12342eab4f360..e711c02279ff48e76038a6281bbe8060e34d900b 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3264,8 +3264,11 @@ public abstract class LivingEntity extends Entity { +@@ -3286,8 +3286,11 @@ public abstract class LivingEntity extends Entity { } } diff --git a/patches/server/0139-Add-option-to-make-parrots-stay-on-shoulders-despite.patch b/patches/server/0139-Add-option-to-make-parrots-stay-on-shoulders-despite.patch index e20ee5f48e..05b0e00728 100644 --- a/patches/server/0139-Add-option-to-make-parrots-stay-on-shoulders-despite.patch +++ b/patches/server/0139-Add-option-to-make-parrots-stay-on-shoulders-despite.patch @@ -11,10 +11,10 @@ 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/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 846ca6e4437cc827584459511c3e119b35f9ba72..db2db8156338eae296f085a60a91a29fe02ab050 100644 +index 9df3561fca35b1649de1545e924bd96ec5f80089..f8955429ec4cfe778de77a7db5fef624c20e4318 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2456,6 +2456,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2464,6 +2464,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic switch (packet.getAction()) { case PRESS_SHIFT_KEY: this.player.setShiftKeyDown(true); diff --git a/patches/server/0143-Item-canEntityPickup.patch b/patches/server/0143-Item-canEntityPickup.patch index 38236cc42a..07b208cc78 100644 --- a/patches/server/0143-Item-canEntityPickup.patch +++ b/patches/server/0143-Item-canEntityPickup.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Item#canEntityPickup diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index c09f1ac470c4055897f8d6c6201bd8dc421cdbfe..049a9c4547428d7306d82ed35bcd470ae6f3efc3 100644 +index 0b056e65b2efe0f96a6beecfc41709bfa18983ca..6d0d194aaababd91a26dffc07f547d60eadd098e 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -629,6 +629,11 @@ public abstract class Mob extends LivingEntity { +@@ -635,6 +635,11 @@ public abstract class Mob extends LivingEntity { ItemEntity entityitem = (ItemEntity) iterator.next(); if (!entityitem.isRemoved() && !entityitem.getItem().isEmpty() && !entityitem.hasPickUpDelay() && this.wantsToPickUp(entityitem.getItem())) { diff --git a/patches/server/0149-Shoulder-Entities-Release-API.patch b/patches/server/0149-Shoulder-Entities-Release-API.patch index 23582ab7cc..50587a494b 100644 --- a/patches/server/0149-Shoulder-Entities-Release-API.patch +++ b/patches/server/0149-Shoulder-Entities-Release-API.patch @@ -58,10 +58,10 @@ index 07a8abb714a9dfd470ab0486c336e3b58ea927a1..d4e3649f4d66545842a7f8cd3dabad39 @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 2c934b7a2142a4d1ae21feeb95d23f22cfda3be0..3954ed194388c6487c6cd0303aea9e1b65a0f8ee 100644 +index 19549dda26c24388bd13a5a2579789e2d1e3ad88..3ca0d08a9e6511b8a96abcf0807a77d52f303a44 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -504,6 +504,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -507,6 +507,32 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { this.getHandle().getCooldowns().addCooldown(CraftMagicNumbers.getItem(material), ticks); } diff --git a/patches/server/0152-Entity-fromMobSpawner.patch b/patches/server/0152-Entity-fromMobSpawner.patch index fa68f366e4..f29310bb65 100644 --- a/patches/server/0152-Entity-fromMobSpawner.patch +++ b/patches/server/0152-Entity-fromMobSpawner.patch @@ -5,7 +5,7 @@ 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 a676fa481ad3e3ac60cf5ba9d86acd61bea329ca..81fd2e443de0b85b340b48e66bc5fb0acad5d60a 100644 +index 55f1886761c7a528f0f1e371de546aff7707e60b..be1b7014a745a3f7ce6cb690c494bbb876e40fcc 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -380,6 +380,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -16,7 +16,7 @@ index a676fa481ad3e3ac60cf5ba9d86acd61bea329ca..81fd2e443de0b85b340b48e66bc5fb0a @javax.annotation.Nullable private org.bukkit.util.Vector origin; @javax.annotation.Nullable -@@ -1954,6 +1955,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -1968,6 +1969,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } nbt.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); } @@ -27,7 +27,7 @@ index a676fa481ad3e3ac60cf5ba9d86acd61bea329ca..81fd2e443de0b85b340b48e66bc5fb0a // Paper end return nbt; } catch (Throwable throwable) { -@@ -2093,6 +2098,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2107,6 +2112,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.originWorld = originWorld; origin = new org.bukkit.util.Vector(originTag.getDouble(0), originTag.getDouble(1), originTag.getDouble(2)); } @@ -49,10 +49,10 @@ index e5b56a85d76d1417dda2d14b1b03850bbb070f4c..5304b0455b070006922e1b5471e9c0ab if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).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 8b4f1ef248d0b13927cbf634a0e2a97eb93c8ae4..5a8e31d3e1078559f17151a8f9a9cbaec58b1942 100644 +index b9599f4f431098d63be98b5175890371103f8813..d3a8c68f5186f69432fb8af8b34b174b04368fc1 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1243,5 +1243,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1260,5 +1260,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { //noinspection ConstantConditions return originVector.toLocation(world); } diff --git a/patches/server/0158-LivingEntity-setKiller.patch b/patches/server/0158-LivingEntity-setKiller.patch index c8eb4f6720..f705351202 100644 --- a/patches/server/0158-LivingEntity-setKiller.patch +++ b/patches/server/0158-LivingEntity-setKiller.patch @@ -5,25 +5,17 @@ Subject: [PATCH] LivingEntity#setKiller diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index b65d44780c7e6e1e2e8724df838d1aa54edcc30a..6455a81fea0de79173419587171b5ed025c30592 100644 +index 4f4ee7071183e7eef918741e38c2bc2e522c72df..a08fd99fb97d8c880c855e6af2a99afcfa8098b5 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.ServerPlayer; - import net.minecraft.world.InteractionHand; - import net.minecraft.world.damagesource.DamageSource; - import net.minecraft.world.effect.MobEffect; -@@ -344,6 +345,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -347,6 +347,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return this.getHandle().lastHurtByPlayer == null ? null : (Player) this.getHandle().lastHurtByPlayer.getBukkitEntity(); } + // Paper start + @Override + public void setKiller(Player killer) { -+ ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); ++ net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); + getHandle().lastHurtByPlayer = entityPlayer; + getHandle().lastHurtByMob = entityPlayer; + getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity diff --git a/patches/server/0165-handle-ServerboundKeepAlivePacket-async.patch b/patches/server/0165-handle-ServerboundKeepAlivePacket-async.patch index e8b1181b67..bb33ae2596 100644 --- a/patches/server/0165-handle-ServerboundKeepAlivePacket-async.patch +++ b/patches/server/0165-handle-ServerboundKeepAlivePacket-async.patch @@ -15,10 +15,10 @@ 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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index fddef0ba2ff963a242e457c4888dc801d1ea6920..95c0f5e429a0dd5075264d91c80f0016e7d330c5 100644 +index 32c4383cfb4cab6329d7046c48daf3050fa027c6..c4f1ded84ec1ed152faeb835a3f50b7e31356655 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3234,14 +3234,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3242,14 +3242,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { diff --git a/patches/server/0169-Add-PlayerArmorChangeEvent.patch b/patches/server/0169-Add-PlayerArmorChangeEvent.patch index 6888a99771..fe028a5f3e 100644 --- a/patches/server/0169-Add-PlayerArmorChangeEvent.patch +++ b/patches/server/0169-Add-PlayerArmorChangeEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add PlayerArmorChangeEvent diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 841149ffcdb2228fac3ac2bdc56c02ed26f219f6..18d29125c19db2ffc3550a843ee9c3974b619f89 100644 +index e711c02279ff48e76038a6281bbe8060e34d900b..2eb002d6231dcb2a4bf4b1eb96ca4ecdb6d4bca2 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1,5 +1,6 @@ @@ -15,7 +15,7 @@ index 841149ffcdb2228fac3ac2bdc56c02ed26f219f6..18d29125c19db2ffc3550a843ee9c397 import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -@@ -2977,6 +2978,13 @@ public abstract class LivingEntity extends Entity { +@@ -2999,6 +3000,13 @@ public abstract class LivingEntity extends Entity { ItemStack itemstack1 = this.getItemBySlot(enumitemslot); if (!ItemStack.matches(itemstack1, itemstack)) { diff --git a/patches/server/0182-Add-ArmorStand-Item-Meta.patch b/patches/server/0182-Add-ArmorStand-Item-Meta.patch index 08077b5138..e8f6a478fb 100644 --- a/patches/server/0182-Add-ArmorStand-Item-Meta.patch +++ b/patches/server/0182-Add-ArmorStand-Item-Meta.patch @@ -13,11 +13,11 @@ 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 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af880d679b1f 100644 +index 4017933f2244fca32cf9d39444f3a4f550e8af01..0c40a4a18cfc6f4ed6473a475f307f5c75ab56c5 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; +@@ -8,9 +8,22 @@ import org.bukkit.Material; + import org.bukkit.configuration.serialization.DelegateDeserialization; @DelegateDeserialization(CraftMetaItem.SerializableMeta.class) -public class CraftMetaArmorStand extends CraftMetaItem { @@ -40,7 +40,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 CompoundTag entityTag; CraftMetaArmorStand(CraftMetaItem meta) { -@@ -22,6 +35,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -21,6 +34,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { } CraftMetaArmorStand armorStand = (CraftMetaArmorStand) meta; @@ -54,7 +54,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 this.entityTag = armorStand.entityTag; } -@@ -30,11 +50,39 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -29,11 +49,39 @@ public class CraftMetaArmorStand extends CraftMetaItem { if (tag.contains(ENTITY_TAG.NBT)) { this.entityTag = tag.getCompound(ENTITY_TAG.NBT).copy(); @@ -94,7 +94,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 } @Override -@@ -57,6 +105,31 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -56,6 +104,31 @@ public class CraftMetaArmorStand extends CraftMetaItem { void applyToItem(CompoundTag tag) { super.applyToItem(tag); @@ -126,7 +126,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 if (this.entityTag != null) { tag.put(ENTITY_TAG.NBT, entityTag); } -@@ -78,7 +151,7 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -72,7 +145,7 @@ public class CraftMetaArmorStand extends CraftMetaItem { } boolean isArmorStandEmpty() { @@ -135,7 +135,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 } @Override -@@ -89,7 +162,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -83,7 +156,13 @@ public class CraftMetaArmorStand extends CraftMetaItem { if (meta instanceof CraftMetaArmorStand) { CraftMetaArmorStand that = (CraftMetaArmorStand) meta; @@ -150,7 +150,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 } return true; } -@@ -104,9 +183,14 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -98,9 +177,14 @@ public class CraftMetaArmorStand extends CraftMetaItem { final int original; int hash = original = super.applyHash(); @@ -168,7 +168,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 return original != hash ? CraftMetaArmorStand.class.hashCode() ^ hash : hash; } -@@ -115,6 +199,28 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -109,6 +193,28 @@ public class CraftMetaArmorStand extends CraftMetaItem { Builder serialize(Builder builder) { super.serialize(builder); @@ -197,7 +197,7 @@ index 1b8be8a7103e09065a405a22d427b9a747fc1a3e..2afedf24e485dd36e95988843c70af88 return builder; } -@@ -128,4 +234,56 @@ public class CraftMetaArmorStand extends CraftMetaItem { +@@ -122,4 +228,56 @@ public class CraftMetaArmorStand extends CraftMetaItem { return clone; } diff --git a/patches/server/0195-Add-openSign-method-to-HumanEntity.patch b/patches/server/0195-Add-openSign-method-to-HumanEntity.patch index e3578a257b..184a121d22 100644 --- a/patches/server/0195-Add-openSign-method-to-HumanEntity.patch +++ b/patches/server/0195-Add-openSign-method-to-HumanEntity.patch @@ -28,10 +28,10 @@ index 911843bf38ab750edd4a63417ba7a9deb6b64cb1..a0950f5902c3719dc31205ec43dca948 // Paper start diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 3954ed194388c6487c6cd0303aea9e1b65a0f8ee..4ff81744b7c9113f57cf1fa89bb943902711b2dc 100644 +index 3ca0d08a9e6511b8a96abcf0807a77d52f303a44..7ea4a2d4e691e0a0a4d9ef3189a29a4a4ca4374b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -614,6 +614,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -617,6 +617,12 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { } } diff --git a/patches/server/0197-Fix-exploit-that-allowed-colored-signs-to-be-created.patch b/patches/server/0197-Fix-exploit-that-allowed-colored-signs-to-be-created.patch index a4b6ac8dfb..1dffd9f0c4 100644 --- a/patches/server/0197-Fix-exploit-that-allowed-colored-signs-to-be-created.patch +++ b/patches/server/0197-Fix-exploit-that-allowed-colored-signs-to-be-created.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix exploit that allowed colored signs to be created diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 40a289c81bffc679d7c77052d4dbea25832d0acf..1dd0c9629d16bfaaebec61cbadf720d14902069d 100644 +index 90bd5c1a010a3a9d24328e5c719053603e206626..116dee1f1f9c489e6f85a8fa3b7f36267109d720 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3248,9 +3248,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3256,9 +3256,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic FilteredText filteredtext = (FilteredText) signText.get(i); if (this.player.isTextFilteringEnabled()) { diff --git a/patches/server/0211-Fix-CraftEntity-hashCode.patch b/patches/server/0211-Fix-CraftEntity-hashCode.patch index 26e3d9d00b..c57f31a94e 100644 --- a/patches/server/0211-Fix-CraftEntity-hashCode.patch +++ b/patches/server/0211-Fix-CraftEntity-hashCode.patch @@ -21,10 +21,10 @@ 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 5a8e31d3e1078559f17151a8f9a9cbaec58b1942..f70cc39d7fdd9308fd328007fcadbaab1780ad5f 100644 +index d3a8c68f5186f69432fb8af8b34b174b04368fc1..2a4f26c5bae66121c5c21d7f1013b3b7753608a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -807,14 +807,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -824,14 +824,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return false; } final CraftEntity other = (CraftEntity) obj; diff --git a/patches/server/0214-Make-shield-blocking-delay-configurable.patch b/patches/server/0214-Make-shield-blocking-delay-configurable.patch index 6eb8e797df..a1c2cf0105 100644 --- a/patches/server/0214-Make-shield-blocking-delay-configurable.patch +++ b/patches/server/0214-Make-shield-blocking-delay-configurable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Make shield blocking delay configurable diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 18d29125c19db2ffc3550a843ee9c3974b619f89..6e1484555eeceacf5a082ad16f1e7539f283fb8a 100644 +index 2eb002d6231dcb2a4bf4b1eb96ca4ecdb6d4bca2..3f2bf9e04b20b74a61d2cf1cadbecd36002d0edc 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3700,12 +3700,24 @@ public abstract class LivingEntity extends Entity { +@@ -3722,12 +3722,24 @@ public abstract class LivingEntity extends Entity { if (this.isUsingItem() && !this.useItem.isEmpty()) { Item item = this.useItem.getItem(); @@ -35,10 +35,10 @@ index 18d29125c19db2ffc3550a843ee9c3974b619f89..6e1484555eeceacf5a082ad16f1e7539 return this.isShiftKeyDown(); } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 0bb285e50d7e327ad811d2d41a241928c5246774..268bbe892a70a829733bea8bf2a8b0f14ac70ad5 100644 +index a08fd99fb97d8c880c855e6af2a99afcfa8098b5..80efb900e37069f4ff39c35d39456959ca24bfe8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -744,5 +744,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -785,5 +785,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { public void setArrowsStuck(int arrows) { getHandle().setArrowCount(arrows); } diff --git a/patches/server/0217-Implement-EntityKnockbackByEntityEvent.patch b/patches/server/0217-Implement-EntityKnockbackByEntityEvent.patch index 0cb31474ad..e1f1a50195 100644 --- a/patches/server/0217-Implement-EntityKnockbackByEntityEvent.patch +++ b/patches/server/0217-Implement-EntityKnockbackByEntityEvent.patch @@ -6,7 +6,7 @@ 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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 6e1484555eeceacf5a082ad16f1e7539f283fb8a..f7a535c35dde266e82369a8cee67c6e58c178731 100644 +index 3f2bf9e04b20b74a61d2cf1cadbecd36002d0edc..84b6c2f73e0f6f3ad3969e7d51f6617e172764ec 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1456,7 +1456,7 @@ public abstract class LivingEntity extends Entity { @@ -56,10 +56,10 @@ index 6e1484555eeceacf5a082ad16f1e7539f283fb8a..f7a535c35dde266e82369a8cee67c6e5 } diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 049a9c4547428d7306d82ed35bcd470ae6f3efc3..95b042aed945adc72238ebeb19369d9b9568dc8d 100644 +index 6d0d194aaababd91a26dffc07f547d60eadd098e..ea836c55bad8b897e0fe0cad6d297b9b52209d69 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1573,7 +1573,7 @@ public abstract class Mob extends LivingEntity { +@@ -1579,7 +1579,7 @@ public abstract class Mob extends LivingEntity { if (flag) { if (f1 > 0.0F && target instanceof LivingEntity) { diff --git a/patches/server/0219-LivingEntity-Hand-Raised-Item-Use-API.patch b/patches/server/0219-LivingEntity-Hand-Raised-Item-Use-API.patch index 11f27800fe..d429d4ce3a 100644 --- a/patches/server/0219-LivingEntity-Hand-Raised-Item-Use-API.patch +++ b/patches/server/0219-LivingEntity-Hand-Raised-Item-Use-API.patch @@ -6,10 +6,10 @@ Subject: [PATCH] LivingEntity Hand Raised/Item Use API How long an entity has raised hands to charge an attack or use an item diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 268bbe892a70a829733bea8bf2a8b0f14ac70ad5..4d4d42fa1bb813b7f977770bfb93e4bc5ba8c2db 100644 +index 80efb900e37069f4ff39c35d39456959ca24bfe8..04829ced2048b07aa4b2dcf98a601d1fdd9431fb 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -754,5 +754,30 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -795,5 +795,30 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { public void setShieldBlockingDelay(int delay) { getHandle().setShieldBlockingDelay(delay); } diff --git a/patches/server/0223-InventoryCloseEvent-Reason-API.patch b/patches/server/0223-InventoryCloseEvent-Reason-API.patch index 57aeaf9d27..6008057827 100644 --- a/patches/server/0223-InventoryCloseEvent-Reason-API.patch +++ b/patches/server/0223-InventoryCloseEvent-Reason-API.patch @@ -75,7 +75,7 @@ index a214916ff80885af262165d5936b8bdf2056cbed..4b9af6ef008a297438bfc583025d235d this.doCloseContainer(); } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1dd0c9629d16bfaaebec61cbadf720d14902069d..1e6f3ea047c7cf94ee420c8c4eb3fe2f31d9b374 100644 +index 116dee1f1f9c489e6f85a8fa3b7f36267109d720..ea2f283634c8794bda3e531a20f39f8a17e3e41c 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -219,6 +219,7 @@ import org.bukkit.event.inventory.ClickType; @@ -86,7 +86,7 @@ index 1dd0c9629d16bfaaebec61cbadf720d14902069d..1e6f3ea047c7cf94ee420c8c4eb3fe2f import org.bukkit.event.inventory.InventoryCreativeEvent; import org.bukkit.event.inventory.InventoryType.SlotType; import org.bukkit.event.inventory.SmithItemEvent; -@@ -2774,10 +2775,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2782,10 +2783,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleContainerClose(ServerboundContainerClosePacket packet) { @@ -144,10 +144,10 @@ index 702a5e524127f9655279a24b54b8d4248dec460e..2520ba136cf17392120f6187a73015f4 this.containerMenu = this.inventoryMenu; } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 4ff81744b7c9113f57cf1fa89bb943902711b2dc..404ed5e8f54d70a50de4232c6ea0f6163b34c2ab 100644 +index 7ea4a2d4e691e0a0a4d9ef3189a29a4a4ca4374b..883b6245f44f3fb82d7678e1092177ca646d484a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -374,7 +374,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -377,7 +377,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { if (((ServerPlayer) this.getHandle()).connection == null) return; if (this.getHandle().containerMenu != this.getHandle().inventoryMenu) { // fire INVENTORY_CLOSE if one already open @@ -156,7 +156,7 @@ index 4ff81744b7c9113f57cf1fa89bb943902711b2dc..404ed5e8f54d70a50de4232c6ea0f616 } ServerPlayer player = (ServerPlayer) this.getHandle(); AbstractContainerMenu container; -@@ -444,8 +444,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { +@@ -447,8 +447,14 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { @Override public void closeInventory() { diff --git a/patches/server/0225-Refresh-player-inventory-when-cancelling-PlayerInter.patch b/patches/server/0225-Refresh-player-inventory-when-cancelling-PlayerInter.patch index 63692348d7..4879efd0d6 100644 --- a/patches/server/0225-Refresh-player-inventory-when-cancelling-PlayerInter.patch +++ b/patches/server/0225-Refresh-player-inventory-when-cancelling-PlayerInter.patch @@ -16,10 +16,10 @@ Refresh the player inventory when PlayerInteractEntityEvent is cancelled to avoid this problem. diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1e6f3ea047c7cf94ee420c8c4eb3fe2f31d9b374..60b29af5e48bed0b1d3749de8313e07b9d82e623 100644 +index ea2f283634c8794bda3e531a20f39f8a17e3e41c..a517eb45cd06c130cbfe6ff6565825c492079287 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2659,6 +2659,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2667,6 +2667,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } if (event.isCancelled()) { diff --git a/patches/server/0227-add-more-information-to-Entity.toString.patch b/patches/server/0227-add-more-information-to-Entity.toString.patch index b441179e4f..6a094a84ad 100644 --- a/patches/server/0227-add-more-information-to-Entity.toString.patch +++ b/patches/server/0227-add-more-information-to-Entity.toString.patch @@ -6,10 +6,10 @@ 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 81fd2e443de0b85b340b48e66bc5fb0acad5d60a..dd00721df47c84629cc5399dd558c531f6cea1cc 100644 +index be1b7014a745a3f7ce6cb690c494bbb876e40fcc..64261427c5f4abf1a4144eb88a6409560667f70b 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2902,7 +2902,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2916,7 +2916,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { public String toString() { String s = this.level == null ? "~NULL~" : this.level.toString(); diff --git a/patches/server/0260-Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/0260-Add-ray-tracing-methods-to-LivingEntity.patch index 5e366ff178..7f12ab44a9 100644 --- a/patches/server/0260-Add-ray-tracing-methods-to-LivingEntity.patch +++ b/patches/server/0260-Add-ray-tracing-methods-to-LivingEntity.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add ray tracing methods to LivingEntity diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 98cd851cdc67ecd9296ec5a8e56141638e108109..4bfccf77d68e8bcfb9b7379bf04a70515b1ebbcd 100644 +index 3e8867c317f7018780f44b62d0bd40fc9fa9ce9f..40d033e8b7c29269a5e194f80c8bccc67836e28d 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -508,6 +508,18 @@ public final class MCUtil { @@ -28,10 +28,10 @@ index 98cd851cdc67ecd9296ec5a8e56141638e108109..4bfccf77d68e8bcfb9b7379bf04a7051 switch (enumDirection) { case DOWN: diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index f7a535c35dde266e82369a8cee67c6e58c178731..75f1d666d7e15cfad3935ba3aff6b68b6b6f992a 100644 +index 84b6c2f73e0f6f3ad3969e7d51f6617e172764ec..a6010e8ddf640a7045f95be04dfce26918d9cd3e 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3721,6 +3721,23 @@ public abstract class LivingEntity extends Entity { +@@ -3743,6 +3743,23 @@ public abstract class LivingEntity extends Entity { } // Paper start @@ -56,10 +56,10 @@ index f7a535c35dde266e82369a8cee67c6e58c178731..75f1d666d7e15cfad3935ba3aff6b68b 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 5c517025576461b426b4a73eea4369f00aeeee41..9c024b46522e2984ed662538302fbac68a77fd86 100644 +index 04829ced2048b07aa4b2dcf98a601d1fdd9431fb..40e777b859151d036ac7ec4e71ed896df4cd689b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -198,6 +198,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -200,6 +200,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return blocks.get(0); } diff --git a/patches/server/0262-Improve-death-events.patch b/patches/server/0262-Improve-death-events.patch index f8f6f3fd1a..9ae53b7aa2 100644 --- a/patches/server/0262-Improve-death-events.patch +++ b/patches/server/0262-Improve-death-events.patch @@ -70,7 +70,7 @@ index 4b9af6ef008a297438bfc583025d235d07d9b780..6d95d572092ad50ffa92c2e1731fd59c } } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 75f1d666d7e15cfad3935ba3aff6b68b6b6f992a..ef1cc74bd09afe7228a78146ff1b8f368874dc70 100644 +index a6010e8ddf640a7045f95be04dfce26918d9cd3e..15af85ec11e40337338696c51083aef91591681b 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -262,6 +262,7 @@ public abstract class LivingEntity extends Entity { @@ -220,10 +220,10 @@ index 75f1d666d7e15cfad3935ba3aff6b68b6b6f992a..ef1cc74bd09afe7228a78146ff1b8f36 // CraftBukkit start public int getExpReward() { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 95b042aed945adc72238ebeb19369d9b9568dc8d..2ffc99730c3d5dbdec63881a1eca07d5fbb1754e 100644 +index ea836c55bad8b897e0fe0cad6d297b9b52209d69..37102e8cdaeb558e80889ff553656f14eaaeb650 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1012,7 +1012,13 @@ public abstract class Mob extends LivingEntity { +@@ -1018,7 +1018,13 @@ public abstract class Mob extends LivingEntity { } this.spawnAtLocation(itemstack); diff --git a/patches/server/0264-Mob-Pathfinding-API.patch b/patches/server/0264-Mob-Pathfinding-API.patch index 49c958043d..099c7b8b06 100644 --- a/patches/server/0264-Mob-Pathfinding-API.patch +++ b/patches/server/0264-Mob-Pathfinding-API.patch @@ -163,10 +163,10 @@ index 4ad2ac8d1e9111933fa58c47442fa1f5e8173fd3..2a335f277bd0e4b8ad0f60d8226eb8aa public Path(List nodes, BlockPos target, boolean reachesTarget) { this.nodes = nodes; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 55c27a3d9b540c4c4d7701bd0cdf167bb7af3dd4..28d6e31bfdf31d3e56024c731b833c4424313307 100644 +index 659ccb6532506b2a8c9feb55dc5aee962f6da795..f3e277dc79124d28e244d59e2a453748610e5ff9 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; +@@ -15,8 +15,11 @@ import org.bukkit.loot.LootTable; public abstract class CraftMob extends CraftLivingEntity implements Mob { public CraftMob(CraftServer server, net.minecraft.world.entity.Mob entity) { super(server, entity); diff --git a/patches/server/0275-Add-LivingEntity-getTargetEntity.patch b/patches/server/0275-Add-LivingEntity-getTargetEntity.patch index 42ee44744d..938905a7ca 100644 --- a/patches/server/0275-Add-LivingEntity-getTargetEntity.patch +++ b/patches/server/0275-Add-LivingEntity-getTargetEntity.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add LivingEntity#getTargetEntity diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index ef1cc74bd09afe7228a78146ff1b8f368874dc70..5dc45412d9251be9c8e516160b1aed9255968bbb 100644 +index 15af85ec11e40337338696c51083aef91591681b..369b2d92dbae896824b2e54cf30f8a607c43d451 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -116,6 +116,7 @@ import net.minecraft.world.level.storage.loot.LootTable; @@ -16,7 +16,7 @@ index ef1cc74bd09afe7228a78146ff1b8f368874dc70..5dc45412d9251be9c8e516160b1aed92 import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.scores.PlayerTeam; -@@ -3784,6 +3785,38 @@ public abstract class LivingEntity extends Entity { +@@ -3806,6 +3807,38 @@ public abstract class LivingEntity extends Entity { return level.clip(raytrace); } @@ -56,7 +56,7 @@ index ef1cc74bd09afe7228a78146ff1b8f368874dc70..5dc45412d9251be9c8e516160b1aed92 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 9c024b46522e2984ed662538302fbac68a77fd86..fb0e0c629d16bc97efc3e91f7ba6fe9e87fc950b 100644 +index 40e777b859151d036ac7ec4e71ed896df4cd689b..fb92f55ae3c8c54edce7565b27fb84f50ee85702 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 @@ @@ -66,7 +66,7 @@ index 9c024b46522e2984ed662538302fbac68a77fd86..fb0e0c629d16bc97efc3e91f7ba6fe9e import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import java.util.ArrayList; -@@ -218,6 +219,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { +@@ -220,6 +221,33 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { new com.destroystokyo.paper.block.TargetBlockInfo(org.bukkit.craftbukkit.block.CraftBlock.at(getHandle().level, ((net.minecraft.world.phys.BlockHitResult)rayTrace).getBlockPos()), net.minecraft.server.MCUtil.toBukkitBlockFace(((net.minecraft.world.phys.BlockHitResult)rayTrace).getDirection())); } diff --git a/patches/server/0276-Add-sun-related-API.patch b/patches/server/0276-Add-sun-related-API.patch index e19d11236a..483f2ac212 100644 --- a/patches/server/0276-Add-sun-related-API.patch +++ b/patches/server/0276-Add-sun-related-API.patch @@ -23,10 +23,10 @@ index ef0bff86d3f5f0c404f66b3e2e0a4976006909ee..3aa2e80e7d30d8824fd7f009282adfd8 public long getGameTime() { return world.levelData.getGameTime(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 28d6e31bfdf31d3e56024c731b833c4424313307..881bb11507eafe87522ad4131ea7859f42918b3e 100644 +index f3e277dc79124d28e244d59e2a453748610e5ff9..206b4d187a486e2c8a3a36eacb2d33f9d2555fe8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -76,4 +76,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { +@@ -85,4 +85,11 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { public long getSeed() { return this.getHandle().lootTableSeed; } diff --git a/patches/server/0294-force-entity-dismount-during-teleportation.patch b/patches/server/0294-force-entity-dismount-during-teleportation.patch index 8f4bc21a21..c6c4c9f4f7 100644 --- a/patches/server/0294-force-entity-dismount-during-teleportation.patch +++ b/patches/server/0294-force-entity-dismount-during-teleportation.patch @@ -41,10 +41,10 @@ index 4c35529c7ed67c2432ac67e7d8ffe295892757ff..f91da5bc234a8f1c120261823a1a4e42 if (entity1 != entity && this.connection != null) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 29c71c951ce7114dfb24f0c99c3bf8c2a6f6144f..cb2b47df90386ffb1305062f3f34a63549151e8b 100644 +index 0282b575d4ff68a306053f86b47908dd44dc54ed..424eed2daa3a9574cf1179a7f970f1565ea5fc71 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2315,11 +2315,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2329,11 +2329,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } public void removeVehicle() { @@ -62,7 +62,7 @@ index 29c71c951ce7114dfb24f0c99c3bf8c2a6f6144f..cb2b47df90386ffb1305062f3f34a635 } } -@@ -2382,7 +2387,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2396,7 +2401,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return true; // CraftBukkit } @@ -74,7 +74,7 @@ index 29c71c951ce7114dfb24f0c99c3bf8c2a6f6144f..cb2b47df90386ffb1305062f3f34a635 if (entity.getVehicle() == this) { throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); } else { -@@ -2392,7 +2400,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2406,7 +2414,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { VehicleExitEvent event = new VehicleExitEvent( (Vehicle) this.getBukkitEntity(), @@ -83,7 +83,7 @@ index 29c71c951ce7114dfb24f0c99c3bf8c2a6f6144f..cb2b47df90386ffb1305062f3f34a635 ); // Suppress during worldgen if (this.valid) { -@@ -2406,7 +2414,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2420,7 +2428,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } // CraftBukkit end // Spigot start @@ -93,10 +93,10 @@ index 29c71c951ce7114dfb24f0c99c3bf8c2a6f6144f..cb2b47df90386ffb1305062f3f34a635 if (this.valid) { Bukkit.getPluginManager().callEvent(event); diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 5dc45412d9251be9c8e516160b1aed9255968bbb..3aebd9999b1e0dba3405117a8223c99b35c7db12 100644 +index 369b2d92dbae896824b2e54cf30f8a607c43d451..1b907ca310ee217c9496f1b9a63d9cc694c177f0 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3382,9 +3382,15 @@ public abstract class LivingEntity extends Entity { +@@ -3404,9 +3404,15 @@ public abstract class LivingEntity extends Entity { @Override public void stopRiding() { diff --git a/patches/server/0306-Limit-Client-Sign-length-more.patch b/patches/server/0306-Limit-Client-Sign-length-more.patch index 998864a21d..f357b73ac8 100644 --- a/patches/server/0306-Limit-Client-Sign-length-more.patch +++ b/patches/server/0306-Limit-Client-Sign-length-more.patch @@ -22,7 +22,7 @@ it only impacts data sent from the client. Set -DPaper.maxSignLength=XX to change limit or -1 to disable diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c45910b192bcc6171a2ae98a525fdc7008c2185f..74d77d3dd5a7f05242d2105c8cfbe10b1a054a1f 100644 +index ac5f70ee86cc5a01b046e8e610434742448e3919..1c1ba459535296e029a8d39a5f78d60eb29cdb71 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -296,6 +296,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @@ -33,7 +33,7 @@ index c45910b192bcc6171a2ae98a525fdc7008c2185f..74d77d3dd5a7f05242d2105c8cfbe10b public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { this.lastChatTimeStamp = new AtomicReference(Instant.EPOCH); -@@ -3288,7 +3289,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3296,7 +3297,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleSignUpdate(ServerboundSignUpdatePacket packet) { diff --git a/patches/server/0312-Entity-getEntitySpawnReason.patch b/patches/server/0312-Entity-getEntitySpawnReason.patch index ba1c9d2221..fa232bad2e 100644 --- a/patches/server/0312-Entity-getEntitySpawnReason.patch +++ b/patches/server/0312-Entity-getEntitySpawnReason.patch @@ -35,7 +35,7 @@ index 3cb257544d95e82f8de2d693f510c15980aa27c8..895d087fbdde840bd6b96b6c8d231fc9 }); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index cb2b47df90386ffb1305062f3f34a63549151e8b..482efc1c544cdd846f7330f4be43cc3e6eb397fc 100644 +index 424eed2daa3a9574cf1179a7f970f1565ea5fc71..a2a607e6ae867a4cabe1c2280154c99e93d0e7e9 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -74,6 +74,8 @@ import net.minecraft.world.InteractionHand; @@ -55,7 +55,7 @@ index cb2b47df90386ffb1305062f3f34a63549151e8b..482efc1c544cdd846f7330f4be43cc3e public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper private CraftEntity bukkitEntity; -@@ -1957,6 +1960,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -1971,6 +1974,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } nbt.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ())); } @@ -65,7 +65,7 @@ index cb2b47df90386ffb1305062f3f34a63549151e8b..482efc1c544cdd846f7330f4be43cc3e // Save entity's from mob spawner status if (spawnedViaMobSpawner) { nbt.putBoolean("Paper.FromMobSpawner", true); -@@ -2102,6 +2108,26 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2116,6 +2122,26 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status @@ -105,10 +105,10 @@ index 0be0c7a323277093a6f8e476048eb9ee8712cbc9..c7e97263eee005fd673882e11c436542 // Spigot Start if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index f70cc39d7fdd9308fd328007fcadbaab1780ad5f..cd958bc3c00f53ebaf9b3ae39564d3abb6c819a1 100644 +index 2a4f26c5bae66121c5c21d7f1013b3b7753608a4..3bda8128c2956d817677e28ff87c9c5ed61c8bd2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1249,5 +1249,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1266,5 +1266,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public boolean fromMobSpawner() { return getHandle().spawnedViaMobSpawner; } diff --git a/patches/server/0313-Update-entity-Metadata-for-all-tracked-players.patch b/patches/server/0313-Update-entity-Metadata-for-all-tracked-players.patch index a3b8d1788e..3ddc7c5d06 100644 --- a/patches/server/0313-Update-entity-Metadata-for-all-tracked-players.patch +++ b/patches/server/0313-Update-entity-Metadata-for-all-tracked-players.patch @@ -22,10 +22,10 @@ index d6f34adbdf45bbef4a39e629dd7cb6d7fcb5db0f..7881176a900daa3306c691454f688c1f this.broadcast.accept(packet); if (this.entity instanceof ServerPlayer) { diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 74d77d3dd5a7f05242d2105c8cfbe10b1a054a1f..a93297667485c27721b3ef45042ea0d2466d0a61 100644 +index 1c1ba459535296e029a8d39a5f78d60eb29cdb71..9f60c0786b4676726036ca56906663698d26aaea 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2724,7 +2724,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2732,7 +2732,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) { // Refresh the current entity metadata diff --git a/patches/server/0335-Prevent-consuming-the-wrong-itemstack.patch b/patches/server/0335-Prevent-consuming-the-wrong-itemstack.patch index 78e9ce8ae0..6074727c03 100644 --- a/patches/server/0335-Prevent-consuming-the-wrong-itemstack.patch +++ b/patches/server/0335-Prevent-consuming-the-wrong-itemstack.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Prevent consuming the wrong itemstack diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 3aebd9999b1e0dba3405117a8223c99b35c7db12..590b8af46f8e060aa568dde50025b4f4c6bb162e 100644 +index 1b907ca310ee217c9496f1b9a63d9cc694c177f0..df35685a016376fa056a8ecbfda2c01b38350b3c 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3599,9 +3599,14 @@ public abstract class LivingEntity extends Entity { +@@ -3621,9 +3621,14 @@ public abstract class LivingEntity extends Entity { } public void startUsingItem(InteractionHand hand) { @@ -24,7 +24,7 @@ index 3aebd9999b1e0dba3405117a8223c99b35c7db12..590b8af46f8e060aa568dde50025b4f4 this.useItem = itemstack; this.useItemRemaining = itemstack.getUseDuration(); if (!this.level.isClientSide) { -@@ -3681,6 +3686,7 @@ public abstract class LivingEntity extends Entity { +@@ -3703,6 +3708,7 @@ public abstract class LivingEntity extends Entity { this.releaseUsingItem(); } else { if (!this.useItem.isEmpty() && this.isUsingItem()) { @@ -32,7 +32,7 @@ index 3aebd9999b1e0dba3405117a8223c99b35c7db12..590b8af46f8e060aa568dde50025b4f4 this.triggerItemUseEffects(this.useItem, 16); // CraftBukkit start - fire PlayerItemConsumeEvent ItemStack itemstack; -@@ -3715,8 +3721,8 @@ public abstract class LivingEntity extends Entity { +@@ -3737,8 +3743,8 @@ public abstract class LivingEntity extends Entity { } this.stopUsingItem(); diff --git a/patches/server/0336-Dont-send-unnecessary-sign-update.patch b/patches/server/0336-Dont-send-unnecessary-sign-update.patch index bfb40d62c7..4d63dfa58a 100644 --- a/patches/server/0336-Dont-send-unnecessary-sign-update.patch +++ b/patches/server/0336-Dont-send-unnecessary-sign-update.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Dont send unnecessary sign update diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 133d76c6a011d4c3f7ad037e535e8faa27f89874..ccd3ed84db4be3ee7b5d34de59a727c0c0ea677e 100644 +index 4ebae3246e3866bbdfa17e43bd0252d637885052..3af9f2d86cf2a9566e22865689101245647d05a5 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3333,6 +3333,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3341,6 +3341,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!tileentitysign.isEditable() || !this.player.getUUID().equals(tileentitysign.getPlayerWhoMayEdit())) { ServerGamePacketListenerImpl.LOGGER.warn("Player {} just tried to change non-editable sign", this.player.getName().getString()); diff --git a/patches/server/0338-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch b/patches/server/0338-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch deleted file mode 100644 index e1945e2b80..0000000000 --- a/patches/server/0338-Fix-AssertionError-when-player-hand-set-to-empty-typ.patch +++ /dev/null @@ -1,23 +0,0 @@ -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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 9d3a88ed093c5eda7a11133ebc97226c544fbd18..0df93b204ddf55a2a3b8af33d6a3273697eea91e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1870,6 +1870,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 - return; - } -+ // Paper start -+ itemstack = this.player.getItemInHand(enumhand); -+ if (itemstack.isEmpty()) return; -+ // Paper end - InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); - - if (enuminteractionresult.shouldSwing()) { diff --git a/patches/server/0338-Flat-bedrock-generator-settings.patch b/patches/server/0338-Flat-bedrock-generator-settings.patch new file mode 100644 index 0000000000..43ca0facd8 --- /dev/null +++ b/patches/server/0338-Flat-bedrock-generator-settings.patch @@ -0,0 +1,196 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Byteflux +Date: Wed, 2 Mar 2016 02:17:54 -0600 +Subject: [PATCH] Flat bedrock generator settings + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java b/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java +index 06e1774dfbb667aca69bc30c9675ed472cb5728c..2f9ba60c0196c3722fbf5a44fbbcf22a85d438db 100644 +--- a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java ++++ b/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java +@@ -53,6 +53,66 @@ public class SurfaceRuleData { + return overworldLike(true, false, true); + } + ++ // Paper start ++ // Taken from SurfaceRules$VerticalGradientConditionSource ++ // isRoof = true if roof, false if floor ++ public record PaperBedrockConditionSource(net.minecraft.resources.ResourceLocation randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove, boolean isRoof) implements SurfaceRules.ConditionSource { ++ ++ public static final net.minecraft.util.KeyDispatchDataCodec CODEC = net.minecraft.util.KeyDispatchDataCodec.of(com.mojang.serialization.codecs.RecordCodecBuilder.mapCodec((instance) -> { ++ return instance.group( ++ net.minecraft.resources.ResourceLocation.CODEC.fieldOf("random_name").forGetter(PaperBedrockConditionSource::randomName), ++ VerticalAnchor.CODEC.fieldOf("true_at_and_below").forGetter(PaperBedrockConditionSource::trueAtAndBelow), ++ VerticalAnchor.CODEC.fieldOf("false_at_and_above").forGetter(PaperBedrockConditionSource::falseAtAndAbove), ++ com.mojang.serialization.Codec.BOOL.fieldOf("roof").forGetter(PaperBedrockConditionSource::isRoof) ++ ).apply(instance, PaperBedrockConditionSource::new); ++ })); ++ ++ public PaperBedrockConditionSource(String randomName, net.minecraft.world.level.levelgen.VerticalAnchor trueAtAndBelow, net.minecraft.world.level.levelgen.VerticalAnchor falseAtAndAbove, boolean invert) { ++ this(new net.minecraft.resources.ResourceLocation(randomName), trueAtAndBelow, falseAtAndAbove, invert); ++ } ++ ++ @Override ++ public net.minecraft.util.KeyDispatchDataCodeccodec() { ++ return CODEC; ++ } ++ ++ @Override ++ public SurfaceRules.Condition apply(SurfaceRules.Context context) { ++ boolean hasFlatBedrock = context.context.getWorld().paperConfig().environment.generateFlatBedrock; ++ int trueAtY = this.trueAtAndBelow().resolveY(context.context); ++ int falseAtY = this.falseAtAndAbove().resolveY(context.context); ++ ++ int y = isRoof ? Math.max(falseAtY, trueAtY) - 1 : Math.min(falseAtY, trueAtY) ; ++ final int i = hasFlatBedrock ? y : trueAtY; ++ final int j = hasFlatBedrock ? y : falseAtY; ++ // TODO access transformer for randomState ++ final net.minecraft.world.level.levelgen.PositionalRandomFactory positionalRandomFactory = context.randomState.getOrCreateRandomFactory(this.randomName()); ++ ++ class VerticalGradientCondition extends SurfaceRules.LazyYCondition { ++ VerticalGradientCondition(SurfaceRules.Context context) { ++ super(context); ++ } ++ ++ @Override ++ protected boolean compute() { ++ int y = this.context.blockY; ++ if (y <= i) { ++ return true; ++ } else if (y >= j) { ++ return false; ++ } else { ++ double d = net.minecraft.util.Mth.map((double) y, (double) i, (double) j, 1.0D, 0.0D); ++ net.minecraft.util.RandomSource randomSource = positionalRandomFactory.at(this.context.blockX, i, this.context.blockZ); ++ return (double) randomSource.nextFloat() < d; ++ } ++ } ++ } ++ ++ return new VerticalGradientCondition(context); ++ } ++ } ++ // Paper end ++ + public static SurfaceRules.RuleSource overworldLike(boolean surface, boolean bedrockRoof, boolean bedrockFloor) { + SurfaceRules.ConditionSource conditionSource = SurfaceRules.yBlockCheck(VerticalAnchor.absolute(97), 2); + SurfaceRules.ConditionSource conditionSource2 = SurfaceRules.yBlockCheck(VerticalAnchor.absolute(256), 0); +@@ -83,11 +143,11 @@ public class SurfaceRuleData { + SurfaceRules.RuleSource ruleSource9 = SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WOODED_BADLANDS), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource16, COARSE_DIRT), SurfaceRules.ifTrue(conditionSource17, COARSE_DIRT), SurfaceRules.ifTrue(conditionSource18, COARSE_DIRT), ruleSource))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SWAMP), SurfaceRules.ifTrue(conditionSource6, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource7), SurfaceRules.ifTrue(SurfaceRules.noiseCondition(Noises.SWAMP, 0.0D), WATER)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.MANGROVE_SWAMP), SurfaceRules.ifTrue(conditionSource5, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource7), SurfaceRules.ifTrue(SurfaceRules.noiseCondition(Noises.SWAMP, 0.0D), WATER)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BADLANDS, Biomes.ERODED_BADLANDS, Biomes.WOODED_BADLANDS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, ORANGE_TERRACOTTA), SurfaceRules.ifTrue(conditionSource4, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource16, TERRACOTTA), SurfaceRules.ifTrue(conditionSource17, TERRACOTTA), SurfaceRules.ifTrue(conditionSource18, TERRACOTTA), SurfaceRules.bandlands())), SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, RED_SANDSTONE), RED_SAND)), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource11), ORANGE_TERRACOTTA), SurfaceRules.ifTrue(conditionSource10, WHITE_TERRACOTTA), ruleSource3)), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource7, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource4), ORANGE_TERRACOTTA)), SurfaceRules.bandlands())), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource10, WHITE_TERRACOTTA)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SurfaceRules.ifTrue(conditionSource11, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource9, AIR), SurfaceRules.ifTrue(SurfaceRules.temperature(), ICE), WATER))), ruleSource8))), SurfaceRules.ifTrue(conditionSource10, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource12, SurfaceRules.ifTrue(conditionSource11, WATER))), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, ruleSource7), SurfaceRules.ifTrue(conditionSource14, SurfaceRules.ifTrue(SurfaceRules.DEEP_UNDER_FLOOR, SANDSTONE)), SurfaceRules.ifTrue(conditionSource15, SurfaceRules.ifTrue(SurfaceRules.VERY_DEEP_UNDER_FLOOR, SANDSTONE)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.FROZEN_PEAKS, Biomes.JAGGED_PEAKS), STONE), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARM_OCEAN, Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN), ruleSource2), ruleSource3))); + ImmutableList.Builder builder = ImmutableList.builder(); + if (bedrockRoof) { +- builder.add(SurfaceRules.ifTrue(SurfaceRules.not(SurfaceRules.verticalGradient("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top())), BEDROCK)); ++ builder.add(SurfaceRules.ifTrue(SurfaceRules.not(new PaperBedrockConditionSource("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top(), true)), BEDROCK)); // Paper + } + + if (bedrockFloor) { +- builder.add(SurfaceRules.ifTrue(SurfaceRules.verticalGradient("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5)), BEDROCK)); ++ builder.add(SurfaceRules.ifTrue(new PaperBedrockConditionSource("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5), false), BEDROCK)); // Paper + } + + SurfaceRules.RuleSource ruleSource10 = SurfaceRules.ifTrue(SurfaceRules.abovePreliminarySurface(), ruleSource9); +@@ -112,7 +172,7 @@ public class SurfaceRuleData { + SurfaceRules.ConditionSource conditionSource11 = SurfaceRules.noiseCondition(Noises.NETHER_WART, 1.17D); + SurfaceRules.ConditionSource conditionSource12 = SurfaceRules.noiseCondition(Noises.NETHER_STATE_SELECTOR, 0.0D); + SurfaceRules.RuleSource ruleSource = SurfaceRules.ifTrue(conditionSource9, SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, GRAVEL))); +- return SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.verticalGradient("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5)), BEDROCK), SurfaceRules.ifTrue(SurfaceRules.not(SurfaceRules.verticalGradient("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top())), BEDROCK), SurfaceRules.ifTrue(conditionSource5, NETHERRACK), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BASALT_DELTAS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, BASALT), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, BASALT), BLACKSTONE)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SOUL_SAND_VALLEY), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource2), SurfaceRules.ifTrue(conditionSource6, LAVA)), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARPED_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, WARPED_WART_BLOCK), WARPED_NYLIUM)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.CRIMSON_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, NETHER_WART_BLOCK), CRIMSON_NYLIUM)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.NETHER_WASTES), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource7, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, SOUL_SAND))), NETHERRACK))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource, SurfaceRules.ifTrue(conditionSource4, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, GRAVEL), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), GRAVEL)))))))), NETHERRACK); ++ return SurfaceRules.sequence(SurfaceRules.ifTrue(new PaperBedrockConditionSource("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5), false), BEDROCK), SurfaceRules.ifTrue(SurfaceRules.not(new PaperBedrockConditionSource("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top(), true)), BEDROCK), SurfaceRules.ifTrue(conditionSource5, NETHERRACK), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BASALT_DELTAS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, BASALT), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, BASALT), BLACKSTONE)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SOUL_SAND_VALLEY), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource2), SurfaceRules.ifTrue(conditionSource6, LAVA)), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARPED_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, WARPED_WART_BLOCK), WARPED_NYLIUM)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.CRIMSON_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, NETHER_WART_BLOCK), CRIMSON_NYLIUM)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.NETHER_WASTES), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource7, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, SOUL_SAND))), NETHERRACK))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource, SurfaceRules.ifTrue(conditionSource4, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, GRAVEL), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), GRAVEL)))))))), NETHERRACK); + } + + public static SurfaceRules.RuleSource end() { +diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java +index c5822637e48fad4ca4e8cf210431b5eafbf5abb1..0ece775ca7e63184f79fcdd1aa7ed5c16fc9bc9e 100644 +--- a/src/main/java/net/minecraft/server/Bootstrap.java ++++ b/src/main/java/net/minecraft/server/Bootstrap.java +@@ -75,6 +75,7 @@ public class Bootstrap { + EntitySelectorOptions.bootStrap(); + DispenseItemBehavior.bootStrap(); + CauldronInteraction.bootStrap(); ++ Registry.register(net.minecraft.core.Registry.CONDITION, new net.minecraft.resources.ResourceLocation("paper", "bedrock_condition_source"), net.minecraft.data.worldgen.SurfaceRuleData.PaperBedrockConditionSource.CODEC.codec()); // Paper - register custom flat bedrock thing. TODO is this the best place to do this? + Registry.freezeBuiltins(); + Bootstrap.wrapStreams(); + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +index e66e52da84408eb705d23504e500bd8a98322b0e..575efe82a7219e256afd8362984eb26795445119 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +@@ -210,7 +210,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + @Override + public void buildSurface(WorldGenRegion region, StructureManager structures, RandomState noiseConfig, ChunkAccess chunk) { + if (!SharedConstants.debugVoidTerrain(chunk.getPos())) { +- WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region); ++ WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region, region.getMinecraftWorld()); // Paper + + this.buildSurface(chunk, worldgenerationcontext, noiseConfig, structures, region.getBiomeManager(), region.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), Blender.of(region)); + } +@@ -238,7 +238,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { + return this.createNoiseChunk(ichunkaccess1, structureAccessor, Blender.of(chunkRegion), noiseConfig); + }); + Aquifer aquifer = noisechunk.aquifer(); +- CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule()); ++ CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule(), chunkRegion.getMinecraftWorld()); // Paper + CarvingMask carvingmask = ((ProtoChunk) chunk).getOrCreateCarvingMask(carverStep); + + for (int j = -8; j <= 8; ++j) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java +index b99283c31193e2110f6e3f39c23dbfc2442bab2b..1c9d9ecdafb2bd04348045ba0404da052dcd6437 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java +@@ -6,10 +6,13 @@ import net.minecraft.world.level.chunk.ChunkGenerator; + public class WorldGenerationContext { + private final int minY; + private final int height; ++ private final @javax.annotation.Nullable net.minecraft.world.level.Level level; // Paper + +- public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) { ++ public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) { this(generator, world, null); } // Paper ++ public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world, @org.jetbrains.annotations.Nullable net.minecraft.world.level.Level level) { // Paper + this.minY = Math.max(world.getMinBuildHeight(), generator.getMinY()); + this.height = Math.min(world.getHeight(), generator.getGenDepth()); ++ this.level = level; // Paper + } + + public int getMinGenY() { +@@ -19,4 +22,13 @@ public class WorldGenerationContext { + public int getGenDepth() { + return this.height; + } ++ ++ // Paper start ++ public net.minecraft.world.level.Level getWorld() { ++ if (this.level == null) { ++ throw new NullPointerException("WorldGenerationContext was initialized without a Level, but WorldGenerationContext#getWorld was called"); ++ } ++ return this.level; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java +index a745458ea3581ea91a68c863e3fd0a0292d73a61..f84ee8afe95f912a972e37fbae7a06ecdd3aba06 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java +@@ -21,8 +21,8 @@ public class CarvingContext extends WorldGenerationContext { + private final RandomState randomState; + private final SurfaceRules.RuleSource surfaceRule; + +- public CarvingContext(NoiseBasedChunkGenerator noiseChunkGenerator, RegistryAccess registryManager, LevelHeightAccessor heightLimitView, NoiseChunk chunkNoiseSampler, RandomState noiseConfig, SurfaceRules.RuleSource materialRule) { +- super(noiseChunkGenerator, heightLimitView); ++ public CarvingContext(NoiseBasedChunkGenerator noiseChunkGenerator, RegistryAccess registryManager, LevelHeightAccessor heightLimitView, NoiseChunk chunkNoiseSampler, RandomState noiseConfig, SurfaceRules.RuleSource materialRule, @javax.annotation.Nullable net.minecraft.world.level.Level level) { // Paper ++ super(noiseChunkGenerator, heightLimitView, level); // Paper + this.registryAccess = registryManager; + this.noiseChunk = chunkNoiseSampler; + this.randomState = noiseConfig; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java +index 640c2683c842655bbaee8f293f1c2613ef44844e..53d818b0cc602f827d0b907e293515f6810c6792 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java +@@ -18,7 +18,7 @@ public class PlacementContext extends WorldGenerationContext { + private final Optional topFeature; + + public PlacementContext(WorldGenLevel world, ChunkGenerator generator, Optional placedFeature) { +- super(generator, world); ++ super(generator, world, world.getLevel()); // Paper + this.level = world; + this.generator = generator; + this.topFeature = placedFeature; diff --git a/patches/server/0339-Flat-bedrock-generator-settings.patch b/patches/server/0339-Flat-bedrock-generator-settings.patch deleted file mode 100644 index 43ca0facd8..0000000000 --- a/patches/server/0339-Flat-bedrock-generator-settings.patch +++ /dev/null @@ -1,196 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Byteflux -Date: Wed, 2 Mar 2016 02:17:54 -0600 -Subject: [PATCH] Flat bedrock generator settings - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java b/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java -index 06e1774dfbb667aca69bc30c9675ed472cb5728c..2f9ba60c0196c3722fbf5a44fbbcf22a85d438db 100644 ---- a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java -+++ b/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java -@@ -53,6 +53,66 @@ public class SurfaceRuleData { - return overworldLike(true, false, true); - } - -+ // Paper start -+ // Taken from SurfaceRules$VerticalGradientConditionSource -+ // isRoof = true if roof, false if floor -+ public record PaperBedrockConditionSource(net.minecraft.resources.ResourceLocation randomName, VerticalAnchor trueAtAndBelow, VerticalAnchor falseAtAndAbove, boolean isRoof) implements SurfaceRules.ConditionSource { -+ -+ public static final net.minecraft.util.KeyDispatchDataCodec CODEC = net.minecraft.util.KeyDispatchDataCodec.of(com.mojang.serialization.codecs.RecordCodecBuilder.mapCodec((instance) -> { -+ return instance.group( -+ net.minecraft.resources.ResourceLocation.CODEC.fieldOf("random_name").forGetter(PaperBedrockConditionSource::randomName), -+ VerticalAnchor.CODEC.fieldOf("true_at_and_below").forGetter(PaperBedrockConditionSource::trueAtAndBelow), -+ VerticalAnchor.CODEC.fieldOf("false_at_and_above").forGetter(PaperBedrockConditionSource::falseAtAndAbove), -+ com.mojang.serialization.Codec.BOOL.fieldOf("roof").forGetter(PaperBedrockConditionSource::isRoof) -+ ).apply(instance, PaperBedrockConditionSource::new); -+ })); -+ -+ public PaperBedrockConditionSource(String randomName, net.minecraft.world.level.levelgen.VerticalAnchor trueAtAndBelow, net.minecraft.world.level.levelgen.VerticalAnchor falseAtAndAbove, boolean invert) { -+ this(new net.minecraft.resources.ResourceLocation(randomName), trueAtAndBelow, falseAtAndAbove, invert); -+ } -+ -+ @Override -+ public net.minecraft.util.KeyDispatchDataCodeccodec() { -+ return CODEC; -+ } -+ -+ @Override -+ public SurfaceRules.Condition apply(SurfaceRules.Context context) { -+ boolean hasFlatBedrock = context.context.getWorld().paperConfig().environment.generateFlatBedrock; -+ int trueAtY = this.trueAtAndBelow().resolveY(context.context); -+ int falseAtY = this.falseAtAndAbove().resolveY(context.context); -+ -+ int y = isRoof ? Math.max(falseAtY, trueAtY) - 1 : Math.min(falseAtY, trueAtY) ; -+ final int i = hasFlatBedrock ? y : trueAtY; -+ final int j = hasFlatBedrock ? y : falseAtY; -+ // TODO access transformer for randomState -+ final net.minecraft.world.level.levelgen.PositionalRandomFactory positionalRandomFactory = context.randomState.getOrCreateRandomFactory(this.randomName()); -+ -+ class VerticalGradientCondition extends SurfaceRules.LazyYCondition { -+ VerticalGradientCondition(SurfaceRules.Context context) { -+ super(context); -+ } -+ -+ @Override -+ protected boolean compute() { -+ int y = this.context.blockY; -+ if (y <= i) { -+ return true; -+ } else if (y >= j) { -+ return false; -+ } else { -+ double d = net.minecraft.util.Mth.map((double) y, (double) i, (double) j, 1.0D, 0.0D); -+ net.minecraft.util.RandomSource randomSource = positionalRandomFactory.at(this.context.blockX, i, this.context.blockZ); -+ return (double) randomSource.nextFloat() < d; -+ } -+ } -+ } -+ -+ return new VerticalGradientCondition(context); -+ } -+ } -+ // Paper end -+ - public static SurfaceRules.RuleSource overworldLike(boolean surface, boolean bedrockRoof, boolean bedrockFloor) { - SurfaceRules.ConditionSource conditionSource = SurfaceRules.yBlockCheck(VerticalAnchor.absolute(97), 2); - SurfaceRules.ConditionSource conditionSource2 = SurfaceRules.yBlockCheck(VerticalAnchor.absolute(256), 0); -@@ -83,11 +143,11 @@ public class SurfaceRuleData { - SurfaceRules.RuleSource ruleSource9 = SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WOODED_BADLANDS), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource16, COARSE_DIRT), SurfaceRules.ifTrue(conditionSource17, COARSE_DIRT), SurfaceRules.ifTrue(conditionSource18, COARSE_DIRT), ruleSource))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SWAMP), SurfaceRules.ifTrue(conditionSource6, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource7), SurfaceRules.ifTrue(SurfaceRules.noiseCondition(Noises.SWAMP, 0.0D), WATER)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.MANGROVE_SWAMP), SurfaceRules.ifTrue(conditionSource5, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource7), SurfaceRules.ifTrue(SurfaceRules.noiseCondition(Noises.SWAMP, 0.0D), WATER)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BADLANDS, Biomes.ERODED_BADLANDS, Biomes.WOODED_BADLANDS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, ORANGE_TERRACOTTA), SurfaceRules.ifTrue(conditionSource4, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource16, TERRACOTTA), SurfaceRules.ifTrue(conditionSource17, TERRACOTTA), SurfaceRules.ifTrue(conditionSource18, TERRACOTTA), SurfaceRules.bandlands())), SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, RED_SANDSTONE), RED_SAND)), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource11), ORANGE_TERRACOTTA), SurfaceRules.ifTrue(conditionSource10, WHITE_TERRACOTTA), ruleSource3)), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource7, SurfaceRules.ifTrue(SurfaceRules.not(conditionSource4), ORANGE_TERRACOTTA)), SurfaceRules.bandlands())), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource10, WHITE_TERRACOTTA)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SurfaceRules.ifTrue(conditionSource11, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource9, AIR), SurfaceRules.ifTrue(SurfaceRules.temperature(), ICE), WATER))), ruleSource8))), SurfaceRules.ifTrue(conditionSource10, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource12, SurfaceRules.ifTrue(conditionSource11, WATER))), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, ruleSource7), SurfaceRules.ifTrue(conditionSource14, SurfaceRules.ifTrue(SurfaceRules.DEEP_UNDER_FLOOR, SANDSTONE)), SurfaceRules.ifTrue(conditionSource15, SurfaceRules.ifTrue(SurfaceRules.VERY_DEEP_UNDER_FLOOR, SANDSTONE)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.FROZEN_PEAKS, Biomes.JAGGED_PEAKS), STONE), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARM_OCEAN, Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN), ruleSource2), ruleSource3))); - ImmutableList.Builder builder = ImmutableList.builder(); - if (bedrockRoof) { -- builder.add(SurfaceRules.ifTrue(SurfaceRules.not(SurfaceRules.verticalGradient("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top())), BEDROCK)); -+ builder.add(SurfaceRules.ifTrue(SurfaceRules.not(new PaperBedrockConditionSource("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top(), true)), BEDROCK)); // Paper - } - - if (bedrockFloor) { -- builder.add(SurfaceRules.ifTrue(SurfaceRules.verticalGradient("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5)), BEDROCK)); -+ builder.add(SurfaceRules.ifTrue(new PaperBedrockConditionSource("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5), false), BEDROCK)); // Paper - } - - SurfaceRules.RuleSource ruleSource10 = SurfaceRules.ifTrue(SurfaceRules.abovePreliminarySurface(), ruleSource9); -@@ -112,7 +172,7 @@ public class SurfaceRuleData { - SurfaceRules.ConditionSource conditionSource11 = SurfaceRules.noiseCondition(Noises.NETHER_WART, 1.17D); - SurfaceRules.ConditionSource conditionSource12 = SurfaceRules.noiseCondition(Noises.NETHER_STATE_SELECTOR, 0.0D); - SurfaceRules.RuleSource ruleSource = SurfaceRules.ifTrue(conditionSource9, SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, GRAVEL))); -- return SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.verticalGradient("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5)), BEDROCK), SurfaceRules.ifTrue(SurfaceRules.not(SurfaceRules.verticalGradient("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top())), BEDROCK), SurfaceRules.ifTrue(conditionSource5, NETHERRACK), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BASALT_DELTAS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, BASALT), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, BASALT), BLACKSTONE)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SOUL_SAND_VALLEY), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource2), SurfaceRules.ifTrue(conditionSource6, LAVA)), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARPED_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, WARPED_WART_BLOCK), WARPED_NYLIUM)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.CRIMSON_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, NETHER_WART_BLOCK), CRIMSON_NYLIUM)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.NETHER_WASTES), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource7, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, SOUL_SAND))), NETHERRACK))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource, SurfaceRules.ifTrue(conditionSource4, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, GRAVEL), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), GRAVEL)))))))), NETHERRACK); -+ return SurfaceRules.sequence(SurfaceRules.ifTrue(new PaperBedrockConditionSource("bedrock_floor", VerticalAnchor.bottom(), VerticalAnchor.aboveBottom(5), false), BEDROCK), SurfaceRules.ifTrue(SurfaceRules.not(new PaperBedrockConditionSource("bedrock_roof", VerticalAnchor.belowTop(5), VerticalAnchor.top(), true)), BEDROCK), SurfaceRules.ifTrue(conditionSource5, NETHERRACK), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.BASALT_DELTAS), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, BASALT), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, BASALT), BLACKSTONE)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.SOUL_SAND_VALLEY), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_CEILING, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)), SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.sequence(ruleSource, SurfaceRules.ifTrue(conditionSource12, SOUL_SAND), SOUL_SOIL)))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource2), SurfaceRules.ifTrue(conditionSource6, LAVA)), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.WARPED_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, WARPED_WART_BLOCK), WARPED_NYLIUM)))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.CRIMSON_FOREST), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource10), SurfaceRules.ifTrue(conditionSource, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource11, NETHER_WART_BLOCK), CRIMSON_NYLIUM)))))), SurfaceRules.ifTrue(SurfaceRules.isBiome(Biomes.NETHER_WASTES), SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.UNDER_FLOOR, SurfaceRules.ifTrue(conditionSource7, SurfaceRules.sequence(SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), SurfaceRules.ifTrue(conditionSource3, SurfaceRules.ifTrue(conditionSource4, SOUL_SAND))), NETHERRACK))), SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, SurfaceRules.ifTrue(conditionSource, SurfaceRules.ifTrue(conditionSource4, SurfaceRules.ifTrue(conditionSource8, SurfaceRules.sequence(SurfaceRules.ifTrue(conditionSource2, GRAVEL), SurfaceRules.ifTrue(SurfaceRules.not(conditionSource6), GRAVEL)))))))), NETHERRACK); - } - - public static SurfaceRules.RuleSource end() { -diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java -index c5822637e48fad4ca4e8cf210431b5eafbf5abb1..0ece775ca7e63184f79fcdd1aa7ed5c16fc9bc9e 100644 ---- a/src/main/java/net/minecraft/server/Bootstrap.java -+++ b/src/main/java/net/minecraft/server/Bootstrap.java -@@ -75,6 +75,7 @@ public class Bootstrap { - EntitySelectorOptions.bootStrap(); - DispenseItemBehavior.bootStrap(); - CauldronInteraction.bootStrap(); -+ Registry.register(net.minecraft.core.Registry.CONDITION, new net.minecraft.resources.ResourceLocation("paper", "bedrock_condition_source"), net.minecraft.data.worldgen.SurfaceRuleData.PaperBedrockConditionSource.CODEC.codec()); // Paper - register custom flat bedrock thing. TODO is this the best place to do this? - Registry.freezeBuiltins(); - Bootstrap.wrapStreams(); - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -index e66e52da84408eb705d23504e500bd8a98322b0e..575efe82a7219e256afd8362984eb26795445119 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -@@ -210,7 +210,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - @Override - public void buildSurface(WorldGenRegion region, StructureManager structures, RandomState noiseConfig, ChunkAccess chunk) { - if (!SharedConstants.debugVoidTerrain(chunk.getPos())) { -- WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region); -+ WorldGenerationContext worldgenerationcontext = new WorldGenerationContext(this, region, region.getMinecraftWorld()); // Paper - - this.buildSurface(chunk, worldgenerationcontext, noiseConfig, structures, region.getBiomeManager(), region.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), Blender.of(region)); - } -@@ -238,7 +238,7 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { - return this.createNoiseChunk(ichunkaccess1, structureAccessor, Blender.of(chunkRegion), noiseConfig); - }); - Aquifer aquifer = noisechunk.aquifer(); -- CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule()); -+ CarvingContext carvingcontext = new CarvingContext(this, chunkRegion.registryAccess(), chunk.getHeightAccessorForGeneration(), noisechunk, noiseConfig, ((NoiseGeneratorSettings) this.settings.value()).surfaceRule(), chunkRegion.getMinecraftWorld()); // Paper - CarvingMask carvingmask = ((ProtoChunk) chunk).getOrCreateCarvingMask(carverStep); - - for (int j = -8; j <= 8; ++j) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java -index b99283c31193e2110f6e3f39c23dbfc2442bab2b..1c9d9ecdafb2bd04348045ba0404da052dcd6437 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/WorldGenerationContext.java -@@ -6,10 +6,13 @@ import net.minecraft.world.level.chunk.ChunkGenerator; - public class WorldGenerationContext { - private final int minY; - private final int height; -+ private final @javax.annotation.Nullable net.minecraft.world.level.Level level; // Paper - -- public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) { -+ public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world) { this(generator, world, null); } // Paper -+ public WorldGenerationContext(ChunkGenerator generator, LevelHeightAccessor world, @org.jetbrains.annotations.Nullable net.minecraft.world.level.Level level) { // Paper - this.minY = Math.max(world.getMinBuildHeight(), generator.getMinY()); - this.height = Math.min(world.getHeight(), generator.getGenDepth()); -+ this.level = level; // Paper - } - - public int getMinGenY() { -@@ -19,4 +22,13 @@ public class WorldGenerationContext { - public int getGenDepth() { - return this.height; - } -+ -+ // Paper start -+ public net.minecraft.world.level.Level getWorld() { -+ if (this.level == null) { -+ throw new NullPointerException("WorldGenerationContext was initialized without a Level, but WorldGenerationContext#getWorld was called"); -+ } -+ return this.level; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java -index a745458ea3581ea91a68c863e3fd0a0292d73a61..f84ee8afe95f912a972e37fbae7a06ecdd3aba06 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/carver/CarvingContext.java -@@ -21,8 +21,8 @@ public class CarvingContext extends WorldGenerationContext { - private final RandomState randomState; - private final SurfaceRules.RuleSource surfaceRule; - -- public CarvingContext(NoiseBasedChunkGenerator noiseChunkGenerator, RegistryAccess registryManager, LevelHeightAccessor heightLimitView, NoiseChunk chunkNoiseSampler, RandomState noiseConfig, SurfaceRules.RuleSource materialRule) { -- super(noiseChunkGenerator, heightLimitView); -+ public CarvingContext(NoiseBasedChunkGenerator noiseChunkGenerator, RegistryAccess registryManager, LevelHeightAccessor heightLimitView, NoiseChunk chunkNoiseSampler, RandomState noiseConfig, SurfaceRules.RuleSource materialRule, @javax.annotation.Nullable net.minecraft.world.level.Level level) { // Paper -+ super(noiseChunkGenerator, heightLimitView, level); // Paper - this.registryAccess = registryManager; - this.noiseChunk = chunkNoiseSampler; - this.randomState = noiseConfig; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java -index 640c2683c842655bbaee8f293f1c2613ef44844e..53d818b0cc602f827d0b907e293515f6810c6792 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/placement/PlacementContext.java -@@ -18,7 +18,7 @@ public class PlacementContext extends WorldGenerationContext { - private final Optional topFeature; - - public PlacementContext(WorldGenLevel world, ChunkGenerator generator, Optional placedFeature) { -- super(generator, world); -+ super(generator, world, world.getLevel()); // Paper - this.level = world; - this.generator = generator; - this.topFeature = placedFeature; diff --git a/patches/server/0339-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server/0339-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch new file mode 100644 index 0000000000..ea3c47b7b2 --- /dev/null +++ b/patches/server/0339-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/SleepInBed.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java +index d7c6064710a04a8ecb5b429567467bd497f826a3..4da7f9af12c9bbc3403cdfd4245bd13f011ed89c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java +@@ -41,7 +41,8 @@ public class SleepInBed extends Behavior { + } + } + +- BlockState blockState = world.getBlockState(globalPos.pos()); ++ BlockState blockState = world.getBlockStateIfLoaded(globalPos.pos()); // Paper ++ if (blockState == null) { return false; } // Paper + return globalPos.pos().closerToCenterThan(entity.position(), 2.0D) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED); + } + } diff --git a/patches/server/0340-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server/0340-MC-145656-Fix-Follow-Range-Initial-Target.patch new file mode 100644 index 0000000000..44844fef0d --- /dev/null +++ b/patches/server/0340-MC-145656-Fix-Follow-Range-Initial-Target.patch @@ -0,0 +1,50 @@ +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/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +index 638942d54c6ea2d305350a330ac9fb8b82294f53..7f4fb6ad4b3b3da52a111b0c58499f27d8443124 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +@@ -38,6 +38,7 @@ public class NearestAttackableTargetGoal extends TargetG + this.randomInterval = reducedTickDelay(reciprocalChance); + this.setFlags(EnumSet.of(Goal.Flag.TARGET)); + this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(targetPredicate); ++ if (mob.level.paperConfig().entities.entitiesTargetWithFollowRange) this.targetConditions.useFollowRange(); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +index 9f65f6cdbcc054bde03c42d7d3b16f65b93e5deb..a7575b5ef56af6f53448d391abb4956e130148ca 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java ++++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +@@ -76,7 +76,7 @@ public class TargetingConditions { + + if (this.range > 0.0D) { + double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D; +- double e = Math.max(this.range * d, 2.0D); ++ double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper + double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + if (f > e * e) { + return false; +@@ -94,4 +94,18 @@ public class TargetingConditions { + return true; + } + } ++ ++ // Paper start ++ private boolean useFollowRange = false; ++ ++ public TargetingConditions useFollowRange() { ++ this.useFollowRange = true; ++ return this; ++ } ++ ++ private double getFollowRange(LivingEntity entityliving) { ++ net.minecraft.world.entity.ai.attributes.AttributeInstance attributeinstance = entityliving.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE); ++ return attributeinstance == null ? 16.0D : attributeinstance.getValue(); ++ } ++ // Paper end + } diff --git a/patches/server/0340-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch b/patches/server/0340-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch deleted file mode 100644 index ea3c47b7b2..0000000000 --- a/patches/server/0340-Prevent-sync-chunk-loads-when-villagers-try-to-find-.patch +++ /dev/null @@ -1,20 +0,0 @@ -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/SleepInBed.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java -index d7c6064710a04a8ecb5b429567467bd497f826a3..4da7f9af12c9bbc3403cdfd4245bd13f011ed89c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SleepInBed.java -@@ -41,7 +41,8 @@ public class SleepInBed extends Behavior { - } - } - -- BlockState blockState = world.getBlockState(globalPos.pos()); -+ BlockState blockState = world.getBlockStateIfLoaded(globalPos.pos()); // Paper -+ if (blockState == null) { return false; } // Paper - return globalPos.pos().closerToCenterThan(entity.position(), 2.0D) && blockState.is(BlockTags.BEDS) && !blockState.getValue(BedBlock.OCCUPIED); - } - } diff --git a/patches/server/0341-Duplicate-UUID-Resolve-Option.patch b/patches/server/0341-Duplicate-UUID-Resolve-Option.patch new file mode 100644 index 0000000000..9c8f0336e7 --- /dev/null +++ b/patches/server/0341-Duplicate-UUID-Resolve-Option.patch @@ -0,0 +1,138 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 21 Jul 2018 14:27:34 -0400 +Subject: [PATCH] Duplicate UUID Resolve Option + +Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 +which was added all the way back in March of 2016, it was unknown (potentially not at the time) +that an entity might actually change the seed of the random object. + +At some point, EntitySquid did start setting the seed. Due to this shared random, this caused +every entity to use a Random object with a predictable seed. + +This has caused entities to potentially generate with the same UUID.... + +Over the years, servers have had entities disappear, but no sign of trouble +because CraftBukkit removed the log lines indicating that something was wrong. + +We have fixed the root issue causing duplicate UUID's, however we now have chunk +files full of entities that have the same UUID as another entity! + +When these chunks load, the 2nd entity will not be added to the world correctly. + +If that chunk loads in a different order in the future, then it will reverse and the +missing one is now the one added to the world and not the other. This results in very +inconsistent entity behavior. + +This change allows you to recover any duplicate entity by generating a new UUID for it. +This also lets you delete them instead if you don't want to risk having new entities added to +the world that you previously did not see. + +But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. + +It is recommended you regenerate the entities, as these were legit entities, and deserve your love. + +diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java +index 1b1bfd5f92f85f46ad9661a0a64a2a1b4c33a80d..2a099fe0d514f181bf2b452d5333bc29b0d29e43 100644 +--- a/src/main/java/net/minecraft/server/ChunkSystem.java ++++ b/src/main/java/net/minecraft/server/ChunkSystem.java +@@ -260,7 +260,9 @@ public final class ChunkSystem { + } + + public static void onEntityPreAdd(final ServerLevel level, final Entity entity) { +- ++ if (net.minecraft.server.level.ChunkMap.checkDupeUUID(level, entity)) { ++ return; ++ } + } + + public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 245f60eb904e26d6e7f7ca02bfa778d4f6db5d76..4b24e4d947e96ea0720f8f6bc33470e07c00310d 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -982,6 +982,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.discard(); + needsRemoval = true; + } ++ checkDupeUUID(world, entity); // Paper + return !needsRemoval; + })); + // CraftBukkit end +@@ -1032,6 +1033,49 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + } + ++ // Paper start ++ // rets true if to prevent the entity from being added ++ public static boolean checkDupeUUID(ServerLevel level, Entity entity) { ++ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; ++ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE ++ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { ++ return false; ++ } ++ Entity other = level.getEntity(entity.getUUID()); ++ ++ if (other == null || other == entity) { ++ return false; ++ } ++ ++ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() ++ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) ++ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange ++ ) { ++ if (ServerLevel.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.discard(); ++ return true; ++ } ++ if (other != null && !other.isRemoved()) { ++ switch (mode) { ++ case SAFE_REGEN: { ++ entity.setUUID(java.util.UUID.randomUUID()); ++ if (ServerLevel.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 (ServerLevel.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.discard(); ++ return true; ++ } ++ default: ++ if (ServerLevel.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; ++ } ++ } ++ return false; ++ } ++ // Paper end + public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { + ChunkPos chunkcoordintpair = holder.getPos(); + CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(chunkcoordintpair, 1, (i) -> { +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index ab7eadf2fc4c4598fa89068332eaaf9a8e0a100f..16519a6414f6f6418de40b714555a52631980617 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -78,7 +78,22 @@ public class PersistentEntitySectionManager implements A + + private boolean addEntityUuid(T entity) { + if (!this.knownUuids.add(entity.getUUID())) { ++ // Paper start ++ T conflict = this.visibleEntityStorage.getEntity(entity.getUUID()); ++ if (conflict != null && ((Entity) conflict).isRemoved()) { ++ stopTracking(conflict); // remove the existing entity ++ return true; ++ } ++ // Paper end + PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); ++ // Paper start ++ if (net.minecraft.world.level.Level.DEBUG_ENTITIES && ((Entity) entity).level.paperConfig().entities.spawning.duplicateUuid.mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.NOTHING) { ++ if (((Entity) entity).addedToWorldStack != null) { ++ ((Entity) entity).addedToWorldStack.printStackTrace(); ++ } ++ net.minecraft.server.level.ServerLevel.getAddToWorldStackTrace((Entity) entity).printStackTrace(); ++ } ++ // Paper end + return false; + } else { + return true; diff --git a/patches/server/0341-MC-145656-Fix-Follow-Range-Initial-Target.patch b/patches/server/0341-MC-145656-Fix-Follow-Range-Initial-Target.patch deleted file mode 100644 index 44844fef0d..0000000000 --- a/patches/server/0341-MC-145656-Fix-Follow-Range-Initial-Target.patch +++ /dev/null @@ -1,50 +0,0 @@ -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/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 638942d54c6ea2d305350a330ac9fb8b82294f53..7f4fb6ad4b3b3da52a111b0c58499f27d8443124 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -38,6 +38,7 @@ public class NearestAttackableTargetGoal extends TargetG - this.randomInterval = reducedTickDelay(reciprocalChance); - this.setFlags(EnumSet.of(Goal.Flag.TARGET)); - this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(targetPredicate); -+ if (mob.level.paperConfig().entities.entitiesTargetWithFollowRange) this.targetConditions.useFollowRange(); // Paper - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -index 9f65f6cdbcc054bde03c42d7d3b16f65b93e5deb..a7575b5ef56af6f53448d391abb4956e130148ca 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -@@ -76,7 +76,7 @@ public class TargetingConditions { - - if (this.range > 0.0D) { - double d = this.testInvisible ? targetEntity.getVisibilityPercent(baseEntity) : 1.0D; -- double e = Math.max(this.range * d, 2.0D); -+ double e = Math.max((this.useFollowRange ? this.getFollowRange(baseEntity) : this.range) * d, 2.0D); // Paper - double f = baseEntity.distanceToSqr(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); - if (f > e * e) { - return false; -@@ -94,4 +94,18 @@ public class TargetingConditions { - return true; - } - } -+ -+ // Paper start -+ private boolean useFollowRange = false; -+ -+ public TargetingConditions useFollowRange() { -+ this.useFollowRange = true; -+ return this; -+ } -+ -+ private double getFollowRange(LivingEntity entityliving) { -+ net.minecraft.world.entity.ai.attributes.AttributeInstance attributeinstance = entityliving.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.FOLLOW_RANGE); -+ return attributeinstance == null ? 16.0D : attributeinstance.getValue(); -+ } -+ // Paper end - } diff --git a/patches/server/0342-Duplicate-UUID-Resolve-Option.patch b/patches/server/0342-Duplicate-UUID-Resolve-Option.patch deleted file mode 100644 index 9c8f0336e7..0000000000 --- a/patches/server/0342-Duplicate-UUID-Resolve-Option.patch +++ /dev/null @@ -1,138 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 21 Jul 2018 14:27:34 -0400 -Subject: [PATCH] Duplicate UUID Resolve Option - -Due to a bug in https://github.com/PaperMC/Paper/commit/2e29af3df05ec0a383f48be549d1c03200756d24 -which was added all the way back in March of 2016, it was unknown (potentially not at the time) -that an entity might actually change the seed of the random object. - -At some point, EntitySquid did start setting the seed. Due to this shared random, this caused -every entity to use a Random object with a predictable seed. - -This has caused entities to potentially generate with the same UUID.... - -Over the years, servers have had entities disappear, but no sign of trouble -because CraftBukkit removed the log lines indicating that something was wrong. - -We have fixed the root issue causing duplicate UUID's, however we now have chunk -files full of entities that have the same UUID as another entity! - -When these chunks load, the 2nd entity will not be added to the world correctly. - -If that chunk loads in a different order in the future, then it will reverse and the -missing one is now the one added to the world and not the other. This results in very -inconsistent entity behavior. - -This change allows you to recover any duplicate entity by generating a new UUID for it. -This also lets you delete them instead if you don't want to risk having new entities added to -the world that you previously did not see. - -But for those who are ok with leaving this inconsistent behavior, you may use WARN or NOTHING options. - -It is recommended you regenerate the entities, as these were legit entities, and deserve your love. - -diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java -index 1b1bfd5f92f85f46ad9661a0a64a2a1b4c33a80d..2a099fe0d514f181bf2b452d5333bc29b0d29e43 100644 ---- a/src/main/java/net/minecraft/server/ChunkSystem.java -+++ b/src/main/java/net/minecraft/server/ChunkSystem.java -@@ -260,7 +260,9 @@ public final class ChunkSystem { - } - - public static void onEntityPreAdd(final ServerLevel level, final Entity entity) { -- -+ if (net.minecraft.server.level.ChunkMap.checkDupeUUID(level, entity)) { -+ return; -+ } - } - - public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 245f60eb904e26d6e7f7ca02bfa778d4f6db5d76..4b24e4d947e96ea0720f8f6bc33470e07c00310d 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -982,6 +982,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - entity.discard(); - needsRemoval = true; - } -+ checkDupeUUID(world, entity); // Paper - return !needsRemoval; - })); - // CraftBukkit end -@@ -1032,6 +1033,49 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - } - -+ // Paper start -+ // rets true if to prevent the entity from being added -+ public static boolean checkDupeUUID(ServerLevel level, Entity entity) { -+ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; -+ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE -+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) { -+ return false; -+ } -+ Entity other = level.getEntity(entity.getUUID()); -+ -+ if (other == null || other == entity) { -+ return false; -+ } -+ -+ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved() -+ && Objects.equals(other.getEncodeId(), entity.getEncodeId()) -+ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange -+ ) { -+ if (ServerLevel.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.discard(); -+ return true; -+ } -+ if (other != null && !other.isRemoved()) { -+ switch (mode) { -+ case SAFE_REGEN: { -+ entity.setUUID(java.util.UUID.randomUUID()); -+ if (ServerLevel.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 (ServerLevel.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.discard(); -+ return true; -+ } -+ default: -+ if (ServerLevel.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; -+ } -+ } -+ return false; -+ } -+ // Paper end - public CompletableFuture> prepareTickingChunk(ChunkHolder holder) { - ChunkPos chunkcoordintpair = holder.getPos(); - CompletableFuture, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkRangeFuture(chunkcoordintpair, 1, (i) -> { -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index ab7eadf2fc4c4598fa89068332eaaf9a8e0a100f..16519a6414f6f6418de40b714555a52631980617 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -78,7 +78,22 @@ public class PersistentEntitySectionManager implements A - - private boolean addEntityUuid(T entity) { - if (!this.knownUuids.add(entity.getUUID())) { -+ // Paper start -+ T conflict = this.visibleEntityStorage.getEntity(entity.getUUID()); -+ if (conflict != null && ((Entity) conflict).isRemoved()) { -+ stopTracking(conflict); // remove the existing entity -+ return true; -+ } -+ // Paper end - PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity); -+ // Paper start -+ if (net.minecraft.world.level.Level.DEBUG_ENTITIES && ((Entity) entity).level.paperConfig().entities.spawning.duplicateUuid.mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.NOTHING) { -+ if (((Entity) entity).addedToWorldStack != null) { -+ ((Entity) entity).addedToWorldStack.printStackTrace(); -+ } -+ net.minecraft.server.level.ServerLevel.getAddToWorldStackTrace((Entity) entity).printStackTrace(); -+ } -+ // Paper end - return false; - } else { - return true; diff --git a/patches/server/0342-Optimize-Hoppers.patch b/patches/server/0342-Optimize-Hoppers.patch new file mode 100644 index 0000000000..7e66fc243d --- /dev/null +++ b/patches/server/0342-Optimize-Hoppers.patch @@ -0,0 +1,460 @@ +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/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7342c8ac1d4bb13c0d8ce6f26b1b43d76a327655..3a085c141a5a6e2df9a85d0e3969363d69824294 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1411,6 +1411,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.profiler.push(() -> { + return worldserver + " " + worldserver.dimension().location(); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 4b41d2dd00c1b206c1419ba767a3474947664e53..5e0852c4656813272a7ee6cb9c2331410c1b7739 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -625,11 +625,12 @@ public final class ItemStack { + return this.getItem().interactLivingEntity(this, user, entity, hand); + } + +- public ItemStack copy() { +- if (this.isEmpty()) { ++ public ItemStack copy() { return cloneItemStack(false); } // Paper ++ public ItemStack cloneItemStack(boolean origItem) { // Paper ++ if (!origItem && this.isEmpty()) { // Paper + return ItemStack.EMPTY; + } else { +- ItemStack itemstack = new ItemStack(this.getItem(), this.count); ++ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper + + itemstack.setPopTime(this.getPopTime()); + if (this.tag != null) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index 06beb18e5e1950aeb6cb427876fcc4c5ea95adb2..b0174aedb7358af1e80278e2f5f13e00c35ab3c6 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -26,6 +26,7 @@ import co.aikar.timings.MinecraftTimings; // Paper + import co.aikar.timings.Timing; // Paper + + public abstract class BlockEntity { ++ static boolean IGNORE_TILE_UPDATES = false; // Paper + + public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers +@@ -162,6 +163,7 @@ public abstract class BlockEntity { + + public void setChanged() { + if (this.level != null) { ++ if (IGNORE_TILE_UPDATES) return; // Paper + BlockEntity.setChanged(this.level, this.worldPosition, this.blockState); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java +index a05acf709735b40ca86f978508c63a86065fd405..6a1405a8630e90db3b5a3c9152259ba6f5f0c784 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java +@@ -14,6 +14,8 @@ public interface Hopper extends Container { + return SUCK; + } + ++ default net.minecraft.core.BlockPos getBlockPosition() { return new net.minecraft.core.BlockPos(getLevelX(), getLevelY(), getLevelZ()); } // Paper ++ + double getLevelX(); + + double getLevelY(); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index a507d7f65a94e49ecd18cd18797b156474558390..a7ac6b528aecae528a17af157f8ec29371e4484c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -3,7 +3,6 @@ package net.minecraft.world.level.block.entity; + import java.util.Iterator; + import java.util.List; + import java.util.function.BooleanSupplier; +-import java.util.stream.Collectors; + import java.util.stream.IntStream; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; +@@ -32,7 +31,6 @@ import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; +-import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.entity.CraftHumanEntity; + import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.entity.HumanEntity; +@@ -190,6 +188,158 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + return false; + } ++ // Paper start - Optimize Hoppers ++ private static boolean skipPullModeEventFire = false; ++ private static boolean skipPushModeEventFire = false; ++ public static boolean skipHopperEvents = false; ++ ++ private static boolean hopperPush(Level level, BlockPos pos, Container destination, Direction enumdirection, HopperBlockEntity hopper) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < hopper.getContainerSize(); ++i) { ++ ItemStack item = hopper.getItem(i); ++ if (!item.isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = item; ++ ItemStack itemstack = origItemStack; ++ ++ final int origCount = origItemStack.getCount(); ++ final int moved = Math.min(level.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(destination, itemstack, hopper); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ return false; ++ } ++ } ++ final ItemStack itemstack2 = addItem(hopper, destination, 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); ++ } ++ hopper.setItem(i, origItemStack); ++ destination.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ } ++ } ++ if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown ++ hopper.setCooldown(level.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(Level level, Hopper ihopper, Container iinventory, ItemStack origItemStack, int i) { ++ ItemStack itemstack = origItemStack; ++ final int origCount = origItemStack.getCount(); ++ final int moved = Math.min(level.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.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ ++ if (level.paperConfig().hopper.cooldownWhenFull) { ++ cooldownHopper(ihopper); ++ } ++ ++ return false; ++ } ++ ++ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) { ++ Inventory destinationInventory = getInventory(iinventory); ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(hopper.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static ItemStack callPullMoveEvent(Hopper hopper, Container 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(Container iinventory) { ++ Inventory sourceInventory;// Have to special case large chests as they work oddly ++ if (iinventory instanceof CompoundContainer) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ } else if (iinventory instanceof BlockEntity) { ++ sourceInventory = ((BlockEntity) iinventory).getOwner(false).getInventory(); ++ } else { ++ sourceInventory = iinventory.getOwner().getInventory(); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(Hopper hopper) { ++ if (hopper instanceof HopperBlockEntity blockEntity) { ++ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); ++ } else if (hopper instanceof MinecartHopper blockEntity) { ++ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer / 2); ++ } ++ } ++ // Paper end + + private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit + Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); +@@ -202,6 +352,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) { + return false; + } else { ++ return hopperPush(world, blockposition, iinventory1, enumdirection, hopper); /* // Paper - disable rest + for (int i = 0; i < iinventory.getContainerSize(); ++i) { + if (!iinventory.getItem(i).isEmpty()) { + ItemStack itemstack = iinventory.getItem(i).copy(); +@@ -239,7 +390,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- return false; ++ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + } + } +@@ -249,27 +400,68 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + private static boolean isFullContainer(Container inventory, Direction direction) { +- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> { +- ItemStack itemstack = inventory.getItem(i); +- +- return itemstack.getCount() >= itemstack.getMaxStackSize(); +- }); ++ return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams + } + + private static boolean isEmptyContainer(Container inv, Direction facing) { +- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> { +- return inv.getItem(i).isEmpty(); +- }); ++ // Paper start ++ return allMatch(inv, facing, IS_EMPTY_TEST); ++ } ++ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } ++ return true; + } + ++ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } ++ return true; ++ } ++ private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); ++ private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); ++ // Paper end ++ + public static boolean suckInItems(Level world, Hopper hopper) { + Container iinventory = HopperBlockEntity.getSourceContainer(world, hopper); + + if (iinventory != null) { + Direction enumdirection = Direction.DOWN; + +- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> { +- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot ++ // Paper start - optimize hoppers and remove streams ++ skipPullModeEventFire = skipHopperEvents; ++ return !HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> { ++ // Logic copied from below to avoid extra getItem calls ++ if (!item.isEmpty() && canTakeItemFromContainer(iinventory, item, i, enumdirection)) { ++ return hopperPull(world, hopper, iinventory, item, i); ++ } else { ++ return false; ++ } ++ // Paper end + }); + } else { + Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator(); +@@ -288,10 +480,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + ++ // Paper - method unused as logic is inlined above + private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot + ItemStack itemstack = iinventory.getItem(i); + +- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) { ++ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left inused incase reflective plugins ++ return hopperPull(world, ihopper, iinventory, itemstack, i); /* // Paper - disable rest + ItemStack itemstack1 = itemstack.copy(); + // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); + // CraftBukkit start - Call event on collection of items from inventories into the hopper +@@ -328,7 +522,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot +- iinventory.setItem(i, itemstack1); ++ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + + return false; +@@ -337,7 +531,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container inventory, ItemEntity itemEntity) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation + itemEntity.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; +@@ -396,7 +590,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(to.getMaxStackSize()); + } + // Spigot end ++ IGNORE_TILE_UPDATES = true; // Paper + to.setItem(slot, stack); ++ IGNORE_TILE_UPDATES = false; // Paper + stack = ItemStack.EMPTY; + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +@@ -447,18 +643,23 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + + public static List getItemsAtAndAbove(Level world, Hopper hopper) { +- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { +- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); +- }).collect(Collectors.toList()); ++ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?! ++ double d0 = hopper.getLevelX(); ++ double d1 = hopper.getLevelY(); ++ double d2 = hopper.getLevelZ(); ++ AABB bb = new AABB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D); ++ return world.getEntitiesOfClass(ItemEntity.class, bb, Entity::isAlive); ++ // Paper end + } + + @Nullable + public static Container getContainerAt(Level world, BlockPos pos) { +- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); ++ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper + } + ++ public static Container getContainerAt(Level world, double x, double y, double z) { return getContainerAt(world, x, y, z, false); } // Paper - overload to default false + @Nullable +- private static Container getContainerAt(Level world, double x, double y, double z) { ++ private static Container getContainerAt(Level world, double x, double y, double z, boolean optimizeEntities) { + Object object = null; + BlockPos blockposition = new BlockPos(x, y, z); + if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( blockposition ) ) return null; // Spigot +@@ -478,7 +679,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + +- if (object == null) { ++ if (object == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper + List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); + + if (!list.isEmpty()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index e3bee2df77d87630e96621470e940d9d9e152e7f..d559f93a9a09bac414dd5d58afccad42c127f09b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -95,12 +95,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + @Override + public boolean isEmpty() { + this.unpackLootTable((Player)null); +- return this.getItems().stream().allMatch(ItemStack::isEmpty); ++ // Paper start ++ for (ItemStack itemStack : this.getItems()) { ++ if (!itemStack.isEmpty()) { ++ return false; ++ } ++ } ++ // Paper end ++ return true; + } + + @Override + public ItemStack getItem(int slot) { +- this.unpackLootTable((Player)null); ++ if (slot == 0) this.unpackLootTable((Player) null); // Paper + return this.getItems().get(slot); + } + diff --git a/patches/server/0343-Optimize-Hoppers.patch b/patches/server/0343-Optimize-Hoppers.patch deleted file mode 100644 index 7e66fc243d..0000000000 --- a/patches/server/0343-Optimize-Hoppers.patch +++ /dev/null @@ -1,460 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 27 Apr 2016 22:09:52 -0400 -Subject: [PATCH] Optimize Hoppers - -* Removes unnecessary extra calls to .update() that are very expensive -* Lots of itemstack cloning removed. Only clone if the item is actually moved -* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. - However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. -* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory -* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration -* 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/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7342c8ac1d4bb13c0d8ce6f26b1b43d76a327655..3a085c141a5a6e2df9a85d0e3969363d69824294 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1411,6 +1411,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper -+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - - this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 4b41d2dd00c1b206c1419ba767a3474947664e53..5e0852c4656813272a7ee6cb9c2331410c1b7739 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -625,11 +625,12 @@ public final class ItemStack { - return this.getItem().interactLivingEntity(this, user, entity, hand); - } - -- public ItemStack copy() { -- if (this.isEmpty()) { -+ public ItemStack copy() { return cloneItemStack(false); } // Paper -+ public ItemStack cloneItemStack(boolean origItem) { // Paper -+ if (!origItem && this.isEmpty()) { // Paper - return ItemStack.EMPTY; - } else { -- ItemStack itemstack = new ItemStack(this.getItem(), this.count); -+ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper - - itemstack.setPopTime(this.getPopTime()); - if (this.tag != null) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 06beb18e5e1950aeb6cb427876fcc4c5ea95adb2..b0174aedb7358af1e80278e2f5f13e00c35ab3c6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -26,6 +26,7 @@ import co.aikar.timings.MinecraftTimings; // Paper - import co.aikar.timings.Timing; // Paper - - public abstract class BlockEntity { -+ static boolean IGNORE_TILE_UPDATES = false; // Paper - - public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper - // CraftBukkit start - data containers -@@ -162,6 +163,7 @@ public abstract class BlockEntity { - - public void setChanged() { - if (this.level != null) { -+ if (IGNORE_TILE_UPDATES) return; // Paper - BlockEntity.setChanged(this.level, this.worldPosition, this.blockState); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java -index a05acf709735b40ca86f978508c63a86065fd405..6a1405a8630e90db3b5a3c9152259ba6f5f0c784 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/Hopper.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/Hopper.java -@@ -14,6 +14,8 @@ public interface Hopper extends Container { - return SUCK; - } - -+ default net.minecraft.core.BlockPos getBlockPosition() { return new net.minecraft.core.BlockPos(getLevelX(), getLevelY(), getLevelZ()); } // Paper -+ - double getLevelX(); - - double getLevelY(); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index a507d7f65a94e49ecd18cd18797b156474558390..a7ac6b528aecae528a17af157f8ec29371e4484c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -3,7 +3,6 @@ package net.minecraft.world.level.block.entity; - import java.util.Iterator; - import java.util.List; - import java.util.function.BooleanSupplier; --import java.util.stream.Collectors; - import java.util.stream.IntStream; - import javax.annotation.Nullable; - import net.minecraft.core.BlockPos; -@@ -32,7 +31,6 @@ import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.shapes.BooleanOp; - import net.minecraft.world.phys.shapes.Shapes; --import org.bukkit.Bukkit; - import org.bukkit.craftbukkit.entity.CraftHumanEntity; - import org.bukkit.craftbukkit.inventory.CraftItemStack; - import org.bukkit.entity.HumanEntity; -@@ -190,6 +188,158 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - return false; - } -+ // Paper start - Optimize Hoppers -+ private static boolean skipPullModeEventFire = false; -+ private static boolean skipPushModeEventFire = false; -+ public static boolean skipHopperEvents = false; -+ -+ private static boolean hopperPush(Level level, BlockPos pos, Container destination, Direction enumdirection, HopperBlockEntity hopper) { -+ skipPushModeEventFire = skipHopperEvents; -+ boolean foundItem = false; -+ for (int i = 0; i < hopper.getContainerSize(); ++i) { -+ ItemStack item = hopper.getItem(i); -+ if (!item.isEmpty()) { -+ foundItem = true; -+ ItemStack origItemStack = item; -+ ItemStack itemstack = origItemStack; -+ -+ final int origCount = origItemStack.getCount(); -+ final int moved = Math.min(level.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(destination, itemstack, hopper); -+ if (itemstack == null) { // cancelled -+ origItemStack.setCount(origCount); -+ return false; -+ } -+ } -+ final ItemStack itemstack2 = addItem(hopper, destination, 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); -+ } -+ hopper.setItem(i, origItemStack); -+ destination.setChanged(); -+ return true; -+ } -+ origItemStack.setCount(origCount); -+ } -+ } -+ if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown -+ hopper.setCooldown(level.spigotConfig.hopperTransfer); -+ } -+ return false; -+ } -+ -+ private static boolean hopperPull(Level level, Hopper ihopper, Container iinventory, ItemStack origItemStack, int i) { -+ ItemStack itemstack = origItemStack; -+ final int origCount = origItemStack.getCount(); -+ final int moved = Math.min(level.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.setChanged(); -+ return true; -+ } -+ origItemStack.setCount(origCount); -+ -+ if (level.paperConfig().hopper.cooldownWhenFull) { -+ cooldownHopper(ihopper); -+ } -+ -+ return false; -+ } -+ -+ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) { -+ Inventory destinationInventory = getInventory(iinventory); -+ InventoryMoveItemEvent event = new InventoryMoveItemEvent(hopper.getOwner(false).getInventory(), -+ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); -+ boolean result = event.callEvent(); -+ if (!event.calledGetItem && !event.calledSetItem) { -+ skipPushModeEventFire = true; -+ } -+ if (!result) { -+ cooldownHopper(hopper); -+ return null; -+ } -+ -+ if (event.calledSetItem) { -+ return CraftItemStack.asNMSCopy(event.getItem()); -+ } else { -+ return itemstack; -+ } -+ } -+ -+ private static ItemStack callPullMoveEvent(Hopper hopper, Container 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(Container iinventory) { -+ Inventory sourceInventory;// Have to special case large chests as they work oddly -+ if (iinventory instanceof CompoundContainer) { -+ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); -+ } else if (iinventory instanceof BlockEntity) { -+ sourceInventory = ((BlockEntity) iinventory).getOwner(false).getInventory(); -+ } else { -+ sourceInventory = iinventory.getOwner().getInventory(); -+ } -+ return sourceInventory; -+ } -+ -+ private static void cooldownHopper(Hopper hopper) { -+ if (hopper instanceof HopperBlockEntity blockEntity) { -+ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); -+ } else if (hopper instanceof MinecartHopper blockEntity) { -+ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer / 2); -+ } -+ } -+ // Paper end - - private static boolean ejectItems(Level world, BlockPos blockposition, BlockState iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit - Container iinventory1 = HopperBlockEntity.getAttachedContainer(world, blockposition, iblockdata); -@@ -202,6 +352,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - if (HopperBlockEntity.isFullContainer(iinventory1, enumdirection)) { - return false; - } else { -+ return hopperPush(world, blockposition, iinventory1, enumdirection, hopper); /* // Paper - disable rest - for (int i = 0; i < iinventory.getContainerSize(); ++i) { - if (!iinventory.getItem(i).isEmpty()) { - ItemStack itemstack = iinventory.getItem(i).copy(); -@@ -239,7 +390,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -- return false; -+ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations - } - } - } -@@ -249,27 +400,68 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - private static boolean isFullContainer(Container inventory, Direction direction) { -- return HopperBlockEntity.getSlots(inventory, direction).allMatch((i) -> { -- ItemStack itemstack = inventory.getItem(i); -- -- return itemstack.getCount() >= itemstack.getMaxStackSize(); -- }); -+ return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams - } - - private static boolean isEmptyContainer(Container inv, Direction facing) { -- return HopperBlockEntity.getSlots(inv, facing).allMatch((i) -> { -- return inv.getItem(i).isEmpty(); -- }); -+ // Paper start -+ return allMatch(inv, facing, IS_EMPTY_TEST); -+ } -+ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { -+ if (iinventory instanceof WorldlyContainer) { -+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { -+ if (!test.test(iinventory.getItem(i), i)) { -+ return false; -+ } -+ } -+ } else { -+ int size = iinventory.getContainerSize(); -+ for (int i = 0; i < size; i++) { -+ if (!test.test(iinventory.getItem(i), i)) { -+ return false; -+ } -+ } -+ } -+ return true; - } - -+ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { -+ if (iinventory instanceof WorldlyContainer) { -+ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { -+ if (test.test(iinventory.getItem(i), i)) { -+ return true; -+ } -+ } -+ } else { -+ int size = iinventory.getContainerSize(); -+ for (int i = 0; i < size; i++) { -+ if (test.test(iinventory.getItem(i), i)) { -+ return true; -+ } -+ } -+ } -+ return true; -+ } -+ private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); -+ private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); -+ // Paper end -+ - public static boolean suckInItems(Level world, Hopper hopper) { - Container iinventory = HopperBlockEntity.getSourceContainer(world, hopper); - - if (iinventory != null) { - Direction enumdirection = Direction.DOWN; - -- return HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) ? false : HopperBlockEntity.getSlots(iinventory, enumdirection).anyMatch((i) -> { -- return HopperBlockEntity.a(hopper, iinventory, i, enumdirection, world); // Spigot -+ // Paper start - optimize hoppers and remove streams -+ skipPullModeEventFire = skipHopperEvents; -+ return !HopperBlockEntity.isEmptyContainer(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> { -+ // Logic copied from below to avoid extra getItem calls -+ if (!item.isEmpty() && canTakeItemFromContainer(iinventory, item, i, enumdirection)) { -+ return hopperPull(world, hopper, iinventory, item, i); -+ } else { -+ return false; -+ } -+ // Paper end - }); - } else { - Iterator iterator = HopperBlockEntity.getItemsAtAndAbove(world, hopper).iterator(); -@@ -288,10 +480,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -+ // Paper - method unused as logic is inlined above - private static boolean a(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot - ItemStack itemstack = iinventory.getItem(i); - -- if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) { -+ if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left inused incase reflective plugins -+ return hopperPull(world, ihopper, iinventory, itemstack, i); /* // Paper - disable rest - ItemStack itemstack1 = itemstack.copy(); - // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null); - // CraftBukkit start - Call event on collection of items from inventories into the hopper -@@ -328,7 +522,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - itemstack1.shrink(origCount - itemstack2.getCount()); // Spigot -- iinventory.setItem(i, itemstack1); -+ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations - } - - return false; -@@ -337,7 +531,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - public static boolean addItem(Container inventory, ItemEntity itemEntity) { - boolean flag = false; - // CraftBukkit start -- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); -+ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation - itemEntity.level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return false; -@@ -396,7 +590,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - stack = stack.split(to.getMaxStackSize()); - } - // Spigot end -+ IGNORE_TILE_UPDATES = true; // Paper - to.setItem(slot, stack); -+ IGNORE_TILE_UPDATES = false; // Paper - stack = ItemStack.EMPTY; - flag = true; - } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { -@@ -447,18 +643,23 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - - public static List getItemsAtAndAbove(Level world, Hopper hopper) { -- return (List) hopper.getSuckShape().toAabbs().stream().flatMap((axisalignedbb) -> { -- return world.getEntitiesOfClass(ItemEntity.class, axisalignedbb.move(hopper.getLevelX() - 0.5D, hopper.getLevelY() - 0.5D, hopper.getLevelZ() - 0.5D), EntitySelector.ENTITY_STILL_ALIVE).stream(); -- }).collect(Collectors.toList()); -+ // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?! -+ double d0 = hopper.getLevelX(); -+ double d1 = hopper.getLevelY(); -+ double d2 = hopper.getLevelZ(); -+ AABB bb = new AABB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D); -+ return world.getEntitiesOfClass(ItemEntity.class, bb, Entity::isAlive); -+ // Paper end - } - - @Nullable - public static Container getContainerAt(Level world, BlockPos pos) { -- return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); -+ return HopperBlockEntity.getContainerAt(world, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); // Paper - } - -+ public static Container getContainerAt(Level world, double x, double y, double z) { return getContainerAt(world, x, y, z, false); } // Paper - overload to default false - @Nullable -- private static Container getContainerAt(Level world, double x, double y, double z) { -+ private static Container getContainerAt(Level world, double x, double y, double z, boolean optimizeEntities) { - Object object = null; - BlockPos blockposition = new BlockPos(x, y, z); - if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( blockposition ) ) return null; // Spigot -@@ -478,7 +679,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - } - } - -- if (object == null) { -+ if (object == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper - List list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); - - if (!list.isEmpty()) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -index e3bee2df77d87630e96621470e940d9d9e152e7f..d559f93a9a09bac414dd5d58afccad42c127f09b 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java -@@ -95,12 +95,19 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - @Override - public boolean isEmpty() { - this.unpackLootTable((Player)null); -- return this.getItems().stream().allMatch(ItemStack::isEmpty); -+ // Paper start -+ for (ItemStack itemStack : this.getItems()) { -+ if (!itemStack.isEmpty()) { -+ return false; -+ } -+ } -+ // Paper end -+ return true; - } - - @Override - public ItemStack getItem(int slot) { -- this.unpackLootTable((Player)null); -+ if (slot == 0) this.unpackLootTable((Player) null); // Paper - return this.getItems().get(slot); - } - diff --git a/patches/server/0343-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0343-PlayerDeathEvent-shouldDropExperience.patch new file mode 100644 index 0000000000..f3bcd088d8 --- /dev/null +++ b/patches/server/0343-PlayerDeathEvent-shouldDropExperience.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 24 Dec 2019 00:35:42 +0000 +Subject: [PATCH] PlayerDeathEvent#shouldDropExperience + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d868c7f953cba9f7691c62edd2169ad26fc7d867..b36effc88b516958a7c0a46a7eb3a77a98ad3a20 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -911,7 +911,7 @@ public class ServerPlayer extends Player { + this.tellNeutralMobsThatIDied(); + } + // SPIGOT-5478 must be called manually now +- this.dropExperience(); ++ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event + // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. + if (!event.getKeepInventory()) { + // Paper start - replace logic diff --git a/patches/server/0344-PlayerDeathEvent-shouldDropExperience.patch b/patches/server/0344-PlayerDeathEvent-shouldDropExperience.patch deleted file mode 100644 index f3bcd088d8..0000000000 --- a/patches/server/0344-PlayerDeathEvent-shouldDropExperience.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 24 Dec 2019 00:35:42 +0000 -Subject: [PATCH] PlayerDeathEvent#shouldDropExperience - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d868c7f953cba9f7691c62edd2169ad26fc7d867..b36effc88b516958a7c0a46a7eb3a77a98ad3a20 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -911,7 +911,7 @@ public class ServerPlayer extends Player { - this.tellNeutralMobsThatIDied(); - } - // SPIGOT-5478 must be called manually now -- this.dropExperience(); -+ if (event.shouldDropExperience()) this.dropExperience(); // Paper - tie to event - // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. - if (!event.getKeepInventory()) { - // Paper start - replace logic diff --git a/patches/server/0344-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server/0344-Prevent-bees-loading-chunks-checking-hive-position.patch new file mode 100644 index 0000000000..3e8d451777 --- /dev/null +++ b/patches/server/0344-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/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index 255ebdcddb46653d70b810b8ca94b86ccde80343..bdc9911f5a72d2f23a3a01d0420ac9ba6cb78570 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -496,6 +496,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + if (!this.hasHive()) { + return false; + } else { ++ if (level.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) + BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); + + return tileentity != null && tileentity.getType() == BlockEntityType.BEEHIVE; diff --git a/patches/server/0345-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server/0345-Don-t-load-Chunks-from-Hoppers-and-other-things.patch new file mode 100644 index 0000000000..71f28eb22f --- /dev/null +++ b/patches/server/0345-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/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +index ff2a7b08fe70adaecdaa508baadcfe40416519e0..6c334703c816d2a04f97006c5796c658f34a12a4 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java ++++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java +@@ -25,7 +25,12 @@ public class DoubleBlockCombiner { + return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); + } else { + BlockPos blockPos = pos.relative(function.apply(state)); +- BlockState blockState = world.getBlockState(blockPos); ++ // Paper start ++ BlockState blockState = world.getBlockStateIfLoaded(blockPos); ++ if (blockState == null) { ++ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); ++ } ++ // Paper end + if (blockState.is(state.getBlock())) { + DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState); + if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE && blockType != blockType2 && blockState.getValue(directionProperty) == state.getValue(directionProperty)) { diff --git a/patches/server/0345-Prevent-bees-loading-chunks-checking-hive-position.patch b/patches/server/0345-Prevent-bees-loading-chunks-checking-hive-position.patch deleted file mode 100644 index 3e8d451777..0000000000 --- a/patches/server/0345-Prevent-bees-loading-chunks-checking-hive-position.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 255ebdcddb46653d70b810b8ca94b86ccde80343..bdc9911f5a72d2f23a3a01d0420ac9ba6cb78570 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -496,6 +496,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - if (!this.hasHive()) { - return false; - } else { -+ if (level.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) - BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); - - return tileentity != null && tileentity.getType() == BlockEntityType.BEEHIVE; diff --git a/patches/server/0346-Don-t-load-Chunks-from-Hoppers-and-other-things.patch b/patches/server/0346-Don-t-load-Chunks-from-Hoppers-and-other-things.patch deleted file mode 100644 index 71f28eb22f..0000000000 --- a/patches/server/0346-Don-t-load-Chunks-from-Hoppers-and-other-things.patch +++ /dev/null @@ -1,32 +0,0 @@ -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/DoubleBlockCombiner.java b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -index ff2a7b08fe70adaecdaa508baadcfe40416519e0..6c334703c816d2a04f97006c5796c658f34a12a4 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -+++ b/src/main/java/net/minecraft/world/level/block/DoubleBlockCombiner.java -@@ -25,7 +25,12 @@ public class DoubleBlockCombiner { - return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); - } else { - BlockPos blockPos = pos.relative(function.apply(state)); -- BlockState blockState = world.getBlockState(blockPos); -+ // Paper start -+ BlockState blockState = world.getBlockStateIfLoaded(blockPos); -+ if (blockState == null) { -+ return new DoubleBlockCombiner.NeighborCombineResult.Single<>(blockEntity); -+ } -+ // Paper end - if (blockState.is(state.getBlock())) { - DoubleBlockCombiner.BlockType blockType2 = typeMapper.apply(blockState); - if (blockType2 != DoubleBlockCombiner.BlockType.SINGLE && blockType != blockType2 && blockState.getValue(directionProperty) == state.getValue(directionProperty)) { diff --git a/patches/server/0346-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0346-Guard-against-serializing-mismatching-chunk-coordina.patch new file mode 100644 index 0000000000..bc391d7a2a --- /dev/null +++ b/patches/server/0346-Guard-against-serializing-mismatching-chunk-coordina.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 27 Dec 2019 09:42:26 -0800 +Subject: [PATCH] Guard against serializing mismatching chunk coordinate + +Should help if something dumb happens + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 8480310bd389ad55b8138a20da59cbcffb973819..864e591b10360b0f12fe5c5a650da372555ebd10 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -84,6 +84,18 @@ public class ChunkSerializer { + + public ChunkSerializer() {} + ++ // Paper start - guard against serializing mismatching coordinates ++ // TODO Note: This needs to be re-checked each update ++ public static ChunkPos getChunkCoordinate(CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); ++ } else { ++ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); ++ } ++ } ++ // Paper end + // Paper start + public static final class InProgressChunkHolder { + +@@ -109,7 +121,7 @@ public class ChunkSerializer { + public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { + java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); + // Paper end +- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); ++ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { + ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 694778b5c23dbe9c8603c3483476b5252aa079bc..315be30daf0be84efbb4d634dc01e1bf9e6e696e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -178,6 +178,13 @@ public class ChunkStorage implements AutoCloseable { + + // Paper start - async chunk io + public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException { ++ // Paper start ++ if (!chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { ++ String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap)this).level.getWorld().getName() : null; ++ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos.toString() ++ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt).toString() + (world == null ? " for an unknown world" : (" for world: " + world))); ++ } ++ // Paper end + this.regionFileCache.write(chunkPos, nbt); + // Paper end - Async chunk loading + if (this.legacyStructureHandler != null) { diff --git a/patches/server/0347-Guard-against-serializing-mismatching-chunk-coordina.patch b/patches/server/0347-Guard-against-serializing-mismatching-chunk-coordina.patch deleted file mode 100644 index bc391d7a2a..0000000000 --- a/patches/server/0347-Guard-against-serializing-mismatching-chunk-coordina.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 27 Dec 2019 09:42:26 -0800 -Subject: [PATCH] Guard against serializing mismatching chunk coordinate - -Should help if something dumb happens - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 8480310bd389ad55b8138a20da59cbcffb973819..864e591b10360b0f12fe5c5a650da372555ebd10 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -84,6 +84,18 @@ public class ChunkSerializer { - - public ChunkSerializer() {} - -+ // Paper start - guard against serializing mismatching coordinates -+ // TODO Note: This needs to be re-checked each update -+ public static ChunkPos getChunkCoordinate(CompoundTag chunkData) { -+ final int dataVersion = ChunkStorage.getVersion(chunkData); -+ if (dataVersion < 2842) { // Level tag is removed after this version -+ final CompoundTag levelData = chunkData.getCompound("Level"); -+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos")); -+ } else { -+ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos")); -+ } -+ } -+ // Paper end - // Paper start - public static final class InProgressChunkHolder { - -@@ -109,7 +121,7 @@ public class ChunkSerializer { - public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { - java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); - // Paper end -- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); -+ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { - ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1}); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 694778b5c23dbe9c8603c3483476b5252aa079bc..315be30daf0be84efbb4d634dc01e1bf9e6e696e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -178,6 +178,13 @@ public class ChunkStorage implements AutoCloseable { - - // Paper start - async chunk io - public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException { -+ // Paper start -+ if (!chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) { -+ String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap)this).level.getWorld().getName() : null; -+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos.toString() -+ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt).toString() + (world == null ? " for an unknown world" : (" for world: " + world))); -+ } -+ // Paper end - this.regionFileCache.write(chunkPos, nbt); - // Paper end - Async chunk loading - if (this.legacyStructureHandler != null) { diff --git a/patches/server/0347-Optimise-IEntityAccess-getPlayerByUUID.patch b/patches/server/0347-Optimise-IEntityAccess-getPlayerByUUID.patch new file mode 100644 index 0000000000..3a47472ec8 --- /dev/null +++ b/patches/server/0347-Optimise-IEntityAccess-getPlayerByUUID.patch @@ -0,0 +1,26 @@ +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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 67a17a0fad369c412a943bb21b574c9cd5031fb8..3cf5ad6a77659073b740a2be3946a39c110dd4b8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -394,6 +394,14 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; + // Paper end + ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ return this.getServer().getPlayerList().getPlayer(uuid); ++ } ++ // Paper end ++ + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error diff --git a/patches/server/0348-Fix-items-not-falling-correctly.patch b/patches/server/0348-Fix-items-not-falling-correctly.patch new file mode 100644 index 0000000000..96b422d772 --- /dev/null +++ b/patches/server/0348-Fix-items-not-falling-correctly.patch @@ -0,0 +1,42 @@ +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 Spigot's entity +activation range check from an item's move method. + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 95dc82a0a9bd8a3fa9c704696e7b3dc48bf4d6a0..ebcf58fe51d1fd0cb8a0f5a84cdd349f29c9442e 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -142,7 +142,7 @@ public class ItemEntity extends Entity { + } + } + +- if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { ++ if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change + this.move(MoverType.SELF, this.getDeltaMovement()); + float f1 = 0.98F; + +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 07e5ece37af6b02210920ce6cc31738274d447a9..7bae24598218dcf0012dd21e619e6f5f984bd6f0 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -251,7 +251,7 @@ public class ActivationRange + isActive = true; + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. +- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) ++ } else if ( !entity.defaultActivationState && entity.tickCount + entity.getId() + 1 % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check + { + isActive = false; + } diff --git a/patches/server/0348-Optimise-IEntityAccess-getPlayerByUUID.patch b/patches/server/0348-Optimise-IEntityAccess-getPlayerByUUID.patch deleted file mode 100644 index 3a47472ec8..0000000000 --- a/patches/server/0348-Optimise-IEntityAccess-getPlayerByUUID.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 11 Jan 2020 21:50:56 -0800 -Subject: [PATCH] Optimise IEntityAccess#getPlayerByUUID - -Use the world entity map instead of iterating over all players - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 67a17a0fad369c412a943bb21b574c9cd5031fb8..3cf5ad6a77659073b740a2be3946a39c110dd4b8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -394,6 +394,14 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; - // Paper end - -+ // Paper start - optimise getPlayerByUUID -+ @Nullable -+ @Override -+ public Player getPlayerByUUID(UUID uuid) { -+ return this.getServer().getPlayerList().getPlayer(uuid); -+ } -+ // Paper end -+ - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error diff --git a/patches/server/0349-Fix-items-not-falling-correctly.patch b/patches/server/0349-Fix-items-not-falling-correctly.patch deleted file mode 100644 index 96b422d772..0000000000 --- a/patches/server/0349-Fix-items-not-falling-correctly.patch +++ /dev/null @@ -1,42 +0,0 @@ -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 Spigot's entity -activation range check from an item's move method. - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 95dc82a0a9bd8a3fa9c704696e7b3dc48bf4d6a0..ebcf58fe51d1fd0cb8a0f5a84cdd349f29c9442e 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -142,7 +142,7 @@ public class ItemEntity extends Entity { - } - } - -- if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { -+ if (!this.onGround || this.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D || (this.tickCount + this.getId()) % 4 == 0) { // Paper - Diff on change - this.move(MoverType.SELF, this.getDeltaMovement()); - float f1 = 0.98F; - -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 07e5ece37af6b02210920ce6cc31738274d447a9..7bae24598218dcf0012dd21e619e6f5f984bd6f0 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -251,7 +251,7 @@ public class ActivationRange - isActive = true; - } - // Add a little performance juice to active entities. Skip 1/4 if not immune. -- } else if ( !entity.defaultActivationState && entity.tickCount % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) -+ } else if ( !entity.defaultActivationState && entity.tickCount + entity.getId() + 1 % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check - { - isActive = false; - } diff --git a/patches/server/0349-Lag-compensate-eating.patch b/patches/server/0349-Lag-compensate-eating.patch new file mode 100644 index 0000000000..2619ab0fc9 --- /dev/null +++ b/patches/server/0349-Lag-compensate-eating.patch @@ -0,0 +1,75 @@ +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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index df35685a016376fa056a8ecbfda2c01b38350b3c..7e7128973153f4c3a737c1e956e41bab0e85c69a 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3564,6 +3564,11 @@ public abstract class LivingEntity extends Entity { + return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; + } + ++ // Paper start - lag compensate eating ++ protected long eatStartTime; ++ protected int totalEatTimeTicks; ++ // Paper end ++ + private void updatingUsingItem() { + if (this.isUsingItem()) { + if (ItemStack.isSameIgnoreDurability(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { +@@ -3581,8 +3586,12 @@ public abstract class LivingEntity extends Entity { + if (this.shouldTriggerItemUseEffects()) { + this.triggerItemUseEffects(stack, 5); + } +- +- if (--this.useItemRemaining == 0 && !this.level.isClientSide && !stack.useOnRelease()) { ++ // Paper start - lag compensate eating ++ // we add 1 to the expected time to avoid lag compensating when we should not ++ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); ++ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level.isClientSide && !this.useItem.useOnRelease()) { ++ this.useItemRemaining = 0; ++ // Paper end + this.completeUsingItem(); + } + +@@ -3630,7 +3639,10 @@ public abstract class LivingEntity extends Entity { + + if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag + this.useItem = itemstack; +- this.useItemRemaining = itemstack.getUseDuration(); ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(); ++ this.eatStartTime = System.nanoTime(); ++ // Paper end + if (!this.level.isClientSide) { + this.setLivingEntityFlag(1, true); + this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND); +@@ -3655,7 +3667,10 @@ public abstract class LivingEntity extends Entity { + } + } else if (!this.isUsingItem() && !this.useItem.isEmpty()) { + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + } + +@@ -3788,7 +3803,10 @@ public abstract class LivingEntity extends Entity { + } + + this.useItem = ItemStack.EMPTY; +- this.useItemRemaining = 0; ++ // Paper start - lag compensate eating ++ this.useItemRemaining = this.totalEatTimeTicks = 0; ++ this.eatStartTime = -1L; ++ // Paper end + } + + public boolean isBlocking() { diff --git a/patches/server/0350-Lag-compensate-eating.patch b/patches/server/0350-Lag-compensate-eating.patch deleted file mode 100644 index 5e5385d34c..0000000000 --- a/patches/server/0350-Lag-compensate-eating.patch +++ /dev/null @@ -1,75 +0,0 @@ -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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 590b8af46f8e060aa568dde50025b4f4c6bb162e..383a892a5c34ec5e8f7d102f5a9bec11ae193c0e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3542,6 +3542,11 @@ public abstract class LivingEntity extends Entity { - return ((Byte) this.entityData.get(LivingEntity.DATA_LIVING_ENTITY_FLAGS) & 2) > 0 ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND; - } - -+ // Paper start - lag compensate eating -+ protected long eatStartTime; -+ protected int totalEatTimeTicks; -+ // Paper end -+ - private void updatingUsingItem() { - if (this.isUsingItem()) { - if (ItemStack.isSameIgnoreDurability(this.getItemInHand(this.getUsedItemHand()), this.useItem)) { -@@ -3559,8 +3564,12 @@ public abstract class LivingEntity extends Entity { - if (this.shouldTriggerItemUseEffects()) { - this.triggerItemUseEffects(stack, 5); - } -- -- if (--this.useItemRemaining == 0 && !this.level.isClientSide && !stack.useOnRelease()) { -+ // Paper start - lag compensate eating -+ // we add 1 to the expected time to avoid lag compensating when we should not -+ boolean shouldLagCompensate = this.useItem.getItem().isEdible() && this.eatStartTime != -1 && (System.nanoTime() - this.eatStartTime) > ((1 + this.totalEatTimeTicks) * 50 * (1000 * 1000)); -+ if ((--this.useItemRemaining == 0 || shouldLagCompensate) && !this.level.isClientSide && !this.useItem.useOnRelease()) { -+ this.useItemRemaining = 0; -+ // Paper end - this.completeUsingItem(); - } - -@@ -3608,7 +3617,10 @@ public abstract class LivingEntity extends Entity { - - if (!itemstack.isEmpty() && !this.isUsingItem() || forceUpdate) { // Paper use override flag - this.useItem = itemstack; -- this.useItemRemaining = itemstack.getUseDuration(); -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = itemstack.getUseDuration(); -+ this.eatStartTime = System.nanoTime(); -+ // Paper end - if (!this.level.isClientSide) { - this.setLivingEntityFlag(1, true); - this.setLivingEntityFlag(2, hand == InteractionHand.OFF_HAND); -@@ -3633,7 +3645,10 @@ public abstract class LivingEntity extends Entity { - } - } else if (!this.isUsingItem() && !this.useItem.isEmpty()) { - this.useItem = ItemStack.EMPTY; -- this.useItemRemaining = 0; -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = 0; -+ this.eatStartTime = -1L; -+ // Paper end - } - } - -@@ -3766,7 +3781,10 @@ public abstract class LivingEntity extends Entity { - } - - this.useItem = ItemStack.EMPTY; -- this.useItemRemaining = 0; -+ // Paper start - lag compensate eating -+ this.useItemRemaining = this.totalEatTimeTicks = 0; -+ this.eatStartTime = -1L; -+ // Paper end - } - - public boolean isBlocking() { diff --git a/patches/server/0350-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0350-Optimize-call-to-getFluid-for-explosions.patch new file mode 100644 index 0000000000..952f1fc45d --- /dev/null +++ b/patches/server/0350-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 af9645a3ef9dab1134977b30937f15fa3fa27a95..38bb502e9f1272020a23a3ef8ebb0cb1a5a251ef 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -172,7 +172,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = new BlockPos(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); +- FluidState fluid = this.level.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper + + if (!this.level.isInWorldBounds(blockposition)) { + break; diff --git a/patches/server/0351-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/patches/server/0351-Fix-last-firework-in-stack-not-having-effects-when-d.patch new file mode 100644 index 0000000000..448854642c --- /dev/null +++ b/patches/server/0351-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/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 6219b89cf04febe0a8ceb93b4212827c297cf7aa..15fc100e468e68cbb0c43363c0eb25dc2ef8c6e0 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -510,7 +510,7 @@ public interface DispenseItemBehavior { + } + + itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); +- FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), stack, pointer.x(), pointer.y(), pointer.x(), true); ++ FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), itemstack1, pointer.x(), pointer.y(), pointer.x(), true); // Paper - GH-2871 - fix last firework in stack having no effects when dispensed + + DispenseItemBehavior.setEntityPokingOutOfBlock(pointer, entityfireworks, enumdirection); + entityfireworks.shoot((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), 0.5F, 1.0F); diff --git a/patches/server/0351-Optimize-call-to-getFluid-for-explosions.patch b/patches/server/0351-Optimize-call-to-getFluid-for-explosions.patch deleted file mode 100644 index 952f1fc45d..0000000000 --- a/patches/server/0351-Optimize-call-to-getFluid-for-explosions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BrodyBeckwith -Date: Tue, 14 Jan 2020 17:49:03 -0500 -Subject: [PATCH] Optimize call to getFluid for explosions - - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index af9645a3ef9dab1134977b30937f15fa3fa27a95..38bb502e9f1272020a23a3ef8ebb0cb1a5a251ef 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -172,7 +172,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = new BlockPos(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -- FluidState fluid = this.level.getFluidState(blockposition); -+ FluidState fluid = iblockdata.getFluidState(); // Paper - - if (!this.level.isInWorldBounds(blockposition)) { - break; diff --git a/patches/server/0352-Add-effect-to-block-break-naturally.patch b/patches/server/0352-Add-effect-to-block-break-naturally.patch new file mode 100644 index 0000000000..b911824bb1 --- /dev/null +++ b/patches/server/0352-Add-effect-to-block-break-naturally.patch @@ -0,0 +1,37 @@ +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 4ae3c0b2c240f01a1fcea0b02616e91e057a297a..ea3358905dcd39a1a855d98570457c65dcd7513d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -475,6 +475,18 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally(ItemStack item) { ++ // Paper start ++ return breakNaturally(item, false); ++ } ++ ++ @Override ++ public boolean breakNaturally(boolean triggerEffect) { ++ return breakNaturally(null, triggerEffect); ++ } ++ ++ @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.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); +@@ -484,6 +496,7 @@ public class CraftBlock implements Block { + // Modelled off EntityHuman#hasBlock + if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { + net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), position, this.world.getBlockEntity(position), null, nmsItem); ++ if (triggerEffect) world.levelEvent(org.bukkit.Effect.STEP_SOUND.getId(), position, net.minecraft.world.level.block.Block.getId(block.defaultBlockState())); // Paper + result = true; + } + diff --git a/patches/server/0352-Fix-last-firework-in-stack-not-having-effects-when-d.patch b/patches/server/0352-Fix-last-firework-in-stack-not-having-effects-when-d.patch deleted file mode 100644 index 448854642c..0000000000 --- a/patches/server/0352-Fix-last-firework-in-stack-not-having-effects-when-d.patch +++ /dev/null @@ -1,23 +0,0 @@ -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/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 6219b89cf04febe0a8ceb93b4212827c297cf7aa..15fc100e468e68cbb0c43363c0eb25dc2ef8c6e0 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -510,7 +510,7 @@ public interface DispenseItemBehavior { - } - - itemstack1 = CraftItemStack.asNMSCopy(event.getItem()); -- FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), stack, pointer.x(), pointer.y(), pointer.x(), true); -+ FireworkRocketEntity entityfireworks = new FireworkRocketEntity(pointer.getLevel(), itemstack1, pointer.x(), pointer.y(), pointer.x(), true); // Paper - GH-2871 - fix last firework in stack having no effects when dispensed - - DispenseItemBehavior.setEntityPokingOutOfBlock(pointer, entityfireworks, enumdirection); - entityfireworks.shoot((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), 0.5F, 1.0F); diff --git a/patches/server/0353-Add-effect-to-block-break-naturally.patch b/patches/server/0353-Add-effect-to-block-break-naturally.patch deleted file mode 100644 index b911824bb1..0000000000 --- a/patches/server/0353-Add-effect-to-block-break-naturally.patch +++ /dev/null @@ -1,37 +0,0 @@ -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 4ae3c0b2c240f01a1fcea0b02616e91e057a297a..ea3358905dcd39a1a855d98570457c65dcd7513d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -475,6 +475,18 @@ public class CraftBlock implements Block { - - @Override - public boolean breakNaturally(ItemStack item) { -+ // Paper start -+ return breakNaturally(item, false); -+ } -+ -+ @Override -+ public boolean breakNaturally(boolean triggerEffect) { -+ return breakNaturally(null, triggerEffect); -+ } -+ -+ @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.BlockState iblockdata = this.getNMS(); - net.minecraft.world.level.block.Block block = iblockdata.getBlock(); -@@ -484,6 +496,7 @@ public class CraftBlock implements Block { - // Modelled off EntityHuman#hasBlock - if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { - net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), position, this.world.getBlockEntity(position), null, nmsItem); -+ if (triggerEffect) world.levelEvent(org.bukkit.Effect.STEP_SOUND.getId(), position, net.minecraft.world.level.block.Block.getId(block.defaultBlockState())); // Paper - result = true; - } - diff --git a/patches/server/0353-Entity-Activation-Range-2.0.patch b/patches/server/0353-Entity-Activation-Range-2.0.patch new file mode 100644 index 0000000000..701c64d52c --- /dev/null +++ b/patches/server/0353-Entity-Activation-Range-2.0.patch @@ -0,0 +1,808 @@ +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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3cf5ad6a77659073b740a2be3946a39c110dd4b8..21fdca3b4bdc49d32deaf3f11d5fecc1ed0d4626 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2,7 +2,6 @@ package net.minecraft.server.level; + + import com.google.common.annotations.VisibleForTesting; + import co.aikar.timings.TimingHistory; // Paper +-import co.aikar.timings.Timings; // Paper + import com.google.common.collect.Lists; + import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; +@@ -993,17 +992,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper +- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { ++ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below + entity.tickCount++; + timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings + entity.inactiveTick(); + } finally { timer.stopTiming(); } // Paper + return; +- } ++ }*/ // Paper - comment out EAR 2 + // Spigot end + // Paper start- timings +- TimingHistory.activatedEntityTicks++; +- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); ++ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper + try { + // Paper end - timings + entity.setOldPosAndRot(); +@@ -1014,9 +1013,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + return Registry.ENTITY_TYPE.getKey(entity.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickNonPassenger"); ++ if (isActive) { // Paper - EAR 2 ++ TimingHistory.activatedEntityTicks++; + entity.tick(); + entity.postTick(); // CraftBukkit ++ } else { entity.inactiveTick(); } // Paper - EAR 2 + this.getProfiler().pop(); ++ } finally { timer.stopTiming(); } // Paper - timings + Iterator iterator = entity.getPassengers().iterator(); + + while (iterator.hasNext()) { +@@ -1024,13 +1027,18 @@ public class ServerLevel extends Level implements WorldGenLevel { + + this.tickPassenger(entity, entity1); + } +- } finally { timer.stopTiming(); } // Paper - timings ++ // } finally { timer.stopTiming(); } // Paper - timings - move up + + } + + private void tickPassenger(Entity vehicle, Entity passenger) { + if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { + if (passenger instanceof Player || this.entityTickList.contains(passenger)) { ++ // Paper - EAR 2 ++ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); ++ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper ++ try { ++ // Paper end + passenger.setOldPosAndRot(); + ++passenger.tickCount; + ProfilerFiller gameprofilerfiller = this.getProfiler(); +@@ -1039,8 +1047,17 @@ public class ServerLevel extends Level implements WorldGenLevel { + return Registry.ENTITY_TYPE.getKey(passenger.getType()).toString(); + }); + gameprofilerfiller.incrementCounter("tickPassenger"); ++ // Paper start - EAR 2 ++ if (isActive) { + passenger.rideTick(); + passenger.postTick(); // CraftBukkit ++ } else { ++ passenger.setDeltaMovement(Vec3.ZERO); ++ passenger.inactiveTick(); ++ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary ++ vehicle.positionRider(passenger); ++ } ++ // Paper end - EAR 2 + gameprofilerfiller.pop(); + Iterator iterator = passenger.getPassengers().iterator(); + +@@ -1050,6 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.tickPassenger(passenger, entity2); + } + ++ } finally { timer.stopTiming(); }// Paper - EAR2 timings + } + } else { + passenger.stopRiding(); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a2a607e6ae867a4cabe1c2280154c99e93d0e7e9..51fda8b02c6259540db220a55dd8ee1aa651a50c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -384,6 +384,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public void inactiveTick() { } + // Spigot end + // Paper start ++ public long activatedImmunityTick = Integer.MIN_VALUE; // Paper ++ public boolean isTemporarilyActive = false; // Paper + protected int numCollisions = 0; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + @javax.annotation.Nullable +@@ -857,6 +859,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } else { + this.wasOnFire = this.isOnFire(); + if (movementType == MoverType.PISTON) { ++ this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper + movement = this.limitPistonMovement(movement); + if (movement.equals(Vec3.ZERO)) { + return; +@@ -869,6 +873,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.stuckSpeedMultiplier = Vec3.ZERO; + this.setDeltaMovement(Vec3.ZERO); + } ++ // Paper start - ignore movement changes while inactive. ++ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) { ++ setDeltaMovement(Vec3.ZERO); ++ this.level.getProfiler().pop(); ++ return; ++ } ++ // Paper end + + movement = this.maybeBackOffFromEdge(movement, movementType); + Vec3 vec3d1 = this.collide(movement); +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 37102e8cdaeb558e80889ff553656f14eaaeb650..d7b137a84deea68c75ee0b3c99b089b8dff25947 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -210,6 +210,19 @@ public abstract class Mob extends LivingEntity { + return this.lookControl; + } + ++ // Paper start ++ @Override ++ public void inactiveTick() { ++ super.inactiveTick(); ++ if (this.goalSelector.inactiveTick()) { ++ this.goalSelector.tick(); ++ } ++ if (this.targetSelector.inactiveTick()) { ++ this.targetSelector.tick(); ++ } ++ } ++ // Paper end ++ + public MoveControl getMoveControl() { + if (this.isPassenger() && this.getVehicle() instanceof Mob) { + Mob entityinsentient = (Mob) this.getVehicle(); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 2df5b50be11297941d13ec9d17001f488af11750..3db309e709cd72e3aae184ff2f8b1a7b98f2c7a8 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -19,6 +19,7 @@ public abstract class PathfinderMob extends Mob { + } + + public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper ++ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper + + public float getWalkTargetValue(BlockPos pos) { + return this.getWalkTargetValue(pos, this.level); +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 3e981fbf81f21b40652f7a05d4a7a37065db4b00..19ee04dd92b39a775260f832ca8880335d24988b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -32,6 +32,7 @@ public class GoalSelector { + private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); + private int tickCount; + private int newGoalRate = 3; ++ private int curRate; + + public GoalSelector(Supplier profiler) { + this.profiler = profiler; +@@ -46,6 +47,20 @@ public class GoalSelector { + this.availableGoals.clear(); + } + ++ // Paper start ++ public boolean inactiveTick() { ++ this.curRate++; ++ return this.curRate % this.newGoalRate == 0; ++ } ++ public boolean hasTasks() { ++ for (WrappedGoal task : this.availableGoals) { ++ if (task.isRunning()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ // Paper end + public void removeGoal(Goal goal) { + this.availableGoals.stream().filter((wrappedGoal) -> { + return wrappedGoal.getGoal() == goal; +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 6efba52c2e5d7811ee329ed22c1c76f75d7ddbe1..26bf383caea68834c654b25653ced9017f1b1b22 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -14,7 +14,7 @@ public abstract class MoveToBlockGoal extends Goal { + protected int nextStartTick; + protected int tryTicks; + private int maxStayTicks; +- protected BlockPos blockPos = BlockPos.ZERO; @Deprecated public final BlockPos getTargetPosition() { return this.blockPos; } // Paper - OBFHELPER ++ protected BlockPos blockPos = BlockPos.ZERO; @Deprecated public final BlockPos getTargetPosition() { return this.blockPos; } @Deprecated public void setTargetPosition(BlockPos pos) { this.blockPos = pos; mob.movingTarget = pos != BlockPos.ZERO ? pos : null; } // Paper - OBFHELPER + private boolean reachedTarget; + private final int searchRange; + private final int verticalSearchRange; +@@ -23,6 +23,13 @@ public abstract class MoveToBlockGoal extends Goal { + public MoveToBlockGoal(PathfinderMob mob, double speed, int range) { + this(mob, speed, range, 1); + } ++ // Paper start - activation range improvements ++ @Override ++ public void stop() { ++ super.stop(); ++ setTargetPosition(BlockPos.ZERO); ++ } ++ // Paper end + + public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) { + this.mob = mob; +@@ -114,6 +121,7 @@ public abstract class MoveToBlockGoal extends Goal { + mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); + if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level, mutableBlockPos)) { + this.blockPos = mutableBlockPos; ++ setTargetPosition(mutableBlockPos.immutable()); // Paper + return true; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index f957c0aca36b7228ac3a33ca04c948b1d10642d1..39fc94b1e1555fd6706391223dd2783139b16016 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -225,17 +225,29 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void inactiveTick() { + // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( +- if (level.spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { +- this.customServerAiStep(); ++ // Paper start ++ if (this.getUnhappyCounter() > 0) { ++ this.setUnhappyCounter(this.getUnhappyCounter() - 1); ++ } ++ if (this.isEffectiveAi()) { ++ if (level.spigotConfig.tickInactiveVillagers) { ++ this.customServerAiStep(); ++ } else { ++ this.mobTick(true); ++ } + } ++ maybeDecayGossip(); ++ // Paper end ++ + super.inactiveTick(); + } + // Spigot End + + @Override +- protected void customServerAiStep() { ++ protected void customServerAiStep() { mobTick(false); } ++ protected void mobTick(boolean inactive) { + this.level.getProfiler().push("villagerBrain"); +- this.getBrain().tick((ServerLevel) this.level, this); ++ if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // Paper + this.level.getProfiler().pop(); + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; +@@ -259,7 +271,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.lastTradedPlayer = null; + } + +- if (!this.isNoAi() && this.random.nextInt(100) == 0) { ++ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper + Raid raid = ((ServerLevel) this.level).getRaidAt(this.blockPosition()); + + if (raid != null && raid.isActive() && !raid.isOver()) { +@@ -270,6 +282,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) { + this.stopTrading(); + } ++ if (inactive) return; // Paper + + super.customServerAiStep(); + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java +index 540c23f6297c34cf8e7bf8312ceaa5fc868f414c..2866385a64b22b7dc82b6122c62bcea6b0908a60 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java +@@ -57,6 +57,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + if (bl != this.isEnabled()) { + this.setEnabled(bl); + } ++ this.immunize(); // Paper + + } + +@@ -107,11 +108,13 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + + public boolean suckInItems() { + if (HopperBlockEntity.suckInItems(this.level, this)) { ++ this.immunize(); // Paper + return true; + } else { + List list = this.level.getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.25D, 0.0D, 0.25D), EntitySelector.ENTITY_STILL_ALIVE); + if (!list.isEmpty()) { + HopperBlockEntity.addItem(this, list.get(0)); ++ this.immunize(); // Paper + } + + return false; +@@ -149,4 +152,11 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper + public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { + return new HopperMenu(syncId, playerInventory, this); + } ++ ++ // Paper start ++ public void immunize() { ++ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); ++ } ++ // Paper end ++ + } +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 244035be96c5218a27e70c3f80ee9d7dcb4419d5..75e74a6bc0072eb9e77a0570d02d050ade0fa92d 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -158,6 +158,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public Map capturedTileEntities = new HashMap<>(); + public List captureDrops; + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); ++ // Paper start ++ public int wakeupInactiveRemainingAnimals; ++ public int wakeupInactiveRemainingFlying; ++ public int wakeupInactiveRemainingMonsters; ++ public int wakeupInactiveRemainingVillagers; ++ // Paper end + public boolean populating; + public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot + // Paper start +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index 4b55b667eebfe50dfeda89015112e275e71b9777..dda0b32a4989bbead35a2219a969a30ba0e975b0 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -140,6 +140,10 @@ public class PistonMovingBlockEntity extends BlockEntity { + } + + entity.setDeltaMovement(e, g, h); ++ // Paper - EAR items stuck in in slime pushed by a piston ++ entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10); ++ entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10); ++ // Paper end + break; + } + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 7bae24598218dcf0012dd21e619e6f5f984bd6f0..0508f43ad396679d3372ae4caf029086a1524109 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -1,39 +1,52 @@ + package org.spigotmc; + ++import net.minecraft.core.BlockPos; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.world.entity.Entity; + import net.minecraft.world.entity.ExperienceOrb; ++import net.minecraft.world.entity.FlyingMob; + import net.minecraft.world.entity.LightningBolt; + import net.minecraft.world.entity.LivingEntity; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.ai.Brain; + import net.minecraft.world.entity.ambient.AmbientCreature; + import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Bee; + import net.minecraft.world.entity.animal.Sheep; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.horse.Llama; + import net.minecraft.world.entity.boss.EnderDragonPart; + import net.minecraft.world.entity.boss.enderdragon.EndCrystal; + import net.minecraft.world.entity.boss.enderdragon.EnderDragon; + import net.minecraft.world.entity.boss.wither.WitherBoss; + import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.monster.Creeper; +-import net.minecraft.world.entity.monster.Monster; +-import net.minecraft.world.entity.monster.Slime; ++import net.minecraft.world.entity.monster.Enemy; ++import net.minecraft.world.entity.monster.Pillager; + import net.minecraft.world.entity.npc.Villager; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; ++import net.minecraft.world.entity.projectile.EyeOfEnder; + import net.minecraft.world.entity.projectile.FireworkRocketEntity; + import net.minecraft.world.entity.projectile.ThrowableProjectile; + import net.minecraft.world.entity.projectile.ThrownTrident; + import net.minecraft.world.entity.raid.Raider; ++import co.aikar.timings.MinecraftTimings; ++import net.minecraft.world.entity.schedule.Activity; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.AABB; +-import co.aikar.timings.MinecraftTimings; + + public class ActivationRange + { + + public enum ActivationType + { ++ WATER, // Paper ++ FLYING_MONSTER, // Paper ++ VILLAGER, // Paper + MONSTER, + ANIMAL, + RAIDER, +@@ -41,6 +54,43 @@ public class ActivationRange + + AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 ); + } ++ // Paper start ++ ++ static Activity[] VILLAGER_PANIC_IMMUNITIES = { ++ Activity.HIDE, ++ Activity.PRE_RAID, ++ Activity.RAID, ++ Activity.PANIC ++ }; ++ ++ private static int checkInactiveWakeup(Entity entity) { ++ Level world = entity.level; ++ SpigotWorldConfig config = world.spigotConfig; ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ if (entity.activationType == ActivationType.VILLAGER) { ++ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { ++ world.wakeupInactiveRemainingVillagers--; ++ return config.wakeUpInactiveVillagersFor; ++ } ++ } else if (entity.activationType == ActivationType.ANIMAL) { ++ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { ++ world.wakeupInactiveRemainingAnimals--; ++ return config.wakeUpInactiveAnimalsFor; ++ } ++ } else if (entity.activationType == ActivationType.FLYING_MONSTER) { ++ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { ++ world.wakeupInactiveRemainingFlying--; ++ return config.wakeUpInactiveFlyingFor; ++ } ++ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { ++ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { ++ world.wakeupInactiveRemainingMonsters--; ++ return config.wakeUpInactiveMonstersFor; ++ } ++ } ++ return -1; ++ } ++ // Paper end + + static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 ); + +@@ -53,10 +103,13 @@ public class ActivationRange + */ + public static ActivationType initializeEntityActivationType(Entity entity) + { ++ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper ++ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper ++ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future + if ( entity instanceof Raider ) + { + return ActivationType.RAIDER; +- } else if ( entity instanceof Monster || entity instanceof Slime ) ++ } else if ( entity instanceof Enemy ) // Paper - correct monster check + { + return ActivationType.MONSTER; + } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature ) +@@ -77,10 +130,14 @@ public class ActivationRange + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { +- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) +- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) +- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) +- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) ++ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 ) ++ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper ++ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper ++ || entity instanceof EyeOfEnder // Paper + || entity instanceof Player + || entity instanceof ThrowableProjectile + || entity instanceof EnderDragon +@@ -113,10 +170,25 @@ public class ActivationRange + final int raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; ++ // Paper start ++ final int waterActivationRange = world.spigotConfig.waterActivationRange; ++ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; ++ final int villagerActivationRange = world.spigotConfig.villagerActivationRange; ++ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); ++ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); ++ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); ++ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); ++ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource(); ++ // Paper end + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, raiderActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); ++ // Paper start ++ maxRange = Math.max( maxRange, flyingActivationRange ); ++ maxRange = Math.max( maxRange, waterActivationRange ); ++ maxRange = Math.max( maxRange, villagerActivationRange ); ++ // Paper end + maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange ); + + for ( Player player : world.players() ) +@@ -127,11 +199,17 @@ public class ActivationRange + continue; + } + +- ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange ); +- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange ); +- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); +- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); +- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); ++ // Paper start ++ int worldHeight = world.getHeight(); ++ ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); ++ ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, worldHeight, miscActivationRange ); ++ ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, worldHeight, raiderActivationRange ); ++ ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, worldHeight, animalActivationRange ); ++ ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, worldHeight, monsterActivationRange ); ++ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, worldHeight, waterActivationRange ); ++ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, worldHeight, flyingActivationRange ); ++ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); ++ // Paper end + + world.getEntities().get(maxBB, ActivationRange::activateEntity); + } +@@ -166,60 +244,118 @@ public class ActivationRange + * @param entity + * @return + */ +- public static boolean checkEntityImmunities(Entity entity) ++ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity + { ++ // Paper start ++ SpigotWorldConfig config = entity.level.spigotConfig; ++ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); ++ if (inactiveWakeUpImmunity > -1) { ++ return inactiveWakeUpImmunity; ++ } ++ if (entity.remainingFireTicks > 0) { ++ return 2; ++ } ++ if (entity.activatedImmunityTick >= MinecraftServer.currentTick) { ++ return 1; ++ } ++ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; ++ // Paper end + // quick checks. +- if ( entity.wasTouchingWater || entity.remainingFireTicks > 0 ) ++ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper + { +- return true; ++ return 100; // Paper ++ } ++ // Paper start ++ if ( !entity.isOnGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D ) ++ { ++ return 100; + } ++ // Paper end + if ( !( entity instanceof AbstractArrow ) ) + { +- if ( !entity.isOnGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) ++ if ( (!entity.isOnGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic + { +- return true; ++ return 10; // Paper + } + } else if ( !( (AbstractArrow) entity ).inGround ) + { +- return true; ++ return 1; // Paper + } + // special cases. + if ( entity instanceof LivingEntity ) + { + LivingEntity living = (LivingEntity) entity; +- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 ) ++ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper + { +- return true; ++ return 1; // Paper + } +- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null ) ++ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper + { +- return true; ++ return 20; // Paper + } +- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() ) ++ // Paper start ++ if (entity instanceof Bee) { ++ Bee bee = (Bee)entity; ++ BlockPos movingTarget = bee.getMovingTarget(); ++ if (bee.isAngry() || ++ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || ++ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget)) ++ ) { ++ return 20; ++ } ++ } ++ if ( entity instanceof Villager ) { ++ Brain behaviorController = ((Villager) entity).getBrain(); ++ ++ if (config.villagersActiveForPanic) { ++ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { ++ if (behaviorController.isActive(activity)) { ++ return 20*5; ++ } ++ } ++ } ++ ++ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { ++ if (behaviorController.isActive(Activity.WORK)) { ++ return config.villagersWorkImmunityFor; ++ } ++ } ++ } ++ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() ) + { +- return true; ++ return 1; + } ++ // Paper end + if ( entity instanceof Animal ) + { + Animal animal = (Animal) entity; + if ( animal.isBaby() || animal.isInLove() ) + { +- return true; ++ return 5; // Paper + } + if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() ) + { +- return true; ++ return 1; // Paper + } + } + if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive +- return true; ++ return 20; // Paper ++ } ++ // Paper start ++ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) { ++ return 0; ++ } ++ if (entity instanceof Pillager) { ++ Pillager pillager = (Pillager) entity; ++ // TODO:? + } ++ // Paper end + } + // SPIGOT-6644: Otherwise the target refresh tick will be missed + if (entity instanceof ExperienceOrb) { +- return true; ++ return 20; // Paper + } +- return false; ++ return -1; // Paper + } + + /** +@@ -234,8 +370,19 @@ public class ActivationRange + if ( entity instanceof FireworkRocketEntity ) { + return true; + } ++ // Paper start - special case always immunities ++ // immunize brand new entities, dead entities, and portal scenarios ++ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) { ++ return true; ++ } ++ // immunize leashed entities ++ if (entity instanceof Mob && ((Mob)entity).leashHolder instanceof Player) { ++ return true; ++ } ++ // Paper end + +- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; ++ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick; ++ entity.isTemporarilyActive = false; // Paper + + // Should this entity tick? + if ( !isActive ) +@@ -243,15 +390,19 @@ public class ActivationRange + if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) + { + // Check immunities every 20 ticks. +- if ( ActivationRange.checkEntityImmunities( entity ) ) +- { +- // Triggered some sort of immunity, give 20 full ticks before we check again. +- entity.activatedTick = MinecraftServer.currentTick + 20; ++ // Paper start ++ int immunity = checkEntityImmunities(entity); ++ if (immunity >= 0) { ++ entity.activatedTick = MinecraftServer.currentTick + immunity; ++ } else { ++ entity.isTemporarilyActive = true; + } ++ // Paper end + isActive = true; ++ + } + // Add a little performance juice to active entities. Skip 1/4 if not immune. +- } else if ( !entity.defaultActivationState && entity.tickCount + entity.getId() + 1 % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check ++ } else if ( entity.tickCount + entity.getId() + 1 % 4 == 0 && ActivationRange.checkEntityImmunities( entity ) < 0 ) // Paper + { + isActive = false; + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index d4d2d11cf19167410ec6ad3417495e7130330d11..9c9723e13b5440d4803a7268057d63cbdc973b77 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -199,14 +199,60 @@ public class SpigotWorldConfig + public int monsterActivationRange = 32; + public int raiderActivationRange = 48; + public int miscActivationRange = 16; ++ // Paper start ++ public int flyingMonsterActivationRange = 32; ++ public int waterActivationRange = 16; ++ public int villagerActivationRange = 32; ++ public int wakeUpInactiveAnimals = 4; ++ public int wakeUpInactiveAnimalsEvery = 60*20; ++ public int wakeUpInactiveAnimalsFor = 5*20; ++ public int wakeUpInactiveMonsters = 8; ++ public int wakeUpInactiveMonstersEvery = 20*20; ++ public int wakeUpInactiveMonstersFor = 5*20; ++ public int wakeUpInactiveVillagers = 4; ++ public int wakeUpInactiveVillagersEvery = 30*20; ++ public int wakeUpInactiveVillagersFor = 5*20; ++ public int wakeUpInactiveFlying = 8; ++ public int wakeUpInactiveFlyingEvery = 10*20; ++ public int wakeUpInactiveFlyingFor = 5*20; ++ public int villagersWorkImmunityAfter = 5*20; ++ public int villagersWorkImmunityFor = 20; ++ public boolean villagersActiveForPanic = true; ++ // Paper end + public boolean tickInactiveVillagers = true; + public boolean ignoreSpectatorActivation = false; + private void activationRange() + { ++ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper + this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange ); + this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange ); + this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange ); + this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange ); ++ // Paper start ++ this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange ); ++ this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange ); ++ this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange ); ++ ++ this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals); ++ this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery); ++ this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor); ++ ++ this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters); ++ this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery); ++ this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor); ++ ++ this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers); ++ this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery); ++ this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor); ++ ++ this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying); ++ this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery); ++ this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor); ++ ++ this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter ); ++ this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor ); ++ this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic ); ++ // Paper end + this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers ); + this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation ); + this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation ); diff --git a/patches/server/0354-Entity-Activation-Range-2.0.patch b/patches/server/0354-Entity-Activation-Range-2.0.patch deleted file mode 100644 index 9dcdbe0cef..0000000000 --- a/patches/server/0354-Entity-Activation-Range-2.0.patch +++ /dev/null @@ -1,808 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 13 May 2016 01:38:06 -0400 -Subject: [PATCH] Entity Activation Range 2.0 - -Optimizes performance of Activation Range - -Adds many new configurations and a new wake up inactive system - -Fixes and adds new Immunities to improve gameplay behavior - -Adds water Mobs to activation range config and nerfs fish -Adds flying monsters to control ghast and phantoms -Adds villagers as separate config - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 3cf5ad6a77659073b740a2be3946a39c110dd4b8..21fdca3b4bdc49d32deaf3f11d5fecc1ed0d4626 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2,7 +2,6 @@ package net.minecraft.server.level; - - import com.google.common.annotations.VisibleForTesting; - import co.aikar.timings.TimingHistory; // Paper --import co.aikar.timings.Timings; // Paper - import com.google.common.collect.Lists; - import com.mojang.datafixers.DataFixer; - import com.mojang.datafixers.util.Pair; -@@ -993,17 +992,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper -- if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { -+ /*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below - entity.tickCount++; - timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings - entity.inactiveTick(); - } finally { timer.stopTiming(); } // Paper - return; -- } -+ }*/ // Paper - comment out EAR 2 - // Spigot end - // Paper start- timings -- TimingHistory.activatedEntityTicks++; -- timer = entity.getVehicle() != null ? entity.getType().passengerTickTimer.startTiming() : entity.getType().tickTimer.startTiming(); -+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); -+ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper - try { - // Paper end - timings - entity.setOldPosAndRot(); -@@ -1014,9 +1013,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - return Registry.ENTITY_TYPE.getKey(entity.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickNonPassenger"); -+ if (isActive) { // Paper - EAR 2 -+ TimingHistory.activatedEntityTicks++; - entity.tick(); - entity.postTick(); // CraftBukkit -+ } else { entity.inactiveTick(); } // Paper - EAR 2 - this.getProfiler().pop(); -+ } finally { timer.stopTiming(); } // Paper - timings - Iterator iterator = entity.getPassengers().iterator(); - - while (iterator.hasNext()) { -@@ -1024,13 +1027,18 @@ public class ServerLevel extends Level implements WorldGenLevel { - - this.tickPassenger(entity, entity1); - } -- } finally { timer.stopTiming(); } // Paper - timings -+ // } finally { timer.stopTiming(); } // Paper - timings - move up - - } - - private void tickPassenger(Entity vehicle, Entity passenger) { - if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { - if (passenger instanceof Player || this.entityTickList.contains(passenger)) { -+ // Paper - EAR 2 -+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); -+ co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper -+ try { -+ // Paper end - passenger.setOldPosAndRot(); - ++passenger.tickCount; - ProfilerFiller gameprofilerfiller = this.getProfiler(); -@@ -1039,8 +1047,17 @@ public class ServerLevel extends Level implements WorldGenLevel { - return Registry.ENTITY_TYPE.getKey(passenger.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickPassenger"); -+ // Paper start - EAR 2 -+ if (isActive) { - passenger.rideTick(); - passenger.postTick(); // CraftBukkit -+ } else { -+ passenger.setDeltaMovement(Vec3.ZERO); -+ passenger.inactiveTick(); -+ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary -+ vehicle.positionRider(passenger); -+ } -+ // Paper end - EAR 2 - gameprofilerfiller.pop(); - Iterator iterator = passenger.getPassengers().iterator(); - -@@ -1050,6 +1067,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.tickPassenger(passenger, entity2); - } - -+ } finally { timer.stopTiming(); }// Paper - EAR2 timings - } - } else { - passenger.stopRiding(); -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 482efc1c544cdd846f7330f4be43cc3e6eb397fc..022682dc729a2e595df09befc18d7d73143b7e74 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -384,6 +384,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - public void inactiveTick() { } - // Spigot end - // Paper start -+ public long activatedImmunityTick = Integer.MIN_VALUE; // Paper -+ public boolean isTemporarilyActive = false; // Paper - protected int numCollisions = 0; // Paper - public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - @javax.annotation.Nullable -@@ -857,6 +859,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } else { - this.wasOnFire = this.isOnFire(); - if (movementType == MoverType.PISTON) { -+ this.activatedTick = Math.max(this.activatedTick, MinecraftServer.currentTick + 20); // Paper -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, MinecraftServer.currentTick + 20); // Paper - movement = this.limitPistonMovement(movement); - if (movement.equals(Vec3.ZERO)) { - return; -@@ -869,6 +873,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.stuckSpeedMultiplier = Vec3.ZERO; - this.setDeltaMovement(Vec3.ZERO); - } -+ // Paper start - ignore movement changes while inactive. -+ if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) { -+ setDeltaMovement(Vec3.ZERO); -+ this.level.getProfiler().pop(); -+ return; -+ } -+ // Paper end - - movement = this.maybeBackOffFromEdge(movement, movementType); - Vec3 vec3d1 = this.collide(movement); -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 2ffc99730c3d5dbdec63881a1eca07d5fbb1754e..1a2e5e8c32a2fabe3b92ded6c630b8258b57bc0f 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -210,6 +210,19 @@ public abstract class Mob extends LivingEntity { - return this.lookControl; - } - -+ // Paper start -+ @Override -+ public void inactiveTick() { -+ super.inactiveTick(); -+ if (this.goalSelector.inactiveTick()) { -+ this.goalSelector.tick(); -+ } -+ if (this.targetSelector.inactiveTick()) { -+ this.targetSelector.tick(); -+ } -+ } -+ // Paper end -+ - public MoveControl getMoveControl() { - if (this.isPassenger() && this.getVehicle() instanceof Mob) { - Mob entityinsentient = (Mob) this.getVehicle(); -diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index 2df5b50be11297941d13ec9d17001f488af11750..3db309e709cd72e3aae184ff2f8b1a7b98f2c7a8 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -19,6 +19,7 @@ public abstract class PathfinderMob extends Mob { - } - - public org.bukkit.craftbukkit.entity.CraftCreature getBukkitCreature() { return (org.bukkit.craftbukkit.entity.CraftCreature) super.getBukkitEntity(); } // Paper -+ public BlockPos movingTarget = null; public BlockPos getMovingTarget() { return movingTarget; } // Paper - - public float getWalkTargetValue(BlockPos pos) { - return this.getWalkTargetValue(pos, this.level); -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -index 3e981fbf81f21b40652f7a05d4a7a37065db4b00..19ee04dd92b39a775260f832ca8880335d24988b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -32,6 +32,7 @@ public class GoalSelector { - private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); - private int tickCount; - private int newGoalRate = 3; -+ private int curRate; - - public GoalSelector(Supplier profiler) { - this.profiler = profiler; -@@ -46,6 +47,20 @@ public class GoalSelector { - this.availableGoals.clear(); - } - -+ // Paper start -+ public boolean inactiveTick() { -+ this.curRate++; -+ return this.curRate % this.newGoalRate == 0; -+ } -+ public boolean hasTasks() { -+ for (WrappedGoal task : this.availableGoals) { -+ if (task.isRunning()) { -+ return true; -+ } -+ } -+ return false; -+ } -+ // Paper end - public void removeGoal(Goal goal) { - this.availableGoals.stream().filter((wrappedGoal) -> { - return wrappedGoal.getGoal() == goal; -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 6efba52c2e5d7811ee329ed22c1c76f75d7ddbe1..26bf383caea68834c654b25653ced9017f1b1b22 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -14,7 +14,7 @@ public abstract class MoveToBlockGoal extends Goal { - protected int nextStartTick; - protected int tryTicks; - private int maxStayTicks; -- protected BlockPos blockPos = BlockPos.ZERO; @Deprecated public final BlockPos getTargetPosition() { return this.blockPos; } // Paper - OBFHELPER -+ protected BlockPos blockPos = BlockPos.ZERO; @Deprecated public final BlockPos getTargetPosition() { return this.blockPos; } @Deprecated public void setTargetPosition(BlockPos pos) { this.blockPos = pos; mob.movingTarget = pos != BlockPos.ZERO ? pos : null; } // Paper - OBFHELPER - private boolean reachedTarget; - private final int searchRange; - private final int verticalSearchRange; -@@ -23,6 +23,13 @@ public abstract class MoveToBlockGoal extends Goal { - public MoveToBlockGoal(PathfinderMob mob, double speed, int range) { - this(mob, speed, range, 1); - } -+ // Paper start - activation range improvements -+ @Override -+ public void stop() { -+ super.stop(); -+ setTargetPosition(BlockPos.ZERO); -+ } -+ // Paper end - - public MoveToBlockGoal(PathfinderMob mob, double speed, int range, int maxYDifference) { - this.mob = mob; -@@ -114,6 +121,7 @@ public abstract class MoveToBlockGoal extends Goal { - mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); - if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level, mutableBlockPos)) { - this.blockPos = mutableBlockPos; -+ setTargetPosition(mutableBlockPos.immutable()); // Paper - return true; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index f957c0aca36b7228ac3a33ca04c948b1d10642d1..39fc94b1e1555fd6706391223dd2783139b16016 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -225,17 +225,29 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - @Override - public void inactiveTick() { - // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( -- if (level.spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { -- this.customServerAiStep(); -+ // Paper start -+ if (this.getUnhappyCounter() > 0) { -+ this.setUnhappyCounter(this.getUnhappyCounter() - 1); -+ } -+ if (this.isEffectiveAi()) { -+ if (level.spigotConfig.tickInactiveVillagers) { -+ this.customServerAiStep(); -+ } else { -+ this.mobTick(true); -+ } - } -+ maybeDecayGossip(); -+ // Paper end -+ - super.inactiveTick(); - } - // Spigot End - - @Override -- protected void customServerAiStep() { -+ protected void customServerAiStep() { mobTick(false); } -+ protected void mobTick(boolean inactive) { - this.level.getProfiler().push("villagerBrain"); -- this.getBrain().tick((ServerLevel) this.level, this); -+ if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // Paper - this.level.getProfiler().pop(); - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; -@@ -259,7 +271,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.lastTradedPlayer = null; - } - -- if (!this.isNoAi() && this.random.nextInt(100) == 0) { -+ if (!inactive && !this.isNoAi() && this.random.nextInt(100) == 0) { // Paper - Raid raid = ((ServerLevel) this.level).getRaidAt(this.blockPosition()); - - if (raid != null && raid.isActive() && !raid.isOver()) { -@@ -270,6 +282,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (this.getVillagerData().getProfession() == VillagerProfession.NONE && this.isTrading()) { - this.stopTrading(); - } -+ if (inactive) return; // Paper - - super.customServerAiStep(); - } -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -index 540c23f6297c34cf8e7bf8312ceaa5fc868f414c..2866385a64b22b7dc82b6122c62bcea6b0908a60 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -@@ -57,6 +57,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - if (bl != this.isEnabled()) { - this.setEnabled(bl); - } -+ this.immunize(); // Paper - - } - -@@ -107,11 +108,13 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - - public boolean suckInItems() { - if (HopperBlockEntity.suckInItems(this.level, this)) { -+ this.immunize(); // Paper - return true; - } else { - List list = this.level.getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(0.25D, 0.0D, 0.25D), EntitySelector.ENTITY_STILL_ALIVE); - if (!list.isEmpty()) { - HopperBlockEntity.addItem(this, list.get(0)); -+ this.immunize(); // Paper - } - - return false; -@@ -149,4 +152,11 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper - public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return new HopperMenu(syncId, playerInventory, this); - } -+ -+ // Paper start -+ public void immunize() { -+ this.activatedImmunityTick = Math.max(this.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 20); -+ } -+ // Paper end -+ - } -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e6bfc9cdf116b2233a638eec369a80eb8536aa18..4c1d34bd274d8e2a4003a286536652367da9488a 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -158,6 +158,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public Map capturedTileEntities = new HashMap<>(); - public List captureDrops; - public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); -+ // Paper start -+ public int wakeupInactiveRemainingAnimals; -+ public int wakeupInactiveRemainingFlying; -+ public int wakeupInactiveRemainingMonsters; -+ public int wakeupInactiveRemainingVillagers; -+ // Paper end - public boolean populating; - public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot - // Paper start -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -index 4b55b667eebfe50dfeda89015112e275e71b9777..dda0b32a4989bbead35a2219a969a30ba0e975b0 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -@@ -140,6 +140,10 @@ public class PistonMovingBlockEntity extends BlockEntity { - } - - entity.setDeltaMovement(e, g, h); -+ // Paper - EAR items stuck in in slime pushed by a piston -+ entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10); -+ entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10); -+ // Paper end - break; - } - } -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 7bae24598218dcf0012dd21e619e6f5f984bd6f0..0508f43ad396679d3372ae4caf029086a1524109 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -1,39 +1,52 @@ - package org.spigotmc; - -+import net.minecraft.core.BlockPos; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.ExperienceOrb; -+import net.minecraft.world.entity.FlyingMob; - import net.minecraft.world.entity.LightningBolt; - import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.PathfinderMob; -+import net.minecraft.world.entity.ai.Brain; - import net.minecraft.world.entity.ambient.AmbientCreature; - import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Bee; - import net.minecraft.world.entity.animal.Sheep; -+import net.minecraft.world.entity.animal.WaterAnimal; -+import net.minecraft.world.entity.animal.horse.Llama; - import net.minecraft.world.entity.boss.EnderDragonPart; - import net.minecraft.world.entity.boss.enderdragon.EndCrystal; - import net.minecraft.world.entity.boss.enderdragon.EnderDragon; - import net.minecraft.world.entity.boss.wither.WitherBoss; - import net.minecraft.world.entity.item.PrimedTnt; - import net.minecraft.world.entity.monster.Creeper; --import net.minecraft.world.entity.monster.Monster; --import net.minecraft.world.entity.monster.Slime; -+import net.minecraft.world.entity.monster.Enemy; -+import net.minecraft.world.entity.monster.Pillager; - import net.minecraft.world.entity.npc.Villager; - import net.minecraft.world.entity.player.Player; - import net.minecraft.world.entity.projectile.AbstractArrow; - import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; -+import net.minecraft.world.entity.projectile.EyeOfEnder; - import net.minecraft.world.entity.projectile.FireworkRocketEntity; - import net.minecraft.world.entity.projectile.ThrowableProjectile; - import net.minecraft.world.entity.projectile.ThrownTrident; - import net.minecraft.world.entity.raid.Raider; -+import co.aikar.timings.MinecraftTimings; -+import net.minecraft.world.entity.schedule.Activity; - import net.minecraft.world.level.Level; - import net.minecraft.world.phys.AABB; --import co.aikar.timings.MinecraftTimings; - - public class ActivationRange - { - - public enum ActivationType - { -+ WATER, // Paper -+ FLYING_MONSTER, // Paper -+ VILLAGER, // Paper - MONSTER, - ANIMAL, - RAIDER, -@@ -41,6 +54,43 @@ public class ActivationRange - - AABB boundingBox = new AABB( 0, 0, 0, 0, 0, 0 ); - } -+ // Paper start -+ -+ static Activity[] VILLAGER_PANIC_IMMUNITIES = { -+ Activity.HIDE, -+ Activity.PRE_RAID, -+ Activity.RAID, -+ Activity.PANIC -+ }; -+ -+ private static int checkInactiveWakeup(Entity entity) { -+ Level world = entity.level; -+ SpigotWorldConfig config = world.spigotConfig; -+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; -+ if (entity.activationType == ActivationType.VILLAGER) { -+ if (inactiveFor > config.wakeUpInactiveVillagersEvery && world.wakeupInactiveRemainingVillagers > 0) { -+ world.wakeupInactiveRemainingVillagers--; -+ return config.wakeUpInactiveVillagersFor; -+ } -+ } else if (entity.activationType == ActivationType.ANIMAL) { -+ if (inactiveFor > config.wakeUpInactiveAnimalsEvery && world.wakeupInactiveRemainingAnimals > 0) { -+ world.wakeupInactiveRemainingAnimals--; -+ return config.wakeUpInactiveAnimalsFor; -+ } -+ } else if (entity.activationType == ActivationType.FLYING_MONSTER) { -+ if (inactiveFor > config.wakeUpInactiveFlyingEvery && world.wakeupInactiveRemainingFlying > 0) { -+ world.wakeupInactiveRemainingFlying--; -+ return config.wakeUpInactiveFlyingFor; -+ } -+ } else if (entity.activationType == ActivationType.MONSTER || entity.activationType == ActivationType.RAIDER) { -+ if (inactiveFor > config.wakeUpInactiveMonstersEvery && world.wakeupInactiveRemainingMonsters > 0) { -+ world.wakeupInactiveRemainingMonsters--; -+ return config.wakeUpInactiveMonstersFor; -+ } -+ } -+ return -1; -+ } -+ // Paper end - - static AABB maxBB = new AABB( 0, 0, 0, 0, 0, 0 ); - -@@ -53,10 +103,13 @@ public class ActivationRange - */ - public static ActivationType initializeEntityActivationType(Entity entity) - { -+ if (entity instanceof WaterAnimal) { return ActivationType.WATER; } // Paper -+ else if (entity instanceof Villager) { return ActivationType.VILLAGER; } // Paper -+ else if (entity instanceof FlyingMob && entity instanceof Enemy) { return ActivationType.FLYING_MONSTER; } // Paper - doing & Monster incase Flying no longer includes monster in future - if ( entity instanceof Raider ) - { - return ActivationType.RAIDER; -- } else if ( entity instanceof Monster || entity instanceof Slime ) -+ } else if ( entity instanceof Enemy ) // Paper - correct monster check - { - return ActivationType.MONSTER; - } else if ( entity instanceof PathfinderMob || entity instanceof AmbientCreature ) -@@ -77,10 +130,14 @@ public class ActivationRange - */ - public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) - { -- if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) -- || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) -- || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) -- || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) -+ if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange <= 0 ) -+ || ( entity.activationType == ActivationType.VILLAGER && config.villagerActivationRange <= 0 ) // Paper -+ || ( entity.activationType == ActivationType.WATER && config.waterActivationRange <= 0 ) // Paper -+ || ( entity.activationType == ActivationType.FLYING_MONSTER && config.flyingMonsterActivationRange <= 0 ) // Paper -+ || entity instanceof EyeOfEnder // Paper - || entity instanceof Player - || entity instanceof ThrowableProjectile - || entity instanceof EnderDragon -@@ -113,10 +170,25 @@ public class ActivationRange - final int raiderActivationRange = world.spigotConfig.raiderActivationRange; - final int animalActivationRange = world.spigotConfig.animalActivationRange; - final int monsterActivationRange = world.spigotConfig.monsterActivationRange; -+ // Paper start -+ final int waterActivationRange = world.spigotConfig.waterActivationRange; -+ final int flyingActivationRange = world.spigotConfig.flyingMonsterActivationRange; -+ final int villagerActivationRange = world.spigotConfig.villagerActivationRange; -+ world.wakeupInactiveRemainingAnimals = Math.min(world.wakeupInactiveRemainingAnimals + 1, world.spigotConfig.wakeUpInactiveAnimals); -+ world.wakeupInactiveRemainingVillagers = Math.min(world.wakeupInactiveRemainingVillagers + 1, world.spigotConfig.wakeUpInactiveVillagers); -+ world.wakeupInactiveRemainingMonsters = Math.min(world.wakeupInactiveRemainingMonsters + 1, world.spigotConfig.wakeUpInactiveMonsters); -+ world.wakeupInactiveRemainingFlying = Math.min(world.wakeupInactiveRemainingFlying + 1, world.spigotConfig.wakeUpInactiveFlying); -+ final ServerChunkCache chunkProvider = (ServerChunkCache) world.getChunkSource(); -+ // Paper end - - int maxRange = Math.max( monsterActivationRange, animalActivationRange ); - maxRange = Math.max( maxRange, raiderActivationRange ); - maxRange = Math.max( maxRange, miscActivationRange ); -+ // Paper start -+ maxRange = Math.max( maxRange, flyingActivationRange ); -+ maxRange = Math.max( maxRange, waterActivationRange ); -+ maxRange = Math.max( maxRange, villagerActivationRange ); -+ // Paper end - maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange ); - - for ( Player player : world.players() ) -@@ -127,11 +199,17 @@ public class ActivationRange - continue; - } - -- ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange ); -- ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange ); -- ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); -- ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); -- ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); -+ // Paper start -+ int worldHeight = world.getHeight(); -+ ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); -+ ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, worldHeight, miscActivationRange ); -+ ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, worldHeight, raiderActivationRange ); -+ ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, worldHeight, animalActivationRange ); -+ ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, worldHeight, monsterActivationRange ); -+ ActivationType.WATER.boundingBox = player.getBoundingBox().inflate( waterActivationRange, worldHeight, waterActivationRange ); -+ ActivationType.FLYING_MONSTER.boundingBox = player.getBoundingBox().inflate( flyingActivationRange, worldHeight, flyingActivationRange ); -+ ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); -+ // Paper end - - world.getEntities().get(maxBB, ActivationRange::activateEntity); - } -@@ -166,60 +244,118 @@ public class ActivationRange - * @param entity - * @return - */ -- public static boolean checkEntityImmunities(Entity entity) -+ public static int checkEntityImmunities(Entity entity) // Paper - return # of ticks to get immunity - { -+ // Paper start -+ SpigotWorldConfig config = entity.level.spigotConfig; -+ int inactiveWakeUpImmunity = checkInactiveWakeup(entity); -+ if (inactiveWakeUpImmunity > -1) { -+ return inactiveWakeUpImmunity; -+ } -+ if (entity.remainingFireTicks > 0) { -+ return 2; -+ } -+ if (entity.activatedImmunityTick >= MinecraftServer.currentTick) { -+ return 1; -+ } -+ long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; -+ // Paper end - // quick checks. -- if ( entity.wasTouchingWater || entity.remainingFireTicks > 0 ) -+ if ( (entity.activationType != ActivationType.WATER && entity.wasTouchingWater && entity.isPushedByFluid()) ) // Paper - { -- return true; -+ return 100; // Paper -+ } -+ // Paper start -+ if ( !entity.isOnGround() || entity.getDeltaMovement().horizontalDistanceSqr() > 9.999999747378752E-6D ) -+ { -+ return 100; - } -+ // Paper end - if ( !( entity instanceof AbstractArrow ) ) - { -- if ( !entity.isOnGround() || !entity.passengers.isEmpty() || entity.isPassenger() ) -+ if ( (!entity.isOnGround() && !(entity instanceof FlyingMob)) ) // Paper - remove passengers logic - { -- return true; -+ return 10; // Paper - } - } else if ( !( (AbstractArrow) entity ).inGround ) - { -- return true; -+ return 1; // Paper - } - // special cases. - if ( entity instanceof LivingEntity ) - { - LivingEntity living = (LivingEntity) entity; -- if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTime > 0 || living.activeEffects.size() > 0 ) -+ if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 ) // Paper - { -- return true; -+ return 1; // Paper - } -- if ( entity instanceof PathfinderMob && ( (PathfinderMob) entity ).getTarget() != null ) -+ if ( entity instanceof Mob && ((Mob) entity ).getTarget() != null) // Paper - { -- return true; -+ return 20; // Paper - } -- if ( entity instanceof Villager && ( (Villager) entity ).canBreed() ) -+ // Paper start -+ if (entity instanceof Bee) { -+ Bee bee = (Bee)entity; -+ BlockPos movingTarget = bee.getMovingTarget(); -+ if (bee.isAngry() || -+ (bee.getHivePos() != null && bee.getHivePos().equals(movingTarget)) || -+ (bee.getSavedFlowerPos() != null && bee.getSavedFlowerPos().equals(movingTarget)) -+ ) { -+ return 20; -+ } -+ } -+ if ( entity instanceof Villager ) { -+ Brain behaviorController = ((Villager) entity).getBrain(); -+ -+ if (config.villagersActiveForPanic) { -+ for (Activity activity : VILLAGER_PANIC_IMMUNITIES) { -+ if (behaviorController.isActive(activity)) { -+ return 20*5; -+ } -+ } -+ } -+ -+ if (config.villagersWorkImmunityAfter > 0 && inactiveFor >= config.villagersWorkImmunityAfter) { -+ if (behaviorController.isActive(Activity.WORK)) { -+ return config.villagersWorkImmunityFor; -+ } -+ } -+ } -+ if ( entity instanceof Llama && ( (Llama) entity ).inCaravan() ) - { -- return true; -+ return 1; - } -+ // Paper end - if ( entity instanceof Animal ) - { - Animal animal = (Animal) entity; - if ( animal.isBaby() || animal.isInLove() ) - { -- return true; -+ return 5; // Paper - } - if ( entity instanceof Sheep && ( (Sheep) entity ).isSheared() ) - { -- return true; -+ return 1; // Paper - } - } - if (entity instanceof Creeper && ((Creeper) entity).isIgnited()) { // isExplosive -- return true; -+ return 20; // Paper -+ } -+ // Paper start -+ if (entity instanceof Mob && ((Mob) entity).targetSelector.hasTasks() ) { -+ return 0; -+ } -+ if (entity instanceof Pillager) { -+ Pillager pillager = (Pillager) entity; -+ // TODO:? - } -+ // Paper end - } - // SPIGOT-6644: Otherwise the target refresh tick will be missed - if (entity instanceof ExperienceOrb) { -- return true; -+ return 20; // Paper - } -- return false; -+ return -1; // Paper - } - - /** -@@ -234,8 +370,19 @@ public class ActivationRange - if ( entity instanceof FireworkRocketEntity ) { - return true; - } -+ // Paper start - special case always immunities -+ // immunize brand new entities, dead entities, and portal scenarios -+ if (entity.defaultActivationState || entity.tickCount < 20*10 || !entity.isAlive() || entity.isInsidePortal || entity.portalCooldown > 0) { -+ return true; -+ } -+ // immunize leashed entities -+ if (entity instanceof Mob && ((Mob)entity).leashHolder instanceof Player) { -+ return true; -+ } -+ // Paper end - -- boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState; -+ boolean isActive = entity.activatedTick >= MinecraftServer.currentTick; -+ entity.isTemporarilyActive = false; // Paper - - // Should this entity tick? - if ( !isActive ) -@@ -243,15 +390,19 @@ public class ActivationRange - if ( ( MinecraftServer.currentTick - entity.activatedTick - 1 ) % 20 == 0 ) - { - // Check immunities every 20 ticks. -- if ( ActivationRange.checkEntityImmunities( entity ) ) -- { -- // Triggered some sort of immunity, give 20 full ticks before we check again. -- entity.activatedTick = MinecraftServer.currentTick + 20; -+ // Paper start -+ int immunity = checkEntityImmunities(entity); -+ if (immunity >= 0) { -+ entity.activatedTick = MinecraftServer.currentTick + immunity; -+ } else { -+ entity.isTemporarilyActive = true; - } -+ // Paper end - isActive = true; -+ - } - // Add a little performance juice to active entities. Skip 1/4 if not immune. -- } else if ( !entity.defaultActivationState && entity.tickCount + entity.getId() + 1 % 4 == 0 && !ActivationRange.checkEntityImmunities( entity ) ) // Paper - Ensure checking item movement is offset from Spigot's entity activation range check -+ } else if ( entity.tickCount + entity.getId() + 1 % 4 == 0 && ActivationRange.checkEntityImmunities( entity ) < 0 ) // Paper - { - isActive = false; - } -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index d4d2d11cf19167410ec6ad3417495e7130330d11..9c9723e13b5440d4803a7268057d63cbdc973b77 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -199,14 +199,60 @@ public class SpigotWorldConfig - public int monsterActivationRange = 32; - public int raiderActivationRange = 48; - public int miscActivationRange = 16; -+ // Paper start -+ public int flyingMonsterActivationRange = 32; -+ public int waterActivationRange = 16; -+ public int villagerActivationRange = 32; -+ public int wakeUpInactiveAnimals = 4; -+ public int wakeUpInactiveAnimalsEvery = 60*20; -+ public int wakeUpInactiveAnimalsFor = 5*20; -+ public int wakeUpInactiveMonsters = 8; -+ public int wakeUpInactiveMonstersEvery = 20*20; -+ public int wakeUpInactiveMonstersFor = 5*20; -+ public int wakeUpInactiveVillagers = 4; -+ public int wakeUpInactiveVillagersEvery = 30*20; -+ public int wakeUpInactiveVillagersFor = 5*20; -+ public int wakeUpInactiveFlying = 8; -+ public int wakeUpInactiveFlyingEvery = 10*20; -+ public int wakeUpInactiveFlyingFor = 5*20; -+ public int villagersWorkImmunityAfter = 5*20; -+ public int villagersWorkImmunityFor = 20; -+ public boolean villagersActiveForPanic = true; -+ // Paper end - public boolean tickInactiveVillagers = true; - public boolean ignoreSpectatorActivation = false; - private void activationRange() - { -+ boolean hasAnimalsConfig = config.getInt("entity-activation-range.animals", this.animalActivationRange) != this.animalActivationRange; // Paper - this.animalActivationRange = this.getInt( "entity-activation-range.animals", this.animalActivationRange ); - this.monsterActivationRange = this.getInt( "entity-activation-range.monsters", this.monsterActivationRange ); - this.raiderActivationRange = this.getInt( "entity-activation-range.raiders", this.raiderActivationRange ); - this.miscActivationRange = this.getInt( "entity-activation-range.misc", this.miscActivationRange ); -+ // Paper start -+ this.waterActivationRange = this.getInt( "entity-activation-range.water", this.waterActivationRange ); -+ this.villagerActivationRange = this.getInt( "entity-activation-range.villagers", hasAnimalsConfig ? this.animalActivationRange : this.villagerActivationRange ); -+ this.flyingMonsterActivationRange = this.getInt( "entity-activation-range.flying-monsters", this.flyingMonsterActivationRange ); -+ -+ this.wakeUpInactiveAnimals = this.getInt("entity-activation-range.wake-up-inactive.animals-max-per-tick", this.wakeUpInactiveAnimals); -+ this.wakeUpInactiveAnimalsEvery = this.getInt("entity-activation-range.wake-up-inactive.animals-every", this.wakeUpInactiveAnimalsEvery); -+ this.wakeUpInactiveAnimalsFor = this.getInt("entity-activation-range.wake-up-inactive.animals-for", this.wakeUpInactiveAnimalsFor); -+ -+ this.wakeUpInactiveMonsters = this.getInt("entity-activation-range.wake-up-inactive.monsters-max-per-tick", this.wakeUpInactiveMonsters); -+ this.wakeUpInactiveMonstersEvery = this.getInt("entity-activation-range.wake-up-inactive.monsters-every", this.wakeUpInactiveMonstersEvery); -+ this.wakeUpInactiveMonstersFor = this.getInt("entity-activation-range.wake-up-inactive.monsters-for", this.wakeUpInactiveMonstersFor); -+ -+ this.wakeUpInactiveVillagers = this.getInt("entity-activation-range.wake-up-inactive.villagers-max-per-tick", this.wakeUpInactiveVillagers); -+ this.wakeUpInactiveVillagersEvery = this.getInt("entity-activation-range.wake-up-inactive.villagers-every", this.wakeUpInactiveVillagersEvery); -+ this.wakeUpInactiveVillagersFor = this.getInt("entity-activation-range.wake-up-inactive.villagers-for", this.wakeUpInactiveVillagersFor); -+ -+ this.wakeUpInactiveFlying = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-max-per-tick", this.wakeUpInactiveFlying); -+ this.wakeUpInactiveFlyingEvery = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-every", this.wakeUpInactiveFlyingEvery); -+ this.wakeUpInactiveFlyingFor = this.getInt("entity-activation-range.wake-up-inactive.flying-monsters-for", this.wakeUpInactiveFlyingFor); -+ -+ this.villagersWorkImmunityAfter = this.getInt( "entity-activation-range.villagers-work-immunity-after", this.villagersWorkImmunityAfter ); -+ this.villagersWorkImmunityFor = this.getInt( "entity-activation-range.villagers-work-immunity-for", this.villagersWorkImmunityFor ); -+ this.villagersActiveForPanic = this.getBoolean( "entity-activation-range.villagers-active-for-panic", this.villagersActiveForPanic ); -+ // Paper end - this.tickInactiveVillagers = this.getBoolean( "entity-activation-range.tick-inactive-villagers", this.tickInactiveVillagers ); - this.ignoreSpectatorActivation = this.getBoolean( "entity-activation-range.ignore-spectators", this.ignoreSpectatorActivation ); - this.log( "Entity Activation Range: An " + this.animalActivationRange + " / Mo " + this.monsterActivationRange + " / Ra " + this.raiderActivationRange + " / Mi " + this.miscActivationRange + " / Tiv " + this.tickInactiveVillagers + " / Isa " + this.ignoreSpectatorActivation ); diff --git a/patches/server/0354-Increase-Light-Queue-Size.patch b/patches/server/0354-Increase-Light-Queue-Size.patch new file mode 100644 index 0000000000..0c0b38a5fb --- /dev/null +++ b/patches/server/0354-Increase-Light-Queue-Size.patch @@ -0,0 +1,28 @@ +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/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3a085c141a5a6e2df9a85d0e3969363d69824294..27f19abc22e295a5480dbed5df86f5d885ad3b73 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -780,7 +780,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -2,6 +2,7 @@ package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; + import io.papermc.paper.command.subcommands.EntityCommand; ++import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -42,6 +43,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); ++ commands.put(Set.of("fixlight"), new FixLightCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +1,121 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayDeque; ++import java.util.Deque; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ThreadedLevelLightEngine; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class FixLightCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doFixLight(sender, args); ++ return true; ++ } ++ ++ private void doFixLight(final CommandSender sender, final String[] args) { ++ if (!(sender instanceof Player)) { ++ sender.sendMessage(text("Only players can use this command", RED)); ++ return; ++ } ++ @Nullable Runnable post = null; ++ int radius = 2; ++ if (args.length > 0) { ++ try { ++ final int parsed = Integer.parseInt(args[0]); ++ if (parsed < 0) { ++ sender.sendMessage(text("Radius cannot be negative!", RED)); ++ return; ++ } ++ final int maxRadius = 5; ++ radius = Math.min(maxRadius, parsed); ++ if (radius != parsed) { ++ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); ++ } ++ } catch (final Exception e) { ++ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); ++ return; ++ } ++ } ++ ++ CraftPlayer player = (CraftPlayer) sender; ++ ServerPlayer handle = player.getHandle(); ++ ServerLevel world = (ServerLevel) handle.level; ++ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ ++ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); ++ Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); ++ updateLight(sender, world, lightengine, queue, post); ++ } ++ ++ private void updateLight( ++ final CommandSender sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final Deque queue, ++ final @Nullable Runnable done ++ ) { ++ @Nullable ChunkPos coord = queue.poll(); ++ if (coord == null) { ++ sender.sendMessage(text("All Chunks Light updated", GREEN)); ++ if (done != null) { ++ done.run(); ++ } ++ return; ++ } ++ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { ++ if (ex != null) { ++ sender.sendMessage(text("Error loading chunk " + coord, RED)); ++ updateLight(sender, world, lightengine, queue, done); ++ return; ++ } ++ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); ++ if (chunk == null) { ++ updateLight(sender, world, lightengine, queue, done); ++ return; ++ } ++ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue ++ sender.sendMessage(text("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++) { ++ net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z); ++ lightengine.checkBlock(pos); ++ } ++ } ++ } ++ lightengine.tryScheduleUpdate(); ++ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); ++ if (visibleChunk != null) { ++ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { ++ MinecraftServer.getServer().processQueue.add(() -> { ++ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false); ++ updateLight(sender, world, lightengine, queue, done); ++ }); ++ }); ++ } else { ++ updateLight(sender, world, lightengine, queue, done); ++ } ++ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize); ++ }, MinecraftServer.getServer()); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 4b24e4d947e96ea0720f8f6bc33470e07c00310d..d60173b03baee4a66da1109795bf6a19737b8bd0 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -141,6 +141,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final ChunkTaskPriorityQueueSorter queueSorter; + private final ProcessorHandle> worldgenMailbox; + public final ProcessorHandle> mainThreadMailbox; ++ // Paper start ++ final ProcessorHandle> mailboxLight; ++ public void addLightTask(ChunkHolder playerchunk, Runnable run) { ++ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run)); ++ } ++ // Paper end + public final ChunkProgressListener progressListener; + private final ChunkStatusUpdateListener chunkStatusListener; + public final ChunkMap.ChunkDistanceManager distanceManager; +@@ -284,11 +290,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.progressListener = worldGenerationProgressListener; + this.chunkStatusListener = chunkStatusChangeListener; +- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); ++ ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper + + this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); + this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); + this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); ++ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper + this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); + this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor); + this.overworldDataStorage = persistentStateManagerFactory; diff --git a/patches/server/0355-Increase-Light-Queue-Size.patch b/patches/server/0355-Increase-Light-Queue-Size.patch deleted file mode 100644 index 0c0b38a5fb..0000000000 --- a/patches/server/0355-Increase-Light-Queue-Size.patch +++ /dev/null @@ -1,28 +0,0 @@ -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/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3a085c141a5a6e2df9a85d0e3969363d69824294..27f19abc22e295a5480dbed5df86f5d885ad3b73 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -780,7 +780,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 25 Nov 2021 13:27:51 +0100 +Subject: [PATCH] Anti-Xray + + +diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java +@@ -0,0 +1,51 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class BitStorageReader { ++ ++ private byte[] buffer; ++ private int bits; ++ private int mask; ++ private int longInBufferIndex; ++ private int bitInLongIndex; ++ private long current; ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public void setBits(int bits) { ++ this.bits = bits; ++ mask = (1 << bits) - 1; ++ } ++ ++ public void setIndex(int index) { ++ longInBufferIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (buffer.length > longInBufferIndex + 7) { ++ current = ((((long) buffer[longInBufferIndex]) << 56) ++ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) ++ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) ++ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) ++ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) ++ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) ++ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) ++ | (((long) buffer[longInBufferIndex + 7] & 0xff))); ++ } ++ } ++ ++ public int read() { ++ if (bitInLongIndex + bits > 64) { ++ bitInLongIndex = 0; ++ longInBufferIndex += 8; ++ init(); ++ } ++ ++ int value = (int) (current >>> bitInLongIndex) & mask; ++ bitInLongIndex += bits; ++ return value; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java +@@ -0,0 +1,79 @@ ++package com.destroystokyo.paper.antixray; ++ ++public final class BitStorageWriter { ++ ++ private byte[] buffer; ++ private int bits; ++ private long mask; ++ private int longInBufferIndex; ++ private int bitInLongIndex; ++ private long current; ++ private boolean dirty; ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public void setBits(int bits) { ++ this.bits = bits; ++ mask = (1L << bits) - 1; ++ } ++ ++ public void setIndex(int index) { ++ longInBufferIndex = index; ++ bitInLongIndex = 0; ++ init(); ++ } ++ ++ private void init() { ++ if (buffer.length > longInBufferIndex + 7) { ++ current = ((((long) buffer[longInBufferIndex]) << 56) ++ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) ++ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) ++ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) ++ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) ++ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) ++ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) ++ | (((long) buffer[longInBufferIndex + 7] & 0xff))); ++ } ++ ++ dirty = false; ++ } ++ ++ public void flush() { ++ if (dirty && buffer.length > longInBufferIndex + 7) { ++ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); ++ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); ++ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); ++ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff); ++ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff); ++ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff); ++ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff); ++ buffer[longInBufferIndex + 7] = (byte) (current & 0xff); ++ } ++ } ++ ++ public void write(int value) { ++ if (bitInLongIndex + bits > 64) { ++ flush(); ++ bitInLongIndex = 0; ++ longInBufferIndex += 8; ++ init(); ++ } ++ ++ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; ++ dirty = true; ++ bitInLongIndex += bits; ++ } ++ ++ public void skip() { ++ bitInLongIndex += bits; ++ ++ if (bitInLongIndex > 64) { ++ flush(); ++ bitInLongIndex = bits; ++ longInBufferIndex += 8; ++ init(); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bd86dc2ad2f87969da4add06de2a629f69d4b5de +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java +@@ -0,0 +1,45 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public class ChunkPacketBlockController { ++ ++ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); ++ ++ protected ChunkPacketBlockController() { ++ ++ } ++ ++ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) { ++ return null; ++ } ++ ++ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { ++ return false; ++ } ++ ++ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ return null; ++ } ++ ++ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { ++ chunkPacket.setReady(true); ++ } ++ ++ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { ++ ++ } ++ ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { ++ ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dabd93c35bdbac6a8b668a82d5f3d4173a1baa4a +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -0,0 +1,635 @@ ++package com.destroystokyo.paper.antixray; ++ ++import io.papermc.paper.configuration.WorldConfiguration; ++import io.papermc.paper.configuration.type.EngineMode; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.Registry; ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.biome.Biomes; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.EntityBlock; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.*; ++import org.bukkit.Bukkit; ++ ++import java.util.*; ++import java.util.concurrent.Executor; ++import java.util.concurrent.ThreadLocalRandom; ++import java.util.function.IntSupplier; ++ ++public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { ++ ++ private static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); ++ private static final LevelChunkSection EMPTY_SECTION = null; ++ private final Executor executor; ++ private final EngineMode engineMode; ++ private final int maxBlockHeight; ++ private final int updateRadius; ++ private final boolean usePermission; ++ private final BlockState[] presetBlockStates; ++ private final BlockState[] presetBlockStatesFull; ++ private final BlockState[] presetBlockStatesStone; ++ private final BlockState[] presetBlockStatesDeepslate; ++ private final BlockState[] presetBlockStatesNetherrack; ++ private final BlockState[] presetBlockStatesEndStone; ++ private final int[] presetBlockStateBitsGlobal; ++ private final int[] presetBlockStateBitsStoneGlobal; ++ private final int[] presetBlockStateBitsDeepslateGlobal; ++ private final int[] presetBlockStateBitsNetherrackGlobal; ++ private final int[] presetBlockStateBitsEndStoneGlobal; ++ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; ++ private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION}; ++ private final int maxBlockHeightUpdatePosition; ++ ++ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) { ++ this.executor = executor; ++ WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray; ++ engineMode = paperWorldConfig.engineMode; ++ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4; ++ updateRadius = paperWorldConfig.updateRadius; ++ usePermission = paperWorldConfig.usePermission; ++ List toObfuscate; ++ ++ if (engineMode == EngineMode.HIDE) { ++ toObfuscate = paperWorldConfig.hiddenBlocks; ++ presetBlockStates = null; ++ presetBlockStatesFull = null; ++ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()}; ++ presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()}; ++ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()}; ++ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()}; ++ presetBlockStateBitsGlobal = null; ++ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())}; ++ presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())}; ++ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())}; ++ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())}; ++ } else { ++ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); ++ List presetBlockStateList = new LinkedList<>(); ++ ++ for (String id : paperWorldConfig.hiddenBlocks) { ++ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); ++ ++ if (block != null && !(block instanceof EntityBlock)) { ++ toObfuscate.add(id); ++ presetBlockStateList.add(block.defaultBlockState()); ++ } ++ } ++ ++ // The doc of the LinkedHashSet(Collection) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation ++ Set presetBlockStateSet = new LinkedHashSet<>(); ++ // Therefore addAll(Collection) is used, which guarantees this order in the doc ++ presetBlockStateSet.addAll(presetBlockStateList); ++ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]); ++ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]); ++ presetBlockStatesStone = null; ++ presetBlockStatesDeepslate = null; ++ presetBlockStatesNetherrack = null; ++ presetBlockStatesEndStone = null; ++ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length]; ++ ++ for (int i = 0; i < presetBlockStatesFull.length; i++) { ++ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]); ++ } ++ ++ presetBlockStateBitsStoneGlobal = null; ++ presetBlockStateBitsDeepslateGlobal = null; ++ presetBlockStateBitsNetherrackGlobal = null; ++ presetBlockStateBitsEndStoneGlobal = null; ++ } ++ ++ for (String id : toObfuscate) { ++ Block block = Registry.BLOCK.getOptional(new ResourceLocation(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.defaultBlockState().isAir()) { ++ // Replace all block states of a specified block ++ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { ++ obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; ++ } ++ } ++ } ++ ++ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getHolderOrThrow(Biomes.PLAINS)); ++ BlockPos zeroPos = new BlockPos(0, 0, 0); ++ ++ for (int i = 0; i < solidGlobal.length; i++) { ++ BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i); ++ ++ if (blockState != null) { ++ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos) ++ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState(); ++ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used ++ // shulker box checks TE. ++ } ++ } ++ ++ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1; ++ } ++ ++ private int getPresetBlockStatesFullLength() { ++ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length; ++ } ++ ++ @Override ++ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) { ++ // Return the block states to be added to the paletted containers so that they can be used for obfuscation ++ if (bottomBlockY < maxBlockHeight) { ++ if (engineMode == EngineMode.HIDE) { ++ return switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStatesNetherrack; ++ case THE_END -> presetBlockStatesEndStone; ++ default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone; ++ }; ++ } ++ ++ return presetBlockStates; ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { ++ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass"); ++ } ++ ++ @Override ++ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later ++ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this); ++ } ++ ++ @Override ++ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { ++ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) { ++ chunkPacket.setReady(true); ++ return; ++ } ++ ++ if (!Bukkit.isPrimaryThread()) { ++ // Plugins? ++ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); ++ return; ++ } ++ ++ LevelChunk chunk = chunkPacketInfo.getChunk(); ++ int x = chunk.getPos().x; ++ int z = chunk.getPos().z; ++ Level level = chunk.getLevel(); ++ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1)); ++ executor.execute((Runnable) chunkPacketInfo); ++ } ++ ++ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal) ++ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here ++ private final ThreadLocal presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]); ++ private static final ThreadLocal SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ private static final ThreadLocal OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); ++ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate ++ private static final ThreadLocal CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ private static final ThreadLocal NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); ++ ++ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { ++ int[] presetBlockStateBits = this.presetBlockStateBits.get(); ++ boolean[] solid = SOLID.get(); ++ boolean[] obfuscate = OBFUSCATE.get(); ++ boolean[][] current = CURRENT.get(); ++ boolean[][] next = NEXT.get(); ++ boolean[][] nextNext = NEXT_NEXT.get(); ++ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it ++ BitStorageReader bitStorageReader = new BitStorageReader(); ++ BitStorageWriter bitStorageWriter = new BitStorageWriter(); ++ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; ++ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk(); ++ Level level = chunk.getLevel(); ++ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount()) - 1; ++ boolean[] solidTemp = null; ++ boolean[] obfuscateTemp = null; ++ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); ++ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); ++ int numberOfBlocks = presetBlockStateBits.length; ++ // Keep the lambda expressions as simple as possible. They are used very frequently. ++ 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.getPresetValues(chunkSectionIndex) != null) { ++ int[] presetBlockStateBitsTemp; ++ ++ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) { ++ if (engineMode == EngineMode.HIDE) { ++ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) { ++ case NETHER -> presetBlockStateBitsNetherrackGlobal; ++ case THE_END -> presetBlockStateBitsEndStoneGlobal; ++ default -> chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal; ++ }; ++ } else { ++ presetBlockStateBitsTemp = presetBlockStateBitsGlobal; ++ } ++ } else { ++ // If it's presetBlockStates, use this.presetBlockStatesFull instead ++ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex); ++ presetBlockStateBitsTemp = presetBlockStateBits; ++ ++ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) { ++ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible ++ // For more details see the comments in the readPalette method ++ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]); ++ } ++ } ++ ++ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); ++ ++ // Check if the chunk section below was not obfuscated ++ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) { ++ // If so, initialize some stuff ++ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); ++ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); ++ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal); ++ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); ++ // Read the blocks of the upper layer of the chunk section below if it exists ++ LevelChunkSection belowChunkSection = null; ++ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ current[z][x] = true; ++ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z); ++ } ++ } ++ ++ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section ++ bitStorageWriter.setBits(0); ++ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); ++ } ++ ++ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); ++ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; ++ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex]; ++ ++ // Obfuscate all layers of the current chunk section except the upper one ++ for (int y = 0; y < 15; y++) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ // Check if the chunk section above doesn't need obfuscation ++ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) { ++ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists ++ LevelChunkSection aboveChunkSection; ++ ++ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) { ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ ++ for (int z = 0; z < 16; z++) { ++ for (int x = 0; x < 16; x++) { ++ if (isTransparent(aboveChunkSection, x, 0, z)) { ++ current[z][x] = true; ++ } ++ } ++ } ++ ++ // There is nothing to read anymore ++ bitStorageReader.setBits(0); ++ solid[0] = true; ++ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ } else { ++ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section ++ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1)); ++ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1)); ++ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal); ++ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); ++ boolean[][] temp = current; ++ current = next; ++ next = nextNext; ++ nextNext = temp; ++ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); ++ } ++ ++ bitStorageWriter.flush(); ++ } ++ } ++ ++ chunkPacketInfoAntiXray.getChunkPacket().setReady(true); ++ } ++ ++ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { ++ // First block of first line ++ int bits = bitStorageReader.read(); ++ ++ if (nextNext[0][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][1] = true; ++ next[1][0] = true; ++ } else { ++ if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][0] = true; ++ } ++ ++ // First line ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[0][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][x - 1] = true; ++ next[0][x + 1] = true; ++ next[1][x] = true; ++ } else { ++ if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][x] = true; ++ } ++ } ++ ++ // Last block of first line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[0][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[0][14] = true; ++ next[1][15] = true; ++ } else { ++ if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[0][15] = true; ++ } ++ ++ // All inner lines ++ for (int z = 1; z < 15; z++) { ++ // First block ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][1] = true; ++ next[z - 1][0] = true; ++ next[z + 1][0] = true; ++ } else { ++ if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][0] = true; ++ } ++ ++ // All inner blocks ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][x - 1] = true; ++ next[z][x + 1] = true; ++ next[z - 1][x] = true; ++ next[z + 1][x] = true; ++ } else { ++ if (current[z][x]) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][x] = true; ++ } ++ } ++ ++ // Last block ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[z][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[z][14] = true; ++ next[z - 1][15] = true; ++ next[z + 1][15] = true; ++ } else { ++ if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[z][15] = true; ++ } ++ } ++ ++ // First block of last line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][0] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][1] = true; ++ next[14][0] = true; ++ } else { ++ if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][0] = true; ++ } ++ ++ // Last line ++ for (int x = 1; x < 15; x++) { ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][x] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][x - 1] = true; ++ next[15][x + 1] = true; ++ next[14][x] = true; ++ } else { ++ if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][x] = true; ++ } ++ } ++ ++ // Last block of last line ++ bits = bitStorageReader.read(); ++ ++ if (nextNext[15][15] = !solid[bits]) { ++ bitStorageWriter.skip(); ++ next[15][14] = true; ++ next[14][15] = true; ++ } else { ++ if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) { ++ bitStorageWriter.skip(); ++ } else { ++ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); ++ } ++ } ++ ++ if (!obfuscate[bits]) { ++ next[15][15] = true; ++ } ++ } ++ ++ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) { ++ if (chunkSection == EMPTY_SECTION) { ++ return true; ++ } ++ ++ try { ++ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))]; ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care and treat the block as transparent ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur ++ return true; ++ } ++ } ++ ++ private boolean[] readPalette(Palette palette, boolean[] temp, boolean[] global) { ++ if (palette instanceof GlobalPalette) { ++ return global; ++ } ++ ++ try { ++ for (int i = 0; i < palette.getSize(); i++) { ++ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))]; ++ } ++ } catch (MissingPaletteEntryException e) { ++ // Race condition / visibility issue / no happens-before relationship ++ // We don't care because we at least see the state as it was when the chunk packet was created ++ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here ++ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data ++ } ++ ++ return temp; ++ } ++ ++ @Override ++ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { ++ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) { ++ updateNearbyBlocks(level, blockPos); ++ } ++ } ++ ++ @Override ++ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { ++ if (blockPos.getY() <= maxBlockHeightUpdatePosition) { ++ updateNearbyBlocks(serverPlayerGameMode.level, blockPos); ++ } ++ } ++ ++ private void updateNearbyBlocks(Level level, BlockPos blockPos) { ++ if (updateRadius >= 2) { ++ BlockPos temp = blockPos.west(); ++ updateBlock(level, temp); ++ updateBlock(level, temp.west()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.east()); ++ updateBlock(level, temp.east()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.below()); ++ updateBlock(level, temp.below()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.above()); ++ updateBlock(level, temp.above()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp.south()); ++ updateBlock(level, temp = blockPos.north()); ++ updateBlock(level, temp.north()); ++ updateBlock(level, temp = blockPos.south()); ++ updateBlock(level, temp.south()); ++ } else if (updateRadius == 1) { ++ updateBlock(level, blockPos.west()); ++ updateBlock(level, blockPos.east()); ++ updateBlock(level, blockPos.below()); ++ updateBlock(level, blockPos.above()); ++ updateBlock(level, blockPos.north()); ++ updateBlock(level, blockPos.south()); ++ } else { ++ // Do nothing if updateRadius <= 0 (test mode) ++ } ++ } ++ ++ private void updateBlock(Level level, BlockPos blockPos) { ++ BlockState blockState = level.getBlockStateIfLoaded(blockPos); ++ ++ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) { ++ ((ServerLevel) level).getChunkSource().blockChanged(blockPos); ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java +@@ -0,0 +1,80 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.Palette; ++ ++public class ChunkPacketInfo { ++ ++ private final ClientboundLevelChunkWithLightPacket chunkPacket; ++ private final LevelChunk chunk; ++ private final int[] bits; ++ private final Object[] palettes; ++ private final int[] indexes; ++ private final Object[][] presetValues; ++ private byte[] buffer; ++ ++ public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { ++ this.chunkPacket = chunkPacket; ++ this.chunk = chunk; ++ int sections = chunk.getSectionsCount(); ++ bits = new int[sections]; ++ palettes = new Object[sections]; ++ indexes = new int[sections]; ++ presetValues = new Object[sections][]; ++ } ++ ++ public ClientboundLevelChunkWithLightPacket getChunkPacket() { ++ return chunkPacket; ++ } ++ ++ public LevelChunk getChunk() { ++ return chunk; ++ } ++ ++ public byte[] getBuffer() { ++ return buffer; ++ } ++ ++ public void setBuffer(byte[] buffer) { ++ this.buffer = buffer; ++ } ++ ++ public int getBits(int chunkSectionIndex) { ++ return bits[chunkSectionIndex]; ++ } ++ ++ public void setBits(int chunkSectionIndex, int bits) { ++ this.bits[chunkSectionIndex] = bits; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public Palette getPalette(int chunkSectionIndex) { ++ return (Palette) palettes[chunkSectionIndex]; ++ } ++ ++ public void setPalette(int chunkSectionIndex, Palette palette) { ++ palettes[chunkSectionIndex] = palette; ++ } ++ ++ public int getIndex(int chunkSectionIndex) { ++ return indexes[chunkSectionIndex]; ++ } ++ ++ public void setIndex(int chunkSectionIndex, int index) { ++ indexes[chunkSectionIndex] = index; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public T[] getPresetValues(int chunkSectionIndex) { ++ return (T[]) presetValues[chunkSectionIndex]; ++ } ++ ++ public void setPresetValues(int chunkSectionIndex, T[] presetValues) { ++ this.presetValues[chunkSectionIndex] = presetValues; ++ } ++ ++ public boolean isWritten(int chunkSectionIndex) { ++ return bits[chunkSectionIndex] != 0; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java +@@ -0,0 +1,29 @@ ++package com.destroystokyo.paper.antixray; ++ ++import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.LevelChunk; ++ ++public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { ++ ++ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; ++ private LevelChunk[] nearbyChunks; ++ ++ public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { ++ super(chunkPacket, chunk); ++ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; ++ } ++ ++ public LevelChunk[] getNearbyChunks() { ++ return nearbyChunks; ++ } ++ ++ public void setNearbyChunks(LevelChunk... nearbyChunks) { ++ this.nearbyChunks = nearbyChunks; ++ } ++ ++ @Override ++ public void run() { ++ chunkPacketBlockControllerAntiXray.obfuscate(this); ++ } ++} +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index 568ac283baf40e2a89f5b002ffd899eba8008ef2..2b35059cfe7a27238e0a74df058733897a26ac1c 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -33,7 +33,10 @@ public class ClientboundLevelChunkPacketData { + } + // Paper end + +- public ClientboundLevelChunkPacketData(LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } ++ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { ++ // Paper end + this.heightmaps = new CompoundTag(); + + for(Map.Entry entry : chunk.getHeightmaps()) { +@@ -43,7 +46,14 @@ public class ClientboundLevelChunkPacketData { + } + + this.buffer = new byte[calculateChunkSize(chunk)]; +- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk); ++ ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(this.buffer); ++ } ++ ++ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo); ++ // Paper end + this.blockEntitiesData = Lists.newArrayList(); + int totalTileEntities = 0; // Paper + +@@ -103,9 +113,12 @@ public class ClientboundLevelChunkPacketData { + return byteBuf; + } + +- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } ++ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { + for(LevelChunkSection levelChunkSection : chunk.getSections()) { +- levelChunkSection.write(buf); ++ levelChunkSection.write(buf, chunkPacketInfo); ++ // Paper end + } + + } +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +index 7825d6f0fdcfda6212cff8033ec55fb7db236154..000853110c7a89f2d0403a7a2737025a5ac28240 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; ++ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); ++ // Paper end + this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits, nonEdge); ++ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + } + + public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index d60173b03baee4a66da1109795bf6a19737b8bd0..55a21a4024debc328d4829477d80c2998e291c22 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1104,7 +1104,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + completablefuture1.thenAcceptAsync((either) -> { + either.ifLeft((chunk) -> { + this.tickingGenerated.getAndIncrement(); +- MutableObject mutableobject = new MutableObject(); ++ MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass + + this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { + this.playerLoadedChunk(entityplayer, mutableobject, chunk); +@@ -1283,7 +1283,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (objectiterator.hasNext()) { + ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); + ChunkPos chunkcoordintpair = playerchunk.getPos(); +- MutableObject mutableobject = new MutableObject(); ++ MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass + + this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { + SectionPos sectionposition = entityplayer.getLastSectionPos(); +@@ -1297,7 +1297,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + +- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { ++ protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass + if (player.level == this.level) { + if (newWithinViewDistance && !oldWithinViewDistance) { + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); +@@ -1834,12 +1834,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + +- private void playerLoadedChunk(ServerPlayer player, MutableObject cachedDataPacket, LevelChunk chunk) { +- if (cachedDataPacket.getValue() == null) { +- cachedDataPacket.setValue(new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true)); ++ // Paper start - Anti-Xray - Bypass ++ private void playerLoadedChunk(ServerPlayer player, MutableObject> cachedDataPackets, LevelChunk chunk) { ++ if (cachedDataPackets.getValue() == null) { ++ cachedDataPackets.setValue(new java.util.HashMap<>()); + } + +- player.trackChunk(chunk.getPos(), (Packet) cachedDataPacket.getValue()); ++ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); ++ player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> { ++ return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s); ++ })); ++ // Paper end + DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 21fdca3b4bdc49d32deaf3f11d5fecc1ed0d4626..5622917a2884e87d43abfaf58e722957931b3178 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -405,7 +405,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error + // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error +- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig))); // Paper ++ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor + this.pvpMode = minecraftserver.isPvpAllowed(); + this.convertable = convertable_conversionsession; + this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 3fadf6b46cc722ad81cf810c0761cf717e9f9b78..af00442931f9f6cf878bd61137c2f29fc7c8d0b1 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -49,7 +49,7 @@ import org.bukkit.event.player.PlayerInteractEvent; + public class ServerPlayerGameMode { + + private static final Logger LOGGER = LogUtils.getLogger(); +- protected ServerLevel level; ++ public ServerLevel level; // Paper - Anti-Xray - protected -> public + protected final ServerPlayer player; + private GameType gameModeForPlayer; + @Nullable +@@ -318,6 +318,8 @@ public class ServerPlayerGameMode { + } + + } ++ ++ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray + } + + public void destroyAndAck(BlockPos pos, int sequence, String reason) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 75e74a6bc0072eb9e77a0570d02d050ade0fa92d..7bd8ffb6bfba7f7188532ae3788701c08e1d624a 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -173,6 +173,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + // Paper end + ++ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; +@@ -191,7 +192,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public abstract ResourceKey getTypeKey(); + +- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper ++ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper + this.generator = gen; +@@ -275,6 +276,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); ++ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + } + + // Paper start +@@ -455,6 +457,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + + BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag ++ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray + + if (iblockdata1 == null) { + // CraftBukkit start - remove blockstate if failed (or the same) +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index 6cec5cda20531aadf8e2148908a70f8b573d7d82..dc164608bfb2fb18a1adf83fa10bac4028dcac0a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -99,17 +99,19 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + } + } + +- ChunkAccess.replaceMissingSections(heightLimitView, biome, this.sections); ++ ChunkAccess.replaceMissingSections(heightLimitView, biome, this.sections, pos); // Paper - Anti-Xray - Add parameters + // CraftBukkit start + this.biomeRegistry = biome; + } + public final Registry biomeRegistry; + // CraftBukkit end + +- private static void replaceMissingSections(LevelHeightAccessor world, Registry biome, LevelChunkSection[] sectionArray) { ++ // Paper start - Anti-Xray - Add parameters ++ private static void replaceMissingSections(LevelHeightAccessor world, Registry biome, LevelChunkSection[] sectionArray, ChunkPos pos) { + for (int i = 0; i < sectionArray.length; ++i) { + if (sectionArray[i] == null) { +- sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome); ++ sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome, pos, world instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) world : null); ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index e63086aba2512051fe1321f6e7e72b40276f5dde..004c8abeafa53cdc3efa0f45742132e3ad492d70 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -93,7 +93,7 @@ public class LevelChunk extends ChunkAccess { + } + + public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { +- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); ++ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry + this.tickersInLevel = Maps.newHashMap(); + this.clientLightReady = false; + this.level = (ServerLevel) world; // CraftBukkit - type +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index ae37e97e52557b48f129cc02eeea395378a48444..785fbcf9bafcdec1c5be213de3d8512690023415 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -36,10 +36,13 @@ public class LevelChunkSection { + this.recalcBlockCounts(); + } + +- public LevelChunkSection(int chunkPos, Registry biomeRegistry) { ++ // Paper start - Anti-Xray - Add parameters ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(int chunkPos, Registry biomeRegistry) { this(chunkPos, biomeRegistry, null, null); } ++ public LevelChunkSection(int chunkPos, Registry biomeRegistry, net.minecraft.world.level.ChunkPos pos, net.minecraft.world.level.Level level) { ++ // Paper end + this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos); +- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); +- this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, pos, this.bottomBlockY())); // Paper - Anti-Xray - Add preset block states ++ this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + public static int getBottomBlockY(int chunkPos) { +@@ -177,10 +180,13 @@ public class LevelChunkSection { + this.biomes = datapaletteblock; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null); } ++ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { + buf.writeShort(this.nonEmptyBlockCount); +- this.states.write(buf); +- this.biomes.write(buf); ++ this.states.write(buf, chunkPacketInfo, this.bottomBlockY()); ++ this.biomes.write(buf, null, this.bottomBlockY()); ++ // Paper end + } + + public int getSerializedSize() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 78e20871e4bd8d92c4475f797a55733c68f6aeb4..b688d239ff11b315f60cd980d8f6780b982a865b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -29,6 +29,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return 0; + }; + public final IdMap registry; ++ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; + private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); +@@ -41,14 +42,19 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + this.threadingDetector.checkAndUnlock(); + } + +- public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { +- PalettedContainerRO.Unpacker> unpacker = PalettedContainer::unpack; ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); } ++ public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues); ++ }; ++ // Paper end + return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker); + } + + public static Codec> codecRO(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { + PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { +- return unpack(idListx, paletteProviderx, serialized).map((result) -> { ++ return unpack(idListx, paletteProviderx, serialized, defaultValue, null).map((result) -> { // Paper - Anti-Xray - Add preset values + return result; + }); + }; +@@ -65,19 +71,52 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + }); + } + +- public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } ++ public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; + this.registry = idList; + this.strategy = paletteProvider; + this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); ++ ++ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) { ++ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us ++ // We readd this here but in a smarter way than it was before ++ int maxSize = 1 << dataProvider.bits(); ++ ++ for (T presetValue : presetValues) { ++ if (this.data.palette.getSize() >= maxSize) { ++ java.util.Set allValues = new java.util.HashSet<>(paletteEntries); ++ allValues.addAll(Arrays.asList(presetValues)); ++ int newBits = Mth.ceillog2(allValues.size()); ++ ++ if (newBits > dataProvider.bits()) { ++ this.onResize(newBits, null); ++ } ++ ++ break; ++ } ++ ++ this.data.palette.idFor(presetValue); ++ } ++ } ++ // Paper end + } + +- private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { ++ // Paper start - Anti-Xray - Add preset values ++ private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.registry = idList; + this.strategy = paletteProvider; + this.data = data; + } + +- public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { ++ // Paper start - Anti-Xray - Add preset values ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } ++ public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) { ++ this.presetValues = presetValues; ++ // Paper end + this.strategy = paletteProvider; + this.registry = idList; + this.data = this.createOrReuseData((PalettedContainer.Data)null, 0); +@@ -92,11 +131,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + @Override + public int onResize(int newBits, T object) { + PalettedContainer.Data data = this.data; ++ ++ // Paper start - Anti-Xray - Add preset values ++ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) { ++ int duplicates = 0; ++ List presetValues = Arrays.asList(this.presetValues); ++ duplicates += presetValues.contains(object) ? 1 : 0; ++ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0; ++ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates); ++ } ++ + PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); + data2.copyFrom(data.palette, data.storage); + this.data = data2; +- return data2.palette.idFor(object); ++ this.addPresetValues(); ++ return object == null ? -1 : data2.palette.idFor(object); ++ // Paper end ++ } ++ ++ // Paper start - Anti-Xray - Add preset values ++ private void addPresetValues() { ++ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) { ++ for (T presetValue : this.presetValues) { ++ this.data.palette.idFor(presetValue); ++ } ++ } + } ++ // Paper end + + public T getAndSet(int x, int y, int z, T value) { + this.acquire(); +@@ -166,25 +227,36 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + data.palette.read(buf); + buf.readLongArray(data.storage.getRaw()); + this.data = data; ++ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server) + } finally { + this.release(); + } + + } + ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Override ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } + @Override +- public void write(FriendlyByteBuf buf) { ++ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { + this.acquire(); + + try { +- this.data.write(buf); ++ this.data.write(buf, chunkPacketInfo, bottomBlockY); ++ ++ if (chunkPacketInfo != null) { ++ // Bottom block to 0 based chunk section index ++ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); ++ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); ++ } ++ // Paper end + } finally { + this.release(); + } + + } + +- private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized) { ++ private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values + List list = serialized.paletteEntries(); + int i = paletteProvider.size(); + int j = paletteProvider.calculateBitsForSerialization(idList, list.size()); +@@ -220,7 +292,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + } + +- return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list)); ++ return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -280,12 +352,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + public PalettedContainer copy() { +- return new PalettedContainer<>(this.registry, this.strategy, this.data.copy()); ++ return new PalettedContainer<>(this.registry, this.strategy, this.data.copy(), this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override + public PalettedContainer recreate() { +- return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy); ++ return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy, this.presetValues); // Paper - Anti-Xray - Add preset values + } + + @Override +@@ -329,9 +401,20 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return 1 + this.palette.getSerializedSize() + FriendlyByteBuf.getVarIntSize(this.storage.getSize()) + this.storage.getRaw().length * 8; + } + +- public void write(FriendlyByteBuf buf) { ++ // Paper start - Anti-Xray - Add chunk packet info ++ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { + buf.writeByte(this.storage.getBits()); + this.palette.write(buf); ++ ++ if (chunkPacketInfo != null) { ++ // Bottom block to 0 based chunk section index ++ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); ++ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); ++ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); ++ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getRaw().length)); ++ } ++ // Paper end ++ + buf.writeLongArray(this.storage.getRaw()); + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +index 9a2bf744abd8916d492e901be889223591bac3fd..a27fce0f1af9776a713bf1b5277869ed5d3e0c8e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java +@@ -14,7 +14,10 @@ public interface PalettedContainerRO { + + void getAll(Consumer action); + +- void write(FriendlyByteBuf buf); ++ // Paper start - Anti-Xray - Add chunk packet info ++ @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf); ++ void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY); ++ // Paper end + + int getSerializedSize(); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 864e591b10360b0f12fe5c5a650da372555ebd10..f26a08f81495dde6205b34254d159b042e5a6ea9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -69,7 +69,7 @@ import org.slf4j.Logger; + + public class ChunkSerializer { + +- public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); ++ public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String TAG_UPGRADE_DATA = "UpgradeData"; + private static final String BLOCK_TICKS_TAG = "block_ticks"; +@@ -149,16 +149,20 @@ public class ChunkSerializer { + if (k >= 0 && k < achunksection.length) { + Logger logger; + PalettedContainer datapaletteblock; ++ // Paper start - Anti-Xray - Add preset block states ++ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0 << 4); + + if (nbttagcompound1.contains("block_states", 10)) { +- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { ++ Codec> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); ++ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { + ChunkSerializer.logErrors(chunkPos, b0, s); + }); + logger = ChunkSerializer.LOGGER; + Objects.requireNonNull(logger); + datapaletteblock = (PalettedContainer) ((DataResult>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); ++ // Paper end + } + + PalettedContainer object; // CraftBukkit - read/write +@@ -171,7 +175,7 @@ public class ChunkSerializer { + Objects.requireNonNull(logger); + object = ((DataResult>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error + } else { +- object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); ++ object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + + LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write +@@ -439,7 +443,7 @@ public class ChunkSerializer { + + // CraftBukkit start - read/write + private static Codec>> makeBiomeCodecRW(Registry iregistry) { +- return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); ++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index dfdcc01c7cb2864d9b5f572c0cafedf16063edd8..d9c2e7e18e1ede37d92cecb8ddb32dae1472bd1c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -54,7 +54,7 @@ public class CraftChunk implements Chunk { + private final ServerLevel worldServer; + private final int x; + private final int z; +- private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); ++ private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states + private static final byte[] emptyLight = new byte[2048]; + + public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e8c11b59dc50fd9c5bbf073b66d0cd9c504d7c25..8cc2a35486e8c6433e722ddc5e776c3332e7c7fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2235,7 +2235,7 @@ public final class CraftServer implements Server { + public ChunkGenerator.ChunkData createChunkData(World world) { + Validate.notNull(world, "World cannot be null"); + ServerLevel handle = ((CraftWorld) world).getHandle(); +- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY)); ++ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY), world); // Paper - Anti-Xray - Add parameters + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 868d9d13b03751b24c4b6695f97ad343ff8d0b8e..a8ab324bfbaaf946af5998402588244465dd7286 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -406,11 +406,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { + List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); + if (playersInRange.isEmpty()) return; + +- ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, true); ++ // Paper start - Anti-Xray - Bypass ++ Map refreshPackets = new HashMap<>(); + for (ServerPlayer player : playersInRange) { + if (player.connection == null) continue; + +- player.connection.send(refreshPacket); ++ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); ++ player.connection.send(refreshPackets.computeIfAbsent(shouldModify, s -> { // Use connection to prevent creating firing event ++ return new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, true, (Boolean) s); ++ })); ++ // Paper end + } + }); + }); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +index 960405935e395a31c0300773c41413801cf0d290..4a23d03757e1735b9ebb8c003adcc0374a7d672d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + private final Registry biomes; + private Set tiles; + private final Set lights = new HashSet<>(); ++ // Paper start - Anti-Xray - Add parameters ++ private final World world; + +- public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { ++ @Deprecated @io.papermc.paper.annotation.DoNotUse public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { this(minHeight, maxHeight, biomes, null); } ++ public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes, World world) { ++ this.world = world; ++ // Paper end + this.minHeight = minHeight; + this.maxHeight = maxHeight; + this.biomes = biomes; +@@ -176,7 +181,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + int offset = (y - this.minHeight) >> 4; + LevelChunkSection section = this.sections[offset]; + if (create && section == null) { +- this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes); ++ this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes, null, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null); // Paper - Anti-Xray - Add parameters + } + return section; + } diff --git a/patches/server/0356-Fix-Light-Command.patch b/patches/server/0356-Fix-Light-Command.patch deleted file mode 100644 index a411a251f1..0000000000 --- a/patches/server/0356-Fix-Light-Command.patch +++ /dev/null @@ -1,186 +0,0 @@ -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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -2,6 +2,7 @@ package io.papermc.paper.command; - - import io.papermc.paper.command.subcommands.ChunkDebugCommand; - import io.papermc.paper.command.subcommands.EntityCommand; -+import io.papermc.paper.command.subcommands.FixLightCommand; - import io.papermc.paper.command.subcommands.HeapDumpCommand; - import io.papermc.paper.command.subcommands.ReloadCommand; - import io.papermc.paper.command.subcommands.VersionCommand; -@@ -42,6 +43,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("reload"), new ReloadCommand()); - commands.put(Set.of("version"), new VersionCommand()); - commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); -+ commands.put(Set.of("fixlight"), new FixLightCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -0,0 +1,121 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayDeque; -+import java.util.Deque; -+import net.minecraft.server.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ThreadedLevelLightEngine; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.LevelChunk; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class FixLightCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doFixLight(sender, args); -+ return true; -+ } -+ -+ private void doFixLight(final CommandSender sender, final String[] args) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage(text("Only players can use this command", RED)); -+ return; -+ } -+ @Nullable Runnable post = null; -+ int radius = 2; -+ if (args.length > 0) { -+ try { -+ final int parsed = Integer.parseInt(args[0]); -+ if (parsed < 0) { -+ sender.sendMessage(text("Radius cannot be negative!", RED)); -+ return; -+ } -+ final int maxRadius = 5; -+ radius = Math.min(maxRadius, parsed); -+ if (radius != parsed) { -+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); -+ } -+ } catch (final Exception e) { -+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); -+ return; -+ } -+ } -+ -+ CraftPlayer player = (CraftPlayer) sender; -+ ServerPlayer handle = player.getHandle(); -+ ServerLevel world = (ServerLevel) handle.level; -+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ -+ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); -+ Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); -+ updateLight(sender, world, lightengine, queue, post); -+ } -+ -+ private void updateLight( -+ final CommandSender sender, -+ final ServerLevel world, -+ final ThreadedLevelLightEngine lightengine, -+ final Deque queue, -+ final @Nullable Runnable done -+ ) { -+ @Nullable ChunkPos coord = queue.poll(); -+ if (coord == null) { -+ sender.sendMessage(text("All Chunks Light updated", GREEN)); -+ if (done != null) { -+ done.run(); -+ } -+ return; -+ } -+ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { -+ if (ex != null) { -+ sender.sendMessage(text("Error loading chunk " + coord, RED)); -+ updateLight(sender, world, lightengine, queue, done); -+ return; -+ } -+ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); -+ if (chunk == null) { -+ updateLight(sender, world, lightengine, queue, done); -+ return; -+ } -+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue -+ sender.sendMessage(text("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++) { -+ net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z); -+ lightengine.checkBlock(pos); -+ } -+ } -+ } -+ lightengine.tryScheduleUpdate(); -+ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey); -+ if (visibleChunk != null) { -+ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> { -+ MinecraftServer.getServer().processQueue.add(() -> { -+ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false); -+ updateLight(sender, world, lightengine, queue, done); -+ }); -+ }); -+ } else { -+ updateLight(sender, world, lightengine, queue, done); -+ } -+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize); -+ }, MinecraftServer.getServer()); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 4b24e4d947e96ea0720f8f6bc33470e07c00310d..d60173b03baee4a66da1109795bf6a19737b8bd0 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -141,6 +141,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final ChunkTaskPriorityQueueSorter queueSorter; - private final ProcessorHandle> worldgenMailbox; - public final ProcessorHandle> mainThreadMailbox; -+ // Paper start -+ final ProcessorHandle> mailboxLight; -+ public void addLightTask(ChunkHolder playerchunk, Runnable run) { -+ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run)); -+ } -+ // Paper end - public final ChunkProgressListener progressListener; - private final ChunkStatusUpdateListener chunkStatusListener; - public final ChunkMap.ChunkDistanceManager distanceManager; -@@ -284,11 +290,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - this.progressListener = worldGenerationProgressListener; - this.chunkStatusListener = chunkStatusChangeListener; -- ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); -+ ProcessorMailbox lightthreaded; ProcessorMailbox threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper - - this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE); - this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false); - this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false); -+ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper - this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false)); - this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor); - this.overworldDataStorage = persistentStateManagerFactory; diff --git a/patches/server/0357-Anti-Xray.patch b/patches/server/0357-Anti-Xray.patch deleted file mode 100644 index 646c29afbb..0000000000 --- a/patches/server/0357-Anti-Xray.patch +++ /dev/null @@ -1,1607 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Thu, 25 Nov 2021 13:27:51 +0100 -Subject: [PATCH] Anti-Xray - - -diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e448c26327b5f6189c3c52e698cff66c8f9ad81a ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageReader.java -@@ -0,0 +1,51 @@ -+package com.destroystokyo.paper.antixray; -+ -+public final class BitStorageReader { -+ -+ private byte[] buffer; -+ private int bits; -+ private int mask; -+ private int longInBufferIndex; -+ private int bitInLongIndex; -+ private long current; -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public void setBits(int bits) { -+ this.bits = bits; -+ mask = (1 << bits) - 1; -+ } -+ -+ public void setIndex(int index) { -+ longInBufferIndex = index; -+ bitInLongIndex = 0; -+ init(); -+ } -+ -+ private void init() { -+ if (buffer.length > longInBufferIndex + 7) { -+ current = ((((long) buffer[longInBufferIndex]) << 56) -+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) -+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) -+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) -+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) -+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) -+ | (((long) buffer[longInBufferIndex + 7] & 0xff))); -+ } -+ } -+ -+ public int read() { -+ if (bitInLongIndex + bits > 64) { -+ bitInLongIndex = 0; -+ longInBufferIndex += 8; -+ init(); -+ } -+ -+ int value = (int) (current >>> bitInLongIndex) & mask; -+ bitInLongIndex += bits; -+ return value; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e4540ea278f2dc871cb6a3cb8897559bfd65e134 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/BitStorageWriter.java -@@ -0,0 +1,79 @@ -+package com.destroystokyo.paper.antixray; -+ -+public final class BitStorageWriter { -+ -+ private byte[] buffer; -+ private int bits; -+ private long mask; -+ private int longInBufferIndex; -+ private int bitInLongIndex; -+ private long current; -+ private boolean dirty; -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public void setBits(int bits) { -+ this.bits = bits; -+ mask = (1L << bits) - 1; -+ } -+ -+ public void setIndex(int index) { -+ longInBufferIndex = index; -+ bitInLongIndex = 0; -+ init(); -+ } -+ -+ private void init() { -+ if (buffer.length > longInBufferIndex + 7) { -+ current = ((((long) buffer[longInBufferIndex]) << 56) -+ | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) -+ | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -+ | (((long) buffer[longInBufferIndex + 3] & 0xff) << 32) -+ | (((long) buffer[longInBufferIndex + 4] & 0xff) << 24) -+ | (((long) buffer[longInBufferIndex + 5] & 0xff) << 16) -+ | (((long) buffer[longInBufferIndex + 6] & 0xff) << 8) -+ | (((long) buffer[longInBufferIndex + 7] & 0xff))); -+ } -+ -+ dirty = false; -+ } -+ -+ public void flush() { -+ if (dirty && buffer.length > longInBufferIndex + 7) { -+ buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); -+ buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); -+ buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); -+ buffer[longInBufferIndex + 3] = (byte) (current >> 32 & 0xff); -+ buffer[longInBufferIndex + 4] = (byte) (current >> 24 & 0xff); -+ buffer[longInBufferIndex + 5] = (byte) (current >> 16 & 0xff); -+ buffer[longInBufferIndex + 6] = (byte) (current >> 8 & 0xff); -+ buffer[longInBufferIndex + 7] = (byte) (current & 0xff); -+ } -+ } -+ -+ public void write(int value) { -+ if (bitInLongIndex + bits > 64) { -+ flush(); -+ bitInLongIndex = 0; -+ longInBufferIndex += 8; -+ init(); -+ } -+ -+ current = current & ~(mask << bitInLongIndex) | (value & mask) << bitInLongIndex; -+ dirty = true; -+ bitInLongIndex += bits; -+ } -+ -+ public void skip() { -+ bitInLongIndex += bits; -+ -+ if (bitInLongIndex > 64) { -+ flush(); -+ bitInLongIndex = bits; -+ longInBufferIndex += 8; -+ init(); -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bd86dc2ad2f87969da4add06de2a629f69d4b5de ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockController.java -@@ -0,0 +1,45 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ServerPlayerGameMode; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.LevelChunk; -+ -+public class ChunkPacketBlockController { -+ -+ public static final ChunkPacketBlockController NO_OPERATION_INSTANCE = new ChunkPacketBlockController(); -+ -+ protected ChunkPacketBlockController() { -+ -+ } -+ -+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) { -+ return null; -+ } -+ -+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { -+ return false; -+ } -+ -+ public ChunkPacketInfo getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ return null; -+ } -+ -+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { -+ chunkPacket.setReady(true); -+ } -+ -+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { -+ -+ } -+ -+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { -+ -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dabd93c35bdbac6a8b668a82d5f3d4173a1baa4a ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -@@ -0,0 +1,635 @@ -+package com.destroystokyo.paper.antixray; -+ -+import io.papermc.paper.configuration.WorldConfiguration; -+import io.papermc.paper.configuration.type.EngineMode; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.Registry; -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.ServerPlayerGameMode; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.biome.Biomes; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.EntityBlock; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.*; -+import org.bukkit.Bukkit; -+ -+import java.util.*; -+import java.util.concurrent.Executor; -+import java.util.concurrent.ThreadLocalRandom; -+import java.util.function.IntSupplier; -+ -+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { -+ -+ private static final Palette GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY); -+ private static final LevelChunkSection EMPTY_SECTION = null; -+ private final Executor executor; -+ private final EngineMode engineMode; -+ private final int maxBlockHeight; -+ private final int updateRadius; -+ private final boolean usePermission; -+ private final BlockState[] presetBlockStates; -+ private final BlockState[] presetBlockStatesFull; -+ private final BlockState[] presetBlockStatesStone; -+ private final BlockState[] presetBlockStatesDeepslate; -+ private final BlockState[] presetBlockStatesNetherrack; -+ private final BlockState[] presetBlockStatesEndStone; -+ private final int[] presetBlockStateBitsGlobal; -+ private final int[] presetBlockStateBitsStoneGlobal; -+ private final int[] presetBlockStateBitsDeepslateGlobal; -+ private final int[] presetBlockStateBitsNetherrackGlobal; -+ private final int[] presetBlockStateBitsEndStoneGlobal; -+ private final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; -+ private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()]; -+ private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION}; -+ private final int maxBlockHeightUpdatePosition; -+ -+ public ChunkPacketBlockControllerAntiXray(Level level, Executor executor) { -+ this.executor = executor; -+ WorldConfiguration.Anticheat.AntiXray paperWorldConfig = level.paperConfig().anticheat.antiXray; -+ engineMode = paperWorldConfig.engineMode; -+ maxBlockHeight = paperWorldConfig.maxBlockHeight >> 4 << 4; -+ updateRadius = paperWorldConfig.updateRadius; -+ usePermission = paperWorldConfig.usePermission; -+ List toObfuscate; -+ -+ if (engineMode == EngineMode.HIDE) { -+ toObfuscate = paperWorldConfig.hiddenBlocks; -+ presetBlockStates = null; -+ presetBlockStatesFull = null; -+ presetBlockStatesStone = new BlockState[]{Blocks.STONE.defaultBlockState()}; -+ presetBlockStatesDeepslate = new BlockState[]{Blocks.DEEPSLATE.defaultBlockState()}; -+ presetBlockStatesNetherrack = new BlockState[]{Blocks.NETHERRACK.defaultBlockState()}; -+ presetBlockStatesEndStone = new BlockState[]{Blocks.END_STONE.defaultBlockState()}; -+ presetBlockStateBitsGlobal = null; -+ presetBlockStateBitsStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.STONE.defaultBlockState())}; -+ presetBlockStateBitsDeepslateGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.DEEPSLATE.defaultBlockState())}; -+ presetBlockStateBitsNetherrackGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.NETHERRACK.defaultBlockState())}; -+ presetBlockStateBitsEndStoneGlobal = new int[]{GLOBAL_BLOCKSTATE_PALETTE.idFor(Blocks.END_STONE.defaultBlockState())}; -+ } else { -+ toObfuscate = new ArrayList<>(paperWorldConfig.replacementBlocks); -+ List presetBlockStateList = new LinkedList<>(); -+ -+ for (String id : paperWorldConfig.hiddenBlocks) { -+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(id)).orElse(null); -+ -+ if (block != null && !(block instanceof EntityBlock)) { -+ toObfuscate.add(id); -+ presetBlockStateList.add(block.defaultBlockState()); -+ } -+ } -+ -+ // The doc of the LinkedHashSet(Collection) constructor doesn't specify that the insertion order is the predictable iteration order of the specified Collection, although it is in the implementation -+ Set presetBlockStateSet = new LinkedHashSet<>(); -+ // Therefore addAll(Collection) is used, which guarantees this order in the doc -+ presetBlockStateSet.addAll(presetBlockStateList); -+ presetBlockStates = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateSet.toArray(new BlockState[0]); -+ presetBlockStatesFull = presetBlockStateSet.isEmpty() ? new BlockState[]{Blocks.DIAMOND_ORE.defaultBlockState()} : presetBlockStateList.toArray(new BlockState[0]); -+ presetBlockStatesStone = null; -+ presetBlockStatesDeepslate = null; -+ presetBlockStatesNetherrack = null; -+ presetBlockStatesEndStone = null; -+ presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length]; -+ -+ for (int i = 0; i < presetBlockStatesFull.length; i++) { -+ presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]); -+ } -+ -+ presetBlockStateBitsStoneGlobal = null; -+ presetBlockStateBitsDeepslateGlobal = null; -+ presetBlockStateBitsNetherrackGlobal = null; -+ presetBlockStateBitsEndStoneGlobal = null; -+ } -+ -+ for (String id : toObfuscate) { -+ Block block = Registry.BLOCK.getOptional(new ResourceLocation(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.defaultBlockState().isAir()) { -+ // Replace all block states of a specified block -+ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { -+ obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true; -+ } -+ } -+ } -+ -+ EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getHolderOrThrow(Biomes.PLAINS)); -+ BlockPos zeroPos = new BlockPos(0, 0, 0); -+ -+ for (int i = 0; i < solidGlobal.length; i++) { -+ BlockState blockState = GLOBAL_BLOCKSTATE_PALETTE.valueFor(i); -+ -+ if (blockState != null) { -+ solidGlobal[i] = blockState.isRedstoneConductor(emptyChunk, zeroPos) -+ && blockState.getBlock() != Blocks.SPAWNER && blockState.getBlock() != Blocks.BARRIER && blockState.getBlock() != Blocks.SHULKER_BOX && blockState.getBlock() != Blocks.SLIME_BLOCK && blockState.getBlock() != Blocks.MANGROVE_ROOTS || paperWorldConfig.lavaObscures && blockState == Blocks.LAVA.defaultBlockState(); -+ // Comparing blockState == Blocks.LAVA.defaultBlockState() instead of blockState.getBlock() == Blocks.LAVA ensures that only "stationary lava" is used -+ // shulker box checks TE. -+ } -+ } -+ -+ maxBlockHeightUpdatePosition = maxBlockHeight + updateRadius - 1; -+ } -+ -+ private int getPresetBlockStatesFullLength() { -+ return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length; -+ } -+ -+ @Override -+ public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) { -+ // Return the block states to be added to the paletted containers so that they can be used for obfuscation -+ if (bottomBlockY < maxBlockHeight) { -+ if (engineMode == EngineMode.HIDE) { -+ return switch (level.getWorld().getEnvironment()) { -+ case NETHER -> presetBlockStatesNetherrack; -+ case THE_END -> presetBlockStatesEndStone; -+ default -> bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone; -+ }; -+ } -+ -+ return presetBlockStates; -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public boolean shouldModify(ServerPlayer player, LevelChunk chunk) { -+ return !usePermission || !player.getBukkitEntity().hasPermission("paper.antixray.bypass"); -+ } -+ -+ @Override -+ public ChunkPacketInfoAntiXray getChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ // Return a new instance to collect data and objects in the right state while creating the chunk packet for thread safe access later -+ return new ChunkPacketInfoAntiXray(chunkPacket, chunk, this); -+ } -+ -+ @Override -+ public void modifyBlocks(ClientboundLevelChunkWithLightPacket chunkPacket, ChunkPacketInfo chunkPacketInfo) { -+ if (!(chunkPacketInfo instanceof ChunkPacketInfoAntiXray)) { -+ chunkPacket.setReady(true); -+ return; -+ } -+ -+ if (!Bukkit.isPrimaryThread()) { -+ // Plugins? -+ MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); -+ return; -+ } -+ -+ LevelChunk chunk = chunkPacketInfo.getChunk(); -+ int x = chunk.getPos().x; -+ int z = chunk.getPos().z; -+ Level level = chunk.getLevel(); -+ ((ChunkPacketInfoAntiXray) chunkPacketInfo).setNearbyChunks(level.getChunkIfLoaded(x - 1, z), level.getChunkIfLoaded(x + 1, z), level.getChunkIfLoaded(x, z - 1), level.getChunkIfLoaded(x, z + 1)); -+ executor.execute((Runnable) chunkPacketInfo); -+ } -+ -+ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal) -+ // If an ExecutorService with multiple threads is used, ThreadLocal must be used here -+ private final ThreadLocal presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]); -+ private static final ThreadLocal SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); -+ private static final ThreadLocal OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]); -+ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate -+ private static final ThreadLocal CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ private static final ThreadLocal NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ private static final ThreadLocal NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]); -+ -+ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { -+ int[] presetBlockStateBits = this.presetBlockStateBits.get(); -+ boolean[] solid = SOLID.get(); -+ boolean[] obfuscate = OBFUSCATE.get(); -+ boolean[][] current = CURRENT.get(); -+ boolean[][] next = NEXT.get(); -+ boolean[][] nextNext = NEXT_NEXT.get(); -+ // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it -+ BitStorageReader bitStorageReader = new BitStorageReader(); -+ BitStorageWriter bitStorageWriter = new BitStorageWriter(); -+ LevelChunkSection[] nearbyChunkSections = new LevelChunkSection[4]; -+ LevelChunk chunk = chunkPacketInfoAntiXray.getChunk(); -+ Level level = chunk.getLevel(); -+ int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount()) - 1; -+ boolean[] solidTemp = null; -+ boolean[] obfuscateTemp = null; -+ bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); -+ bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); -+ int numberOfBlocks = presetBlockStateBits.length; -+ // Keep the lambda expressions as simple as possible. They are used very frequently. -+ 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.getPresetValues(chunkSectionIndex) != null) { -+ int[] presetBlockStateBitsTemp; -+ -+ if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) { -+ if (engineMode == EngineMode.HIDE) { -+ presetBlockStateBitsTemp = switch (level.getWorld().getEnvironment()) { -+ case NETHER -> presetBlockStateBitsNetherrackGlobal; -+ case THE_END -> presetBlockStateBitsEndStoneGlobal; -+ default -> chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal; -+ }; -+ } else { -+ presetBlockStateBitsTemp = presetBlockStateBitsGlobal; -+ } -+ } else { -+ // If it's presetBlockStates, use this.presetBlockStatesFull instead -+ BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex); -+ presetBlockStateBitsTemp = presetBlockStateBits; -+ -+ for (int i = 0; i < presetBlockStateBitsTemp.length; i++) { -+ // This is thread safe because we only request IDs that are guaranteed to be in the palette and are visible -+ // For more details see the comments in the readPalette method -+ presetBlockStateBitsTemp[i] = chunkPacketInfoAntiXray.getPalette(chunkSectionIndex).idFor(presetBlockStatesFull[i]); -+ } -+ } -+ -+ bitStorageWriter.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); -+ -+ // Check if the chunk section below was not obfuscated -+ if (chunkSectionIndex == 0 || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex - 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex - 1) == null) { -+ // If so, initialize some stuff -+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); -+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex)); -+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), solid, solidGlobal); -+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex), obfuscate, obfuscateGlobal); -+ // Read the blocks of the upper layer of the chunk section below if it exists -+ LevelChunkSection belowChunkSection = null; -+ boolean skipFirstLayer = chunkSectionIndex == 0 || (belowChunkSection = chunk.getSections()[chunkSectionIndex - 1]) == EMPTY_SECTION; -+ -+ for (int z = 0; z < 16; z++) { -+ for (int x = 0; x < 16; x++) { -+ current[z][x] = true; -+ next[z][x] = skipFirstLayer || isTransparent(belowChunkSection, x, 15, z); -+ } -+ } -+ -+ // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section -+ bitStorageWriter.setBits(0); -+ obfuscateLayer(-1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, emptyNearbyChunkSections, random); -+ } -+ -+ bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex)); -+ nearbyChunkSections[0] = chunkPacketInfoAntiXray.getNearbyChunks()[0] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[0].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[1] = chunkPacketInfoAntiXray.getNearbyChunks()[1] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[1].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[2] = chunkPacketInfoAntiXray.getNearbyChunks()[2] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[2].getSections()[chunkSectionIndex]; -+ nearbyChunkSections[3] = chunkPacketInfoAntiXray.getNearbyChunks()[3] == null ? EMPTY_SECTION : chunkPacketInfoAntiXray.getNearbyChunks()[3].getSections()[chunkSectionIndex]; -+ -+ // Obfuscate all layers of the current chunk section except the upper one -+ for (int y = 0; y < 15; y++) { -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ obfuscateLayer(y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ -+ // Check if the chunk section above doesn't need obfuscation -+ if (chunkSectionIndex == maxChunkSectionIndex || !chunkPacketInfoAntiXray.isWritten(chunkSectionIndex + 1) || chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex + 1) == null) { -+ // If so, obfuscate the upper layer of the current chunk section by reading blocks of the first layer from the chunk section above if it exists -+ LevelChunkSection aboveChunkSection; -+ -+ if (chunkSectionIndex != chunk.getSectionsCount() - 1 && (aboveChunkSection = chunk.getSections()[chunkSectionIndex + 1]) != EMPTY_SECTION) { -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ -+ for (int z = 0; z < 16; z++) { -+ for (int x = 0; x < 16; x++) { -+ if (isTransparent(aboveChunkSection, x, 0, z)) { -+ current[z][x] = true; -+ } -+ } -+ } -+ -+ // There is nothing to read anymore -+ bitStorageReader.setBits(0); -+ solid[0] = true; -+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ } else { -+ // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section -+ bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1)); -+ bitStorageReader.setIndex(chunkPacketInfoAntiXray.getIndex(chunkSectionIndex + 1)); -+ solidTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), solid, solidGlobal); -+ obfuscateTemp = readPalette(chunkPacketInfoAntiXray.getPalette(chunkSectionIndex + 1), obfuscate, obfuscateGlobal); -+ boolean[][] temp = current; -+ current = next; -+ next = nextNext; -+ nextNext = temp; -+ obfuscateLayer(15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, presetBlockStateBitsTemp, current, next, nextNext, nearbyChunkSections, random); -+ } -+ -+ bitStorageWriter.flush(); -+ } -+ } -+ -+ chunkPacketInfoAntiXray.getChunkPacket().setReady(true); -+ } -+ -+ private void obfuscateLayer(int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, LevelChunkSection[] nearbyChunkSections, IntSupplier random) { -+ // First block of first line -+ int bits = bitStorageReader.read(); -+ -+ if (nextNext[0][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][1] = true; -+ next[1][0] = true; -+ } else { -+ if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][0] = true; -+ } -+ -+ // First line -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[0][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][x - 1] = true; -+ next[0][x + 1] = true; -+ next[1][x] = true; -+ } else { -+ if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][x] = true; -+ } -+ } -+ -+ // Last block of first line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[0][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[0][14] = true; -+ next[1][15] = true; -+ } else { -+ if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[0][15] = true; -+ } -+ -+ // All inner lines -+ for (int z = 1; z < 15; z++) { -+ // First block -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][1] = true; -+ next[z - 1][0] = true; -+ next[z + 1][0] = true; -+ } else { -+ if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][0] = true; -+ } -+ -+ // All inner blocks -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][x - 1] = true; -+ next[z][x + 1] = true; -+ next[z - 1][x] = true; -+ next[z + 1][x] = true; -+ } else { -+ if (current[z][x]) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][x] = true; -+ } -+ } -+ -+ // Last block -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[z][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[z][14] = true; -+ next[z - 1][15] = true; -+ next[z + 1][15] = true; -+ } else { -+ if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[z][15] = true; -+ } -+ } -+ -+ // First block of last line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][0] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][1] = true; -+ next[14][0] = true; -+ } else { -+ if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][0] = true; -+ } -+ -+ // Last line -+ for (int x = 1; x < 15; x++) { -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][x] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][x - 1] = true; -+ next[15][x + 1] = true; -+ next[14][x] = true; -+ } else { -+ if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][x] = true; -+ } -+ } -+ -+ // Last block of last line -+ bits = bitStorageReader.read(); -+ -+ if (nextNext[15][15] = !solid[bits]) { -+ bitStorageWriter.skip(); -+ next[15][14] = true; -+ next[14][15] = true; -+ } else { -+ if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) { -+ bitStorageWriter.skip(); -+ } else { -+ bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); -+ } -+ } -+ -+ if (!obfuscate[bits]) { -+ next[15][15] = true; -+ } -+ } -+ -+ private boolean isTransparent(LevelChunkSection chunkSection, int x, int y, int z) { -+ if (chunkSection == EMPTY_SECTION) { -+ return true; -+ } -+ -+ try { -+ return !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(chunkSection.getBlockState(x, y, z))]; -+ } catch (MissingPaletteEntryException e) { -+ // Race condition / visibility issue / no happens-before relationship -+ // We don't care and treat the block as transparent -+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur -+ return true; -+ } -+ } -+ -+ private boolean[] readPalette(Palette palette, boolean[] temp, boolean[] global) { -+ if (palette instanceof GlobalPalette) { -+ return global; -+ } -+ -+ try { -+ for (int i = 0; i < palette.getSize(); i++) { -+ temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))]; -+ } -+ } catch (MissingPaletteEntryException e) { -+ // Race condition / visibility issue / no happens-before relationship -+ // We don't care because we at least see the state as it was when the chunk packet was created -+ // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here -+ // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data -+ } -+ -+ return temp; -+ } -+ -+ @Override -+ public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) { -+ if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) { -+ updateNearbyBlocks(level, blockPos); -+ } -+ } -+ -+ @Override -+ public void onPlayerLeftClickBlock(ServerPlayerGameMode serverPlayerGameMode, BlockPos blockPos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { -+ if (blockPos.getY() <= maxBlockHeightUpdatePosition) { -+ updateNearbyBlocks(serverPlayerGameMode.level, blockPos); -+ } -+ } -+ -+ private void updateNearbyBlocks(Level level, BlockPos blockPos) { -+ if (updateRadius >= 2) { -+ BlockPos temp = blockPos.west(); -+ updateBlock(level, temp); -+ updateBlock(level, temp.west()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.east()); -+ updateBlock(level, temp.east()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.below()); -+ updateBlock(level, temp.below()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.above()); -+ updateBlock(level, temp.above()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp.south()); -+ updateBlock(level, temp = blockPos.north()); -+ updateBlock(level, temp.north()); -+ updateBlock(level, temp = blockPos.south()); -+ updateBlock(level, temp.south()); -+ } else if (updateRadius == 1) { -+ updateBlock(level, blockPos.west()); -+ updateBlock(level, blockPos.east()); -+ updateBlock(level, blockPos.below()); -+ updateBlock(level, blockPos.above()); -+ updateBlock(level, blockPos.north()); -+ updateBlock(level, blockPos.south()); -+ } else { -+ // Do nothing if updateRadius <= 0 (test mode) -+ } -+ } -+ -+ private void updateBlock(Level level, BlockPos blockPos) { -+ BlockState blockState = level.getBlockStateIfLoaded(blockPos); -+ -+ if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) { -+ ((ServerLevel) level).getChunkSource().blockChanged(blockPos); -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d98a3f5c54c67a673eb7dc456dd039cd78f9c34d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfo.java -@@ -0,0 +1,80 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.Palette; -+ -+public class ChunkPacketInfo { -+ -+ private final ClientboundLevelChunkWithLightPacket chunkPacket; -+ private final LevelChunk chunk; -+ private final int[] bits; -+ private final Object[] palettes; -+ private final int[] indexes; -+ private final Object[][] presetValues; -+ private byte[] buffer; -+ -+ public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { -+ this.chunkPacket = chunkPacket; -+ this.chunk = chunk; -+ int sections = chunk.getSectionsCount(); -+ bits = new int[sections]; -+ palettes = new Object[sections]; -+ indexes = new int[sections]; -+ presetValues = new Object[sections][]; -+ } -+ -+ public ClientboundLevelChunkWithLightPacket getChunkPacket() { -+ return chunkPacket; -+ } -+ -+ public LevelChunk getChunk() { -+ return chunk; -+ } -+ -+ public byte[] getBuffer() { -+ return buffer; -+ } -+ -+ public void setBuffer(byte[] buffer) { -+ this.buffer = buffer; -+ } -+ -+ public int getBits(int chunkSectionIndex) { -+ return bits[chunkSectionIndex]; -+ } -+ -+ public void setBits(int chunkSectionIndex, int bits) { -+ this.bits[chunkSectionIndex] = bits; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public Palette getPalette(int chunkSectionIndex) { -+ return (Palette) palettes[chunkSectionIndex]; -+ } -+ -+ public void setPalette(int chunkSectionIndex, Palette palette) { -+ palettes[chunkSectionIndex] = palette; -+ } -+ -+ public int getIndex(int chunkSectionIndex) { -+ return indexes[chunkSectionIndex]; -+ } -+ -+ public void setIndex(int chunkSectionIndex, int index) { -+ indexes[chunkSectionIndex] = index; -+ } -+ -+ @SuppressWarnings("unchecked") -+ public T[] getPresetValues(int chunkSectionIndex) { -+ return (T[]) presetValues[chunkSectionIndex]; -+ } -+ -+ public void setPresetValues(int chunkSectionIndex, T[] presetValues) { -+ this.presetValues[chunkSectionIndex] = presetValues; -+ } -+ -+ public boolean isWritten(int chunkSectionIndex) { -+ return bits[chunkSectionIndex] != 0; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..80a2dfb266ae1221680a7b24fee2f7e2a8330b7d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketInfoAntiXray.java -@@ -0,0 +1,29 @@ -+package com.destroystokyo.paper.antixray; -+ -+import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.LevelChunk; -+ -+public final class ChunkPacketInfoAntiXray extends ChunkPacketInfo implements Runnable { -+ -+ private final ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray; -+ private LevelChunk[] nearbyChunks; -+ -+ public ChunkPacketInfoAntiXray(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk, ChunkPacketBlockControllerAntiXray chunkPacketBlockControllerAntiXray) { -+ super(chunkPacket, chunk); -+ this.chunkPacketBlockControllerAntiXray = chunkPacketBlockControllerAntiXray; -+ } -+ -+ public LevelChunk[] getNearbyChunks() { -+ return nearbyChunks; -+ } -+ -+ public void setNearbyChunks(LevelChunk... nearbyChunks) { -+ this.nearbyChunks = nearbyChunks; -+ } -+ -+ @Override -+ public void run() { -+ chunkPacketBlockControllerAntiXray.obfuscate(this); -+ } -+} -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -index 568ac283baf40e2a89f5b002ffd899eba8008ef2..2b35059cfe7a27238e0a74df058733897a26ac1c 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -@@ -33,7 +33,10 @@ public class ClientboundLevelChunkPacketData { - } - // Paper end - -- public ClientboundLevelChunkPacketData(LevelChunk chunk) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public ClientboundLevelChunkPacketData(LevelChunk chunk) { this(chunk, null); } -+ public ClientboundLevelChunkPacketData(LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { -+ // Paper end - this.heightmaps = new CompoundTag(); - - for(Map.Entry entry : chunk.getHeightmaps()) { -@@ -43,7 +46,14 @@ public class ClientboundLevelChunkPacketData { - } - - this.buffer = new byte[calculateChunkSize(chunk)]; -- extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk); -+ -+ // Paper start - Anti-Xray - Add chunk packet info -+ if (chunkPacketInfo != null) { -+ chunkPacketInfo.setBuffer(this.buffer); -+ } -+ -+ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk, chunkPacketInfo); -+ // Paper end - this.blockEntitiesData = Lists.newArrayList(); - int totalTileEntities = 0; // Paper - -@@ -103,9 +113,12 @@ public class ClientboundLevelChunkPacketData { - return byteBuf; - } - -- public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk) { ClientboundLevelChunkPacketData.extractChunkData(buf, chunk, null); } -+ public static void extractChunkData(FriendlyByteBuf buf, LevelChunk chunk, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { - for(LevelChunkSection levelChunkSection : chunk.getSections()) { -- levelChunkSection.write(buf); -+ levelChunkSection.write(buf, chunkPacketInfo); -+ // Paper end - } - - } -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -index 7825d6f0fdcfda6212cff8033ec55fb7db236154..000853110c7a89f2d0403a7a2737025a5ac28240 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -@@ -13,13 +13,30 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; -+ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo); -+ // Paper end - this.lightData = new ClientboundLightUpdatePacketData(chunkPos, lightProvider, skyBits, blockBits, nonEdge); -+ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks - } - - public ClientboundLevelChunkWithLightPacket(FriendlyByteBuf buf) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index d60173b03baee4a66da1109795bf6a19737b8bd0..55a21a4024debc328d4829477d80c2998e291c22 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1104,7 +1104,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - completablefuture1.thenAcceptAsync((either) -> { - either.ifLeft((chunk) -> { - this.tickingGenerated.getAndIncrement(); -- MutableObject mutableobject = new MutableObject(); -+ MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass - - this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { - this.playerLoadedChunk(entityplayer, mutableobject, chunk); -@@ -1283,7 +1283,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - while (objectiterator.hasNext()) { - ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); - ChunkPos chunkcoordintpair = playerchunk.getPos(); -- MutableObject mutableobject = new MutableObject(); -+ MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass - - this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { - SectionPos sectionposition = entityplayer.getLastSectionPos(); -@@ -1297,7 +1297,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - } - -- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { -+ protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass - if (player.level == this.level) { - if (newWithinViewDistance && !oldWithinViewDistance) { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); -@@ -1834,12 +1834,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - } - -- private void playerLoadedChunk(ServerPlayer player, MutableObject cachedDataPacket, LevelChunk chunk) { -- if (cachedDataPacket.getValue() == null) { -- cachedDataPacket.setValue(new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true)); -+ // Paper start - Anti-Xray - Bypass -+ private void playerLoadedChunk(ServerPlayer player, MutableObject> cachedDataPackets, LevelChunk chunk) { -+ if (cachedDataPackets.getValue() == null) { -+ cachedDataPackets.setValue(new java.util.HashMap<>()); - } - -- player.trackChunk(chunk.getPos(), (Packet) cachedDataPacket.getValue()); -+ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); -+ player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> { -+ return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s); -+ })); -+ // Paper end - DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); - List list = Lists.newArrayList(); - List list1 = Lists.newArrayList(); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 21fdca3b4bdc49d32deaf3f11d5fecc1ed0d4626..5622917a2884e87d43abfaf58e722957931b3178 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -405,7 +405,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error - // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error -- super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig))); // Paper -+ super(iworlddataserver, resourcekey, worlddimension.typeHolder(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig)), executor); // Paper - Async-Anti-Xray - Pass executor - this.pvpMode = minecraftserver.isPvpAllowed(); - this.convertable = convertable_conversionsession; - this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 3fadf6b46cc722ad81cf810c0761cf717e9f9b78..af00442931f9f6cf878bd61137c2f29fc7c8d0b1 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -49,7 +49,7 @@ import org.bukkit.event.player.PlayerInteractEvent; - public class ServerPlayerGameMode { - - private static final Logger LOGGER = LogUtils.getLogger(); -- protected ServerLevel level; -+ public ServerLevel level; // Paper - Anti-Xray - protected -> public - protected final ServerPlayer player; - private GameType gameModeForPlayer; - @Nullable -@@ -318,6 +318,8 @@ public class ServerPlayerGameMode { - } - - } -+ -+ this.level.chunkPacketBlockController.onPlayerLeftClickBlock(this, pos, action, direction, worldHeight, sequence); // Paper - Anti-Xray - } - - public void destroyAndAck(BlockPos pos, int sequence, String reason) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 4c1d34bd274d8e2a4003a286536652367da9488a..8e5e773fffcb17d20328903d1b1fc9d9e0aefa3e 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -173,6 +173,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - -+ public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray - public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; -@@ -191,7 +192,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - public abstract ResourceKey getTypeKey(); - -- protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper -+ protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - this.generator = gen; -@@ -275,6 +276,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.keepSpawnInMemory = this.paperConfig().spawn.keepSpawnLoaded; // Paper - this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); -+ this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray - } - - // Paper start -@@ -455,6 +457,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // CraftBukkit end - - BlockState iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag -+ this.chunkPacketBlockController.onBlockChange(this, pos, state, iblockdata1, flags, maxUpdateDepth); // Paper - Anti-Xray - - if (iblockdata1 == null) { - // CraftBukkit start - remove blockstate if failed (or the same) -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index 6cec5cda20531aadf8e2148908a70f8b573d7d82..dc164608bfb2fb18a1adf83fa10bac4028dcac0a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -99,17 +99,19 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - } - } - -- ChunkAccess.replaceMissingSections(heightLimitView, biome, this.sections); -+ ChunkAccess.replaceMissingSections(heightLimitView, biome, this.sections, pos); // Paper - Anti-Xray - Add parameters - // CraftBukkit start - this.biomeRegistry = biome; - } - public final Registry biomeRegistry; - // CraftBukkit end - -- private static void replaceMissingSections(LevelHeightAccessor world, Registry biome, LevelChunkSection[] sectionArray) { -+ // Paper start - Anti-Xray - Add parameters -+ private static void replaceMissingSections(LevelHeightAccessor world, Registry biome, LevelChunkSection[] sectionArray, ChunkPos pos) { - for (int i = 0; i < sectionArray.length; ++i) { - if (sectionArray[i] == null) { -- sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome); -+ sectionArray[i] = new LevelChunkSection(world.getSectionYFromSectionIndex(i), biome, pos, world instanceof net.minecraft.world.level.Level ? (net.minecraft.world.level.Level) world : null); -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index e63086aba2512051fe1321f6e7e72b40276f5dde..004c8abeafa53cdc3efa0f45742132e3ad492d70 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -93,7 +93,7 @@ public class LevelChunk extends ChunkAccess { - } - - public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { -- super(pos, upgradeData, world, world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); -+ super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry - this.tickersInLevel = Maps.newHashMap(); - this.clientLightReady = false; - this.level = (ServerLevel) world; // CraftBukkit - type -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index ae37e97e52557b48f129cc02eeea395378a48444..785fbcf9bafcdec1c5be213de3d8512690023415 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -36,10 +36,13 @@ public class LevelChunkSection { - this.recalcBlockCounts(); - } - -- public LevelChunkSection(int chunkPos, Registry biomeRegistry) { -+ // Paper start - Anti-Xray - Add parameters -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public LevelChunkSection(int chunkPos, Registry biomeRegistry) { this(chunkPos, biomeRegistry, null, null); } -+ public LevelChunkSection(int chunkPos, Registry biomeRegistry, net.minecraft.world.level.ChunkPos pos, net.minecraft.world.level.Level level) { -+ // Paper end - this.bottomBlockY = LevelChunkSection.getBottomBlockY(chunkPos); -- this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -- this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); -+ this.states = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, level == null || level.chunkPacketBlockController == null ? null : level.chunkPacketBlockController.getPresetBlockStates(level, pos, this.bottomBlockY())); // Paper - Anti-Xray - Add preset block states -+ this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes - } - - public static int getBottomBlockY(int chunkPos) { -@@ -177,10 +180,13 @@ public class LevelChunkSection { - this.biomes = datapaletteblock; - } - -- public void write(FriendlyByteBuf buf) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null); } -+ public void write(FriendlyByteBuf buf, com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo) { - buf.writeShort(this.nonEmptyBlockCount); -- this.states.write(buf); -- this.biomes.write(buf); -+ this.states.write(buf, chunkPacketInfo, this.bottomBlockY()); -+ this.biomes.write(buf, null, this.bottomBlockY()); -+ // Paper end - } - - public int getSerializedSize() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 78e20871e4bd8d92c4475f797a55733c68f6aeb4..b688d239ff11b315f60cd980d8f6780b982a865b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -29,6 +29,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return 0; - }; - public final IdMap registry; -+ private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values - private volatile PalettedContainer.Data data; - private final PalettedContainer.Strategy strategy; - private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); -@@ -41,14 +42,19 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - this.threadingDetector.checkAndUnlock(); - } - -- public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { -- PalettedContainerRO.Unpacker> unpacker = PalettedContainer::unpack; -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { return PalettedContainer.codecRW(idList, entryCodec, paletteProvider, defaultValue, null); } -+ public static Codec> codecRW(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { -+ return unpack(idListx, paletteProviderx, serialized, defaultValue, presetValues); -+ }; -+ // Paper end - return codec(idList, entryCodec, paletteProvider, defaultValue, unpacker); - } - - public static Codec> codecRO(IdMap idList, Codec entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) { - PalettedContainerRO.Unpacker> unpacker = (idListx, paletteProviderx, serialized) -> { -- return unpack(idListx, paletteProviderx, serialized).map((result) -> { -+ return unpack(idListx, paletteProviderx, serialized, defaultValue, null).map((result) -> { // Paper - Anti-Xray - Add preset values - return result; - }); - }; -@@ -65,19 +71,52 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - }); - } - -- public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries) { this(idList, paletteProvider, dataProvider, storage, paletteEntries, null, null); } -+ public PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Configuration dataProvider, BitStorage storage, List paletteEntries, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; - this.registry = idList; - this.strategy = paletteProvider; - this.data = new PalettedContainer.Data<>(dataProvider, storage, dataProvider.factory().create(dataProvider.bits(), idList, this, paletteEntries)); -+ -+ if (presetValues != null && (dataProvider.factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY ? this.data.palette.valueFor(0) != defaultValue : dataProvider.factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY)) { -+ // In 1.18 Mojang unfortunately removed code that already handled possible resize operations on read from disk for us -+ // We readd this here but in a smarter way than it was before -+ int maxSize = 1 << dataProvider.bits(); -+ -+ for (T presetValue : presetValues) { -+ if (this.data.palette.getSize() >= maxSize) { -+ java.util.Set allValues = new java.util.HashSet<>(paletteEntries); -+ allValues.addAll(Arrays.asList(presetValues)); -+ int newBits = Mth.ceillog2(allValues.size()); -+ -+ if (newBits > dataProvider.bits()) { -+ this.onResize(newBits, null); -+ } -+ -+ break; -+ } -+ -+ this.data.palette.idFor(presetValue); -+ } -+ } -+ // Paper end - } - -- private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data) { -+ // Paper start - Anti-Xray - Add preset values -+ private PalettedContainer(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainer.Data data, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; -+ // Paper end - this.registry = idList; - this.strategy = paletteProvider; - this.data = data; - } - -- public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { -+ // Paper start - Anti-Xray - Add preset values -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider) { this(idList, object, paletteProvider, null); } -+ public PalettedContainer(IdMap idList, T object, PalettedContainer.Strategy paletteProvider, T @org.jetbrains.annotations.Nullable [] presetValues) { -+ this.presetValues = presetValues; -+ // Paper end - this.strategy = paletteProvider; - this.registry = idList; - this.data = this.createOrReuseData((PalettedContainer.Data)null, 0); -@@ -92,11 +131,33 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - @Override - public int onResize(int newBits, T object) { - PalettedContainer.Data data = this.data; -+ -+ // Paper start - Anti-Xray - Add preset values -+ if (this.presetValues != null && object != null && data.configuration().factory() == PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY) { -+ int duplicates = 0; -+ List presetValues = Arrays.asList(this.presetValues); -+ duplicates += presetValues.contains(object) ? 1 : 0; -+ duplicates += presetValues.contains(data.palette.valueFor(0)) ? 1 : 0; -+ newBits = Mth.ceillog2((1 << this.strategy.calculateBitsForSerialization(this.registry, 1 << newBits)) + presetValues.size() - duplicates); -+ } -+ - PalettedContainer.Data data2 = this.createOrReuseData(data, newBits); - data2.copyFrom(data.palette, data.storage); - this.data = data2; -- return data2.palette.idFor(object); -+ this.addPresetValues(); -+ return object == null ? -1 : data2.palette.idFor(object); -+ // Paper end -+ } -+ -+ // Paper start - Anti-Xray - Add preset values -+ private void addPresetValues() { -+ if (this.presetValues != null && this.data.configuration().factory() != PalettedContainer.Strategy.GLOBAL_PALETTE_FACTORY) { -+ for (T presetValue : this.presetValues) { -+ this.data.palette.idFor(presetValue); -+ } -+ } - } -+ // Paper end - - public T getAndSet(int x, int y, int z, T value) { - this.acquire(); -@@ -166,25 +227,36 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - data.palette.read(buf); - buf.readLongArray(data.storage.getRaw()); - this.data = data; -+ this.addPresetValues(); // Paper - Anti-Xray - Add preset values (inefficient, but this isn't used by the server) - } finally { - this.release(); - } - - } - -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Override -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } - @Override -- public void write(FriendlyByteBuf buf) { -+ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { - this.acquire(); - - try { -- this.data.write(buf); -+ this.data.write(buf, chunkPacketInfo, bottomBlockY); -+ -+ if (chunkPacketInfo != null) { -+ // Bottom block to 0 based chunk section index -+ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); -+ chunkPacketInfo.setPresetValues(chunkSectionIndex, this.presetValues); -+ } -+ // Paper end - } finally { - this.release(); - } - - } - -- private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized) { -+ private static DataResult> unpack(IdMap idList, PalettedContainer.Strategy paletteProvider, PalettedContainerRO.PackedData serialized, T defaultValue, T @org.jetbrains.annotations.Nullable [] presetValues) { // Paper - Anti-Xray - Add preset values - List list = serialized.paletteEntries(); - int i = paletteProvider.size(); - int j = paletteProvider.calculateBitsForSerialization(idList, list.size()); -@@ -220,7 +292,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -- return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list)); -+ return DataResult.success(new PalettedContainer<>(idList, paletteProvider, configuration, bitStorage, list, defaultValue, presetValues)); // Paper - Anti-Xray - Add preset values - } - - @Override -@@ -280,12 +352,12 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - public PalettedContainer copy() { -- return new PalettedContainer<>(this.registry, this.strategy, this.data.copy()); -+ return new PalettedContainer<>(this.registry, this.strategy, this.data.copy(), this.presetValues); // Paper - Anti-Xray - Add preset values - } - - @Override - public PalettedContainer recreate() { -- return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy); -+ return new PalettedContainer<>(this.registry, this.data.palette.valueFor(0), this.strategy, this.presetValues); // Paper - Anti-Xray - Add preset values - } - - @Override -@@ -329,9 +401,20 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return 1 + this.palette.getSerializedSize() + FriendlyByteBuf.getVarIntSize(this.storage.getSize()) + this.storage.getRaw().length * 8; - } - -- public void write(FriendlyByteBuf buf) { -+ // Paper start - Anti-Xray - Add chunk packet info -+ public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { - buf.writeByte(this.storage.getBits()); - this.palette.write(buf); -+ -+ if (chunkPacketInfo != null) { -+ // Bottom block to 0 based chunk section index -+ int chunkSectionIndex = (bottomBlockY >> 4) - chunkPacketInfo.getChunk().getMinSection(); -+ chunkPacketInfo.setBits(chunkSectionIndex, this.configuration.bits()); -+ chunkPacketInfo.setPalette(chunkSectionIndex, this.palette); -+ chunkPacketInfo.setIndex(chunkSectionIndex, buf.writerIndex() + FriendlyByteBuf.getVarIntSize(this.storage.getRaw().length)); -+ } -+ // Paper end -+ - buf.writeLongArray(this.storage.getRaw()); - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -index 9a2bf744abd8916d492e901be889223591bac3fd..a27fce0f1af9776a713bf1b5277869ed5d3e0c8e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainerRO.java -@@ -14,7 +14,10 @@ public interface PalettedContainerRO { - - void getAll(Consumer action); - -- void write(FriendlyByteBuf buf); -+ // Paper start - Anti-Xray - Add chunk packet info -+ @Deprecated @io.papermc.paper.annotation.DoNotUse void write(FriendlyByteBuf buf); -+ void write(FriendlyByteBuf buf, @javax.annotation.Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY); -+ // Paper end - - int getSerializedSize(); - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 864e591b10360b0f12fe5c5a650da372555ebd10..f26a08f81495dde6205b34254d159b042e5a6ea9 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -69,7 +69,7 @@ import org.slf4j.Logger; - - public class ChunkSerializer { - -- public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); -+ public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states - private static final Logger LOGGER = LogUtils.getLogger(); - private static final String TAG_UPGRADE_DATA = "UpgradeData"; - private static final String BLOCK_TICKS_TAG = "block_ticks"; -@@ -149,16 +149,20 @@ public class ChunkSerializer { - if (k >= 0 && k < achunksection.length) { - Logger logger; - PalettedContainer datapaletteblock; -+ // Paper start - Anti-Xray - Add preset block states -+ BlockState[] presetBlockStates = world.chunkPacketBlockController.getPresetBlockStates(world, chunkPos, b0 << 4); - - if (nbttagcompound1.contains("block_states", 10)) { -- dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { -+ Codec> blockStateCodec = presetBlockStates == null ? ChunkSerializer.BLOCK_STATE_CODEC : PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), presetBlockStates); -+ dataresult = blockStateCodec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("block_states")).promotePartial((s) -> { - ChunkSerializer.logErrors(chunkPos, b0, s); - }); - logger = ChunkSerializer.LOGGER; - Objects.requireNonNull(logger); - datapaletteblock = (PalettedContainer) ((DataResult>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error - } else { -- datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -+ datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, presetBlockStates); -+ // Paper end - } - - PalettedContainer object; // CraftBukkit - read/write -@@ -171,7 +175,7 @@ public class ChunkSerializer { - Objects.requireNonNull(logger); - object = ((DataResult>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error - } else { -- object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); -+ object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes - } - - LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write -@@ -439,7 +443,7 @@ public class ChunkSerializer { - - // CraftBukkit start - read/write - private static Codec>> makeBiomeCodecRW(Registry iregistry) { -- return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); -+ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS), null); // Paper - Anti-Xray - Add preset biomes - } - // CraftBukkit end - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index dfdcc01c7cb2864d9b5f572c0cafedf16063edd8..d9c2e7e18e1ede37d92cecb8ddb32dae1472bd1c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -54,7 +54,7 @@ public class CraftChunk implements Chunk { - private final ServerLevel worldServer; - private final int x; - private final int z; -- private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); -+ private static final PalettedContainer emptyBlockIDs = new PalettedContainer<>(net.minecraft.world.level.block.Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null); // Paper - Anti-Xray - Add preset block states - private static final byte[] emptyLight = new byte[2048]; - - public CraftChunk(net.minecraft.world.level.chunk.LevelChunk chunk) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e8c11b59dc50fd9c5bbf073b66d0cd9c504d7c25..8cc2a35486e8c6433e722ddc5e776c3332e7c7fe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2235,7 +2235,7 @@ public final class CraftServer implements Server { - public ChunkGenerator.ChunkData createChunkData(World world) { - Validate.notNull(world, "World cannot be null"); - ServerLevel handle = ((CraftWorld) world).getHandle(); -- return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY)); -+ return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY), world); // Paper - Anti-Xray - Add parameters - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 868d9d13b03751b24c4b6695f97ad343ff8d0b8e..a8ab324bfbaaf946af5998402588244465dd7286 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -406,11 +406,16 @@ public class CraftWorld extends CraftRegionAccessor implements World { - List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); - if (playersInRange.isEmpty()) return; - -- ClientboundLevelChunkWithLightPacket refreshPacket = new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, true); -+ // Paper start - Anti-Xray - Bypass -+ Map refreshPackets = new HashMap<>(); - for (ServerPlayer player : playersInRange) { - if (player.connection == null) continue; - -- player.connection.send(refreshPacket); -+ Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); -+ player.connection.send(refreshPackets.computeIfAbsent(shouldModify, s -> { // Use connection to prevent creating firing event -+ return new ClientboundLevelChunkWithLightPacket(chunk, this.world.getLightEngine(), null, null, true, (Boolean) s); -+ })); -+ // Paper end - } - }); - }); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -index 960405935e395a31c0300773c41413801cf0d290..4a23d03757e1735b9ebb8c003adcc0374a7d672d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -@@ -27,8 +27,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - private final Registry biomes; - private Set tiles; - private final Set lights = new HashSet<>(); -+ // Paper start - Anti-Xray - Add parameters -+ private final World world; - -- public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { -+ @Deprecated @io.papermc.paper.annotation.DoNotUse public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes) { this(minHeight, maxHeight, biomes, null); } -+ public OldCraftChunkData(int minHeight, int maxHeight, Registry biomes, World world) { -+ this.world = world; -+ // Paper end - this.minHeight = minHeight; - this.maxHeight = maxHeight; - this.biomes = biomes; -@@ -176,7 +181,7 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - int offset = (y - this.minHeight) >> 4; - LevelChunkSection section = this.sections[offset]; - if (create && section == null) { -- this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes); -+ this.sections[offset] = section = new LevelChunkSection(offset + (this.minHeight >> 4), this.biomes, null, this.world instanceof org.bukkit.craftbukkit.CraftWorld ? ((org.bukkit.craftbukkit.CraftWorld) this.world).getHandle() : null); // Paper - Anti-Xray - Add parameters - } - return section; - } diff --git a/patches/server/0357-Implement-alternative-item-despawn-rate.patch b/patches/server/0357-Implement-alternative-item-despawn-rate.patch new file mode 100644 index 0000000000..db8d53f699 --- /dev/null +++ b/patches/server/0357-Implement-alternative-item-despawn-rate.patch @@ -0,0 +1,63 @@ +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 + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index ebcf58fe51d1fd0cb8a0f5a84cdd349f29c9442e..7e293167e73238f42fc213ee29d89aa775cf9e60 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -54,6 +54,7 @@ public class ItemEntity extends Entity { + public final float bobOffs; + private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit + public boolean canMobPickup = true; // Paper ++ private int despawnRate = -1; // Paper + + public ItemEntity(EntityType type, Level world) { + super(type, world); +@@ -182,7 +183,7 @@ public class ItemEntity extends Entity { + } + } + +- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level.isClientSide && this.age >= this.despawnRate) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -206,7 +207,7 @@ public class ItemEntity extends Entity { + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end + +- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot ++ if (!this.level.isClientSide && this.age >= this.despawnRate) { // Spigot // Paper + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; +@@ -257,7 +258,7 @@ public class ItemEntity extends Entity { + private boolean isMergable() { + ItemStack itemstack = this.getItem(); + +- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize(); ++ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - respect despawn rate in pickup check. + } + + private void tryToMerge(ItemEntity other) { +@@ -501,6 +502,7 @@ public class ItemEntity extends Entity { + com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit + this.getEntityData().set(ItemEntity.DATA_ITEM, stack); + this.getEntityData().markDirty(ItemEntity.DATA_ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty ++ this.despawnRate = level.paperConfig().entities.spawning.altItemDespawnRate.enabled ? level.paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), level.spigotConfig.itemDespawnRate) : level.spigotConfig.itemDespawnRate; // Paper + } + + @Override +@@ -564,7 +566,7 @@ public class ItemEntity extends Entity { + + public void makeFakeItem() { + this.setNeverPickUp(); +- this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot ++ this.age = this.despawnRate - 1; // Spigot + } + + public float getSpin(float tickDelta) { diff --git a/patches/server/0358-Implement-alternative-item-despawn-rate.patch b/patches/server/0358-Implement-alternative-item-despawn-rate.patch deleted file mode 100644 index db8d53f699..0000000000 --- a/patches/server/0358-Implement-alternative-item-despawn-rate.patch +++ /dev/null @@ -1,63 +0,0 @@ -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 - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index ebcf58fe51d1fd0cb8a0f5a84cdd349f29c9442e..7e293167e73238f42fc213ee29d89aa775cf9e60 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -54,6 +54,7 @@ public class ItemEntity extends Entity { - public final float bobOffs; - private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit - public boolean canMobPickup = true; // Paper -+ private int despawnRate = -1; // Paper - - public ItemEntity(EntityType type, Level world) { - super(type, world); -@@ -182,7 +183,7 @@ public class ItemEntity extends Entity { - } - } - -- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot -+ if (!this.level.isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - // CraftBukkit start - fire ItemDespawnEvent - if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { - this.age = 0; -@@ -206,7 +207,7 @@ public class ItemEntity extends Entity { - this.lastTick = MinecraftServer.currentTick; - // CraftBukkit end - -- if (!this.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot -+ if (!this.level.isClientSide && this.age >= this.despawnRate) { // Spigot // Paper - // CraftBukkit start - fire ItemDespawnEvent - if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { - this.age = 0; -@@ -257,7 +258,7 @@ public class ItemEntity extends Entity { - private boolean isMergable() { - ItemStack itemstack = this.getItem(); - -- return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < 6000 && itemstack.getCount() < itemstack.getMaxStackSize(); -+ return this.isAlive() && this.pickupDelay != 32767 && this.age != -32768 && this.age < this.despawnRate && itemstack.getCount() < itemstack.getMaxStackSize(); // Paper - respect despawn rate in pickup check. - } - - private void tryToMerge(ItemEntity other) { -@@ -501,6 +502,7 @@ public class ItemEntity extends Entity { - com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit - this.getEntityData().set(ItemEntity.DATA_ITEM, stack); - this.getEntityData().markDirty(ItemEntity.DATA_ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty -+ this.despawnRate = level.paperConfig().entities.spawning.altItemDespawnRate.enabled ? level.paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), level.spigotConfig.itemDespawnRate) : level.spigotConfig.itemDespawnRate; // Paper - } - - @Override -@@ -564,7 +566,7 @@ public class ItemEntity extends Entity { - - public void makeFakeItem() { - this.setNeverPickUp(); -- this.age = level.spigotConfig.itemDespawnRate - 1; // Spigot -+ this.age = this.despawnRate - 1; // Spigot - } - - public float getSpin(float tickDelta) { diff --git a/patches/server/0358-Tracking-Range-Improvements.patch b/patches/server/0358-Tracking-Range-Improvements.patch new file mode 100644 index 0000000000..53db6644cf --- /dev/null +++ b/patches/server/0358-Tracking-Range-Improvements.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: kickash32 +Date: Sat, 21 Dec 2019 15:22:09 -0500 +Subject: [PATCH] Tracking Range Improvements + +Sets tracking range of watermobs to animals instead of misc and simplifies code + +Also ignores Enderdragon, defaulting it to Mojang's setting + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 55a21a4024debc328d4829477d80c2998e291c22..0b68e6c7ef63460d596050ed55039a5ba3cefb24 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -2022,6 +2022,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + int j = entity.getType().clientTrackingRange() * 16; ++ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper + + if (j > i) { + i = j; +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 2d5cb8991e368372f1ab227735aac0c060deb199..55ce69b5fe097841d00ef5c241459dce9bb0d4db 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -6,7 +6,6 @@ import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.item.ItemEntity; +-import net.minecraft.world.entity.monster.Ghast; + + public class TrackingRange + { +@@ -29,26 +28,26 @@ public class TrackingRange + if ( entity instanceof ServerPlayer ) + { + return config.playerTrackingRange; +- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) +- { +- return config.monsterTrackingRange; +- } else if ( entity instanceof Ghast ) +- { +- if ( config.monsterTrackingRange > config.monsterActivationRange ) +- { ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: + return config.monsterTrackingRange; +- } else +- { +- return config.monsterActivationRange; +- } +- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) +- { +- return config.animalTrackingRange; +- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return config.animalTrackingRange; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end + { + return config.miscTrackingRange; + } else + { ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - enderdragon is exempt + return config.otherTrackingRange; + } + } diff --git a/patches/server/0359-Fix-items-vanishing-through-end-portal.patch b/patches/server/0359-Fix-items-vanishing-through-end-portal.patch new file mode 100644 index 0000000000..32e4e717c9 --- /dev/null +++ b/patches/server/0359-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 51fda8b02c6259540db220a55dd8ee1aa651a50c..f94462c3aeec3a71b30de1834fb72934e77c35ac 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3123,6 +3123,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (flag1) { + blockposition1 = ServerLevel.END_SPAWN_POINT; + } else { ++ // Paper start - Ensure spawn chunk is always loaded before calculating Y coordinate ++ destination.getChunkAt(destination.getSharedSpawnPos()); ++ // Paper end + blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos()); + } + // CraftBukkit start diff --git a/patches/server/0359-Tracking-Range-Improvements.patch b/patches/server/0359-Tracking-Range-Improvements.patch deleted file mode 100644 index 53db6644cf..0000000000 --- a/patches/server/0359-Tracking-Range-Improvements.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Sat, 21 Dec 2019 15:22:09 -0500 -Subject: [PATCH] Tracking Range Improvements - -Sets tracking range of watermobs to animals instead of misc and simplifies code - -Also ignores Enderdragon, defaulting it to Mojang's setting - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 55a21a4024debc328d4829477d80c2998e291c22..0b68e6c7ef63460d596050ed55039a5ba3cefb24 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -2022,6 +2022,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - int j = entity.getType().clientTrackingRange() * 16; -+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - - if (j > i) { - i = j; -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index 2d5cb8991e368372f1ab227735aac0c060deb199..55ce69b5fe097841d00ef5c241459dce9bb0d4db 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -6,7 +6,6 @@ import net.minecraft.world.entity.ExperienceOrb; - import net.minecraft.world.entity.decoration.ItemFrame; - import net.minecraft.world.entity.decoration.Painting; - import net.minecraft.world.entity.item.ItemEntity; --import net.minecraft.world.entity.monster.Ghast; - - public class TrackingRange - { -@@ -29,26 +28,26 @@ public class TrackingRange - if ( entity instanceof ServerPlayer ) - { - return config.playerTrackingRange; -- } else if ( entity.activationType == ActivationRange.ActivationType.MONSTER || entity.activationType == ActivationRange.ActivationType.RAIDER ) -- { -- return config.monsterTrackingRange; -- } else if ( entity instanceof Ghast ) -- { -- if ( config.monsterTrackingRange > config.monsterActivationRange ) -- { -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: - return config.monsterTrackingRange; -- } else -- { -- return config.monsterActivationRange; -- } -- } else if ( entity.activationType == ActivationRange.ActivationType.ANIMAL ) -- { -- return config.animalTrackingRange; -- } else if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return config.animalTrackingRange; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end - { - return config.miscTrackingRange; - } else - { -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - enderdragon is exempt - return config.otherTrackingRange; - } - } diff --git a/patches/server/0360-Fix-items-vanishing-through-end-portal.patch b/patches/server/0360-Fix-items-vanishing-through-end-portal.patch deleted file mode 100644 index 0ed6a4eaae..0000000000 --- a/patches/server/0360-Fix-items-vanishing-through-end-portal.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AJMFactsheets -Date: Wed, 22 Jan 2020 19:52:28 -0600 -Subject: [PATCH] Fix items vanishing through end portal - -If the Paper configuration option "keep-spawn-loaded" is set to false, -items entering the overworld from the end will spawn at Y = 0. - -This is due to logic in the getHighestBlockYAt method in World.java -only searching the heightmap if the chunk is loaded. - -Quickly loading the exact world spawn chunk before searching the -heightmap resolves the issue without having to load all spawn chunks. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 022682dc729a2e595df09befc18d7d73143b7e74..240650cee26fc907f632e0c8ef3559a36460a3ba 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3109,6 +3109,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (flag1) { - blockposition1 = ServerLevel.END_SPAWN_POINT; - } else { -+ // Paper start - Ensure spawn chunk is always loaded before calculating Y coordinate -+ destination.getChunkAt(destination.getSharedSpawnPos()); -+ // Paper end - blockposition1 = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.getSharedSpawnPos()); - } - // CraftBukkit start diff --git a/patches/server/0360-implement-optional-per-player-mob-spawns.patch b/patches/server/0360-implement-optional-per-player-mob-spawns.patch new file mode 100644 index 0000000000..7aa2416ddc --- /dev/null +++ b/patches/server/0360-implement-optional-per-player-mob-spawns.patch @@ -0,0 +1,578 @@ +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/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java +new file mode 100644 +index 0000000000000000000000000000000000000000..11de56afaf059b00fa5bec293516bcdce7c4b2b9 +--- /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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0b68e6c7ef63460d596050ed55039a5ba3cefb24..9e940920f6ff6a43d7162d965936c171c55ed570 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -159,6 +159,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final Long2LongMap chunkSaveCooldowns; + private final Queue unloadQueue; + int viewDistance; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); +@@ -199,16 +200,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // Paper start - per player mob spawning ++ if (this.playerMobDistanceMap != null) { ++ this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); ++ } ++ // Paper end - per player mob spawning + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { + ++ // Paper start - per player mob spawning ++ if (this.playerMobDistanceMap != null) { ++ this.playerMobDistanceMap.remove(player); ++ } ++ // Paper end - per player mob spawning + } + + void updateMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // Paper start - per player mob spawning ++ if (this.playerMobDistanceMap != null) { ++ this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); ++ } ++ // Paper end - per player mob spawning + } + // Paper end + // Paper start +@@ -305,6 +321,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); + this.regionManagers.add(this.dataRegionManager); + // Paper end ++ this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper + } + + protected ChunkGenerator generator() { +@@ -356,6 +373,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + // Paper end ++ // Paper start ++ public void updatePlayerMobTypeMap(Entity entity) { ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ return; ++ } ++ int index = entity.getType().getCategory().ordinal(); ++ ++ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerMobDistanceMap.getObjectsInRange(entity.chunkPosition()); ++ if (inRange == null) { ++ return; ++ } ++ final Object[] backingSet = inRange.getBackingSet(); ++ for (int i = 0; i < backingSet.length; i++) { ++ if (!(backingSet[i] instanceof final ServerPlayer player)) { ++ continue; ++ } ++ ++player.mobCounts[index]; ++ } ++ } ++ ++ public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory mobCategory) { ++ return entityPlayer.mobCounts[mobCategory.ordinal()]; ++ } ++ // Paper end + + private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { + double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index b9b56068cdacd984f873cfb2a06a312e9912893d..9309ea89a440606be3e56ef634f5048a72b0009e 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -466,6 +466,12 @@ public abstract class DistanceManager { + + } + ++ // Paper start ++ public int getSimulationDistance() { ++ return this.simulationDistance; ++ } ++ // Paper end ++ + public int getNaturalSpawnChunkCount() { + this.naturalSpawnChunkCounter.runAllUpdates(); + return this.naturalSpawnChunkCounter.chunks.size(); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index d1fa959b6e872565b689ee6b4f1bed9ad9e4077e..f982241898c4aeaf50854e67fe188478f2f2b186 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -764,7 +764,18 @@ public class ServerChunkCache extends ChunkSource { + gameprofilerfiller.push("naturalSpawnCount"); + this.level.timings.countNaturalMobs.startTiming(); // Paper - timings + int l = this.distanceManager.getNaturalSpawnChunkCount(); +- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)); ++ // Paper start - per player mob spawning ++ NaturalSpawner.SpawnState spawnercreature_d; // moved down ++ if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled ++ // re-set mob counts ++ for (ServerPlayer player : this.level.players) { ++ Arrays.fill(player.mobCounts, 0); ++ } ++ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true); ++ } else { ++ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false); ++ } ++ // Paper end + this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings + + this.lastSpawnState = spawnercreature_d; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index b36effc88b516958a7c0a46a7eb3a77a98ad3a20..582e08eaeee8fb6c777ad451ccc0436b02cac000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -236,6 +236,11 @@ public class ServerPlayer extends Player { + public boolean queueHealthUpdatePacket = false; + public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; + // Paper end ++ // Paper start - mob spawning rework ++ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; ++ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper ++ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; ++ // Paper end + + // CraftBukkit start + public String displayName; +@@ -326,6 +331,7 @@ public class ServerPlayer extends Player { + this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper + this.bukkitPickUpLoot = true; + this.maxHealthCache = this.getMaxHealth(); ++ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + } + // Paper start - Chunk priority + public BlockPos getPointInFront(double inFront) { +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index b4098068e674b639e82c07e5d60e4e2120b4305b..fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -69,6 +69,12 @@ public final class NaturalSpawner { + private NaturalSpawner() {} + + public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator) { ++ // Paper start - add countMobs parameter ++ return createState(spawningChunkCount, entities, chunkSource, localmobcapcalculator, false); ++ } ++ ++ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator, boolean countMobs) { ++ // Paper end + PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator(); + Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); + Iterator iterator = entities.iterator(); +@@ -103,11 +109,16 @@ public final class NaturalSpawner { + spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.getCharge()); + } + +- if (entity instanceof Mob) { ++ if (localmobcapcalculator != null && entity instanceof Mob) { // Paper + localmobcapcalculator.addMob(chunk.getPos(), enumcreaturetype); + } + + object2intopenhashmap.addTo(enumcreaturetype, 1); ++ // Paper start ++ if (countMobs) { ++ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity); ++ } ++ // Paper end + }); + } + } +@@ -142,13 +153,37 @@ public final class NaturalSpawner { + continue; + } + +- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) { ++ // Paper start - only allow spawns upto the limit per chunk and update count afterwards ++ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype); ++ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER; ++ int difference = k1 - currEntityCount; ++ ++ if (world.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ int minDiff = Integer.MAX_VALUE; ++ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = world.getChunkSource().chunkMap.playerMobDistanceMap.getObjectsInRange(chunk.getPos()); ++ if (inRange != null) { ++ final Object[] backingSet = inRange.getBackingSet(); ++ for (int k = 0; k < backingSet.length; k++) { ++ if (!(backingSet[k] instanceof final net.minecraft.server.level.ServerPlayer player)) { ++ continue; ++ } ++ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(player, enumcreaturetype), minDiff); ++ } ++ } ++ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; ++ } ++ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) { ++ // Paper end + // CraftBukkit end + Objects.requireNonNull(info); + NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn; + + Objects.requireNonNull(info); +- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn); ++ // Paper start ++ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn, ++ difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); ++ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum); ++ // Paper end + } + } + +@@ -157,11 +192,17 @@ public final class NaturalSpawner { + } + + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { ++ // Paper start - add parameters and int ret type ++ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); ++ } ++ public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add parameters and int ret type + BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); + + if (blockposition.getY() >= world.getMinBuildHeight() + 1) { +- NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner); ++ return NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper + } ++ return 0; // Paper + } + + @VisibleForDebug +@@ -172,15 +213,21 @@ public final class NaturalSpawner { + }); + } + ++ // Paper start - add maxSpawns parameter and return spawned mobs + public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { ++ spawnCategoryForPosition(group, world,chunk, pos, checker, runner, Integer.MAX_VALUE, null); ++ } ++ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { ++ // Paper end - add maxSpawns parameter and return spawned mobs + StructureManager structuremanager = world.structureManager(); + ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); + int i = pos.getY(); + BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn ++ int j = 0; // Paper - moved up + + if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); +- int j = 0; ++ //int j = 0; // Paper - moved up + int k = 0; + + while (k < 3) { +@@ -222,14 +269,14 @@ public final class NaturalSpawner { + // Paper start + Boolean doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); + if (doSpawning == null) { +- return; ++ return j; // Paper + } + if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { + // Paper end + Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); + + if (entityinsentient == null) { +- return; ++ return j; // Paper + } + + entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); +@@ -241,10 +288,15 @@ public final class NaturalSpawner { + ++j; + ++k1; + runner.run(entityinsentient, chunk); ++ // Paper start ++ if (trackEntity != null) { ++ trackEntity.accept(entityinsentient); ++ } ++ // Paper end + } + // CraftBukkit end +- if (j >= entityinsentient.getMaxSpawnClusterSize()) { +- return; ++ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper ++ return j; // Paper + } + + if (entityinsentient.isMaxGroupSizeReached(k1)) { +@@ -266,6 +318,7 @@ public final class NaturalSpawner { + } + + } ++ return j; // Paper + } + + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { +@@ -551,7 +604,7 @@ public final class NaturalSpawner { + MobCategory enumcreaturetype = entitytypes.getCategory(); + + this.mobCategoryCounts.addTo(enumcreaturetype, 1); +- this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); ++ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper + } + + public int getSpawnableChunkCount() { +@@ -567,6 +620,7 @@ public final class NaturalSpawner { + int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; + // CraftBukkit end + ++ if (this.localMobCapCalculator == null) return this.mobCategoryCounts.getInt(enumcreaturetype) < i; // Paper + return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair); + } + } diff --git a/patches/server/0361-Avoid-hopper-searches-if-there-are-no-items.patch b/patches/server/0361-Avoid-hopper-searches-if-there-are-no-items.patch new file mode 100644 index 0000000000..bc2e7f6645 --- /dev/null +++ b/patches/server/0361-Avoid-hopper-searches-if-there-are-no-items.patch @@ -0,0 +1,124 @@ +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/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 7bd8ffb6bfba7f7188532ae3788701c08e1d624a..ef4e48127168acdd614bbdcac97a98da5240928e 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -966,7 +966,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + } + +- }); ++ }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper + return list; + } + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +index 524f3c42964eb83c9109bcc548a1075f1e295411..e9aee7d11798c3bd990466f101e9e342686de11c 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java +@@ -13,6 +13,10 @@ public class EntitySection { + private static final Logger LOGGER = LogUtils.getLogger(); + private final ClassInstanceMultiMap storage; + private Visibility chunkStatus; ++ // Paper start - track number of items and minecarts ++ public int itemCount; ++ public int inventoryEntityCount; ++ // Paper end + + public EntitySection(Class entityClass, Visibility status) { + this.chunkStatus = status; +@@ -20,10 +24,24 @@ public class EntitySection { + } + + public void add(T entity) { ++ // Paper start ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ this.itemCount++; ++ } else if (entity instanceof net.minecraft.world.Container) { ++ this.inventoryEntityCount++; ++ } ++ // Paper end + this.storage.add(entity); + } + + public boolean remove(T entity) { ++ // Paper start ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ this.itemCount--; ++ } else if (entity instanceof net.minecraft.world.Container) { ++ this.inventoryEntityCount--; ++ } ++ // Paper end + return this.storage.remove(entity); + } + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +index 4c1e7c219e1ca153be4423347bd239ebaec4a31d..f54ca6383298848b2ee7108c41fcea593f924881 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -112,13 +112,20 @@ public class EntitySectionStorage { + } + + public void getEntities(AABB box, Consumer action) { ++ // Paper start ++ this.getEntities(box, action, false); ++ } ++ public void getEntities(AABB box, Consumer action, boolean isContainerSearch) { ++ // Paper end + this.forEachAccessibleNonEmptySection(box, (section) -> { ++ if (isContainerSearch && section.inventoryEntityCount <= 0) return; // Paper + section.getEntities(box, action); + }); + } + + public void getEntities(EntityTypeTest filter, AABB box, Consumer action) { + this.forEachAccessibleNonEmptySection(box, (section) -> { ++ if (filter.getBaseClass() == net.minecraft.world.entity.item.ItemEntity.class && section.itemCount <= 0) return; // Paper + section.getEntities(filter, box, action); + }); + } +diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java +index 9723a0ad61548c8c6c4c5ef20a150d5b17d80afd..da1ad0b2679e392ed81b50c15f012c63cb5c939e 100644 +--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java +@@ -17,6 +17,7 @@ public interface LevelEntityGetter { + void get(EntityTypeTest filter, Consumer action); + + void get(AABB box, Consumer action); ++ void get(AABB box, Consumer action, boolean isContainerSearch); // Paper + + void get(EntityTypeTest filter, AABB box, Consumer action); + } +diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java +index d5129c12c79eb6fe6b7e5f8eed4d24226423f5fd..3b13f6ea36a3bfecabe09221eb5c48dddab119db 100644 +--- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java ++++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java +@@ -38,7 +38,13 @@ public class LevelEntityGetterAdapter implements LevelEn + + @Override + public void get(AABB box, Consumer action) { +- this.sectionStorage.getEntities(box, action); ++ // Paper start ++ this.get(box, action, false); ++ } ++ @Override ++ public void get(AABB box, Consumer action, boolean isContainerSearch) { ++ this.sectionStorage.getEntities(box, action, isContainerSearch); ++ // Paper end + } + + @Override diff --git a/patches/server/0361-implement-optional-per-player-mob-spawns.patch b/patches/server/0361-implement-optional-per-player-mob-spawns.patch deleted file mode 100644 index 7aa2416ddc..0000000000 --- a/patches/server/0361-implement-optional-per-player-mob-spawns.patch +++ /dev/null @@ -1,578 +0,0 @@ -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/com/destroystokyo/paper/util/PooledHashSets.java b/src/main/java/com/destroystokyo/paper/util/PooledHashSets.java -new file mode 100644 -index 0000000000000000000000000000000000000000..11de56afaf059b00fa5bec293516bcdce7c4b2b9 ---- /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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 0b68e6c7ef63460d596050ed55039a5ba3cefb24..9e940920f6ff6a43d7162d965936c171c55ed570 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -159,6 +159,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final Long2LongMap chunkSaveCooldowns; - private final Queue unloadQueue; - int viewDistance; -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper - - // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() - public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -@@ -199,16 +200,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ // Paper start - per player mob spawning -+ if (this.playerMobDistanceMap != null) { -+ this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -+ } -+ // Paper end - per player mob spawning - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { - -+ // Paper start - per player mob spawning -+ if (this.playerMobDistanceMap != null) { -+ this.playerMobDistanceMap.remove(player); -+ } -+ // Paper end - per player mob spawning - } - - void updateMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ // Paper start - per player mob spawning -+ if (this.playerMobDistanceMap != null) { -+ this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -+ } -+ // Paper end - per player mob spawning - } - // Paper end - // Paper start -@@ -305,6 +321,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); - this.regionManagers.add(this.dataRegionManager); - // Paper end -+ this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper - } - - protected ChunkGenerator generator() { -@@ -356,6 +373,30 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - } - // Paper end -+ // Paper start -+ public void updatePlayerMobTypeMap(Entity entity) { -+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ return; -+ } -+ int index = entity.getType().getCategory().ordinal(); -+ -+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerMobDistanceMap.getObjectsInRange(entity.chunkPosition()); -+ if (inRange == null) { -+ return; -+ } -+ final Object[] backingSet = inRange.getBackingSet(); -+ for (int i = 0; i < backingSet.length; i++) { -+ if (!(backingSet[i] instanceof final ServerPlayer player)) { -+ continue; -+ } -+ ++player.mobCounts[index]; -+ } -+ } -+ -+ public int getMobCountNear(ServerPlayer entityPlayer, net.minecraft.world.entity.MobCategory mobCategory) { -+ return entityPlayer.mobCounts[mobCategory.ordinal()]; -+ } -+ // Paper end - - private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { - double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8); -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index b9b56068cdacd984f873cfb2a06a312e9912893d..9309ea89a440606be3e56ef634f5048a72b0009e 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -466,6 +466,12 @@ public abstract class DistanceManager { - - } - -+ // Paper start -+ public int getSimulationDistance() { -+ return this.simulationDistance; -+ } -+ // Paper end -+ - public int getNaturalSpawnChunkCount() { - this.naturalSpawnChunkCounter.runAllUpdates(); - return this.naturalSpawnChunkCounter.chunks.size(); -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index d1fa959b6e872565b689ee6b4f1bed9ad9e4077e..f982241898c4aeaf50854e67fe188478f2f2b186 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -764,7 +764,18 @@ public class ServerChunkCache extends ChunkSource { - gameprofilerfiller.push("naturalSpawnCount"); - this.level.timings.countNaturalMobs.startTiming(); // Paper - timings - int l = this.distanceManager.getNaturalSpawnChunkCount(); -- NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)); -+ // Paper start - per player mob spawning -+ NaturalSpawner.SpawnState spawnercreature_d; // moved down -+ if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled -+ // re-set mob counts -+ for (ServerPlayer player : this.level.players) { -+ Arrays.fill(player.mobCounts, 0); -+ } -+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true); -+ } else { -+ spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ } -+ // Paper end - this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings - - this.lastSpawnState = spawnercreature_d; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b36effc88b516958a7c0a46a7eb3a77a98ad3a20..582e08eaeee8fb6c777ad451ccc0436b02cac000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -236,6 +236,11 @@ public class ServerPlayer extends Player { - public boolean queueHealthUpdatePacket = false; - public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; - // Paper end -+ // Paper start - mob spawning rework -+ public static final int MOBCATEGORY_TOTAL_ENUMS = net.minecraft.world.entity.MobCategory.values().length; -+ public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper -+ public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; -+ // Paper end - - // CraftBukkit start - public String displayName; -@@ -326,6 +331,7 @@ public class ServerPlayer extends Player { - this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper - this.bukkitPickUpLoot = true; - this.maxHealthCache = this.getMaxHealth(); -+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - } - // Paper start - Chunk priority - public BlockPos getPointInFront(double inFront) { -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index b4098068e674b639e82c07e5d60e4e2120b4305b..fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -69,6 +69,12 @@ public final class NaturalSpawner { - private NaturalSpawner() {} - - public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator) { -+ // Paper start - add countMobs parameter -+ return createState(spawningChunkCount, entities, chunkSource, localmobcapcalculator, false); -+ } -+ -+ public static NaturalSpawner.SpawnState createState(int spawningChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkSource, LocalMobCapCalculator localmobcapcalculator, boolean countMobs) { -+ // Paper end - PotentialCalculator spawnercreatureprobabilities = new PotentialCalculator(); - Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); - Iterator iterator = entities.iterator(); -@@ -103,11 +109,16 @@ public final class NaturalSpawner { - spawnercreatureprobabilities.addCharge(entity.blockPosition(), biomesettingsmobs_b.getCharge()); - } - -- if (entity instanceof Mob) { -+ if (localmobcapcalculator != null && entity instanceof Mob) { // Paper - localmobcapcalculator.addMob(chunk.getPos(), enumcreaturetype); - } - - object2intopenhashmap.addTo(enumcreaturetype, 1); -+ // Paper start -+ if (countMobs) { -+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity); -+ } -+ // Paper end - }); - } - } -@@ -142,13 +153,37 @@ public final class NaturalSpawner { - continue; - } - -- if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && info.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) { -+ // Paper start - only allow spawns upto the limit per chunk and update count afterwards -+ int currEntityCount = info.mobCategoryCounts.getInt(enumcreaturetype); -+ int k1 = limit * info.getSpawnableChunkCount() / NaturalSpawner.MAGIC_NUMBER; -+ int difference = k1 - currEntityCount; -+ -+ if (world.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ int minDiff = Integer.MAX_VALUE; -+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = world.getChunkSource().chunkMap.playerMobDistanceMap.getObjectsInRange(chunk.getPos()); -+ if (inRange != null) { -+ final Object[] backingSet = inRange.getBackingSet(); -+ for (int k = 0; k < backingSet.length; k++) { -+ if (!(backingSet[k] instanceof final net.minecraft.server.level.ServerPlayer player)) { -+ continue; -+ } -+ minDiff = Math.min(limit - world.getChunkSource().chunkMap.getMobCountNear(player, enumcreaturetype), minDiff); -+ } -+ } -+ difference = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; -+ } -+ if ((spawnAnimals || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (rareSpawn || !enumcreaturetype.isPersistent()) && difference > 0) { -+ // Paper end - // CraftBukkit end - Objects.requireNonNull(info); - NaturalSpawner.SpawnPredicate spawnercreature_c = info::canSpawn; - - Objects.requireNonNull(info); -- NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn); -+ // Paper start -+ int spawnCount = NaturalSpawner.spawnCategoryForChunk(enumcreaturetype, world, chunk, spawnercreature_c, info::afterSpawn, -+ difference, world.paperConfig().entities.spawning.perPlayerMobSpawns ? world.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); -+ info.mobCategoryCounts.mergeInt(enumcreaturetype, spawnCount, Integer::sum); -+ // Paper end - } - } - -@@ -157,11 +192,17 @@ public final class NaturalSpawner { - } - - public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { -+ // Paper start - add parameters and int ret type -+ spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); -+ } -+ public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { -+ // Paper end - add parameters and int ret type - BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); - - if (blockposition.getY() >= world.getMinBuildHeight() + 1) { -- NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner); -+ return NaturalSpawner.spawnCategoryForPosition(group, world, chunk, blockposition, checker, runner, maxSpawns, trackEntity); // Paper - } -+ return 0; // Paper - } - - @VisibleForDebug -@@ -172,15 +213,21 @@ public final class NaturalSpawner { - }); - } - -+ // Paper start - add maxSpawns parameter and return spawned mobs - public static void spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { -+ spawnCategoryForPosition(group, world,chunk, pos, checker, runner, Integer.MAX_VALUE, null); -+ } -+ public static int spawnCategoryForPosition(MobCategory group, ServerLevel world, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { -+ // Paper end - add maxSpawns parameter and return spawned mobs - StructureManager structuremanager = world.structureManager(); - ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); - int i = pos.getY(); - BlockState iblockdata = world.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn -+ int j = 0; // Paper - moved up - - if (iblockdata != null && !iblockdata.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn - BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); -- int j = 0; -+ //int j = 0; // Paper - moved up - int k = 0; - - while (k < 3) { -@@ -222,14 +269,14 @@ public final class NaturalSpawner { - // Paper start - Boolean doSpawning = isValidSpawnPostitionForType(world, group, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2); - if (doSpawning == null) { -- return; -+ return j; // Paper - } - if (doSpawning && checker.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) { - // Paper end - Mob entityinsentient = NaturalSpawner.getMobForSpawn(world, biomesettingsmobs_c.type); - - if (entityinsentient == null) { -- return; -+ return j; // Paper - } - - entityinsentient.moveTo(d0, (double) i, d1, world.random.nextFloat() * 360.0F, 0.0F); -@@ -241,10 +288,15 @@ public final class NaturalSpawner { - ++j; - ++k1; - runner.run(entityinsentient, chunk); -+ // Paper start -+ if (trackEntity != null) { -+ trackEntity.accept(entityinsentient); -+ } -+ // Paper end - } - // CraftBukkit end -- if (j >= entityinsentient.getMaxSpawnClusterSize()) { -- return; -+ if (j >= entityinsentient.getMaxSpawnClusterSize() || j >= maxSpawns) { // Paper -+ return j; // Paper - } - - if (entityinsentient.isMaxGroupSizeReached(k1)) { -@@ -266,6 +318,7 @@ public final class NaturalSpawner { - } - - } -+ return j; // Paper - } - - private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { -@@ -551,7 +604,7 @@ public final class NaturalSpawner { - MobCategory enumcreaturetype = entitytypes.getCategory(); - - this.mobCategoryCounts.addTo(enumcreaturetype, 1); -- this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); -+ if (this.localMobCapCalculator != null) this.localMobCapCalculator.addMob(new ChunkPos(blockposition), enumcreaturetype); // Paper - } - - public int getSpawnableChunkCount() { -@@ -567,6 +620,7 @@ public final class NaturalSpawner { - int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; - // CraftBukkit end - -+ if (this.localMobCapCalculator == null) return this.mobCategoryCounts.getInt(enumcreaturetype) < i; // Paper - return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair); - } - } diff --git a/patches/server/0362-Avoid-hopper-searches-if-there-are-no-items.patch b/patches/server/0362-Avoid-hopper-searches-if-there-are-no-items.patch deleted file mode 100644 index bc2e7f6645..0000000000 --- a/patches/server/0362-Avoid-hopper-searches-if-there-are-no-items.patch +++ /dev/null @@ -1,124 +0,0 @@ -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/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 7bd8ffb6bfba7f7188532ae3788701c08e1d624a..ef4e48127168acdd614bbdcac97a98da5240928e 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -966,7 +966,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - } - -- }); -+ }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - return list; - } - -diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySection.java b/src/main/java/net/minecraft/world/level/entity/EntitySection.java -index 524f3c42964eb83c9109bcc548a1075f1e295411..e9aee7d11798c3bd990466f101e9e342686de11c 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntitySection.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntitySection.java -@@ -13,6 +13,10 @@ public class EntitySection { - private static final Logger LOGGER = LogUtils.getLogger(); - private final ClassInstanceMultiMap storage; - private Visibility chunkStatus; -+ // Paper start - track number of items and minecarts -+ public int itemCount; -+ public int inventoryEntityCount; -+ // Paper end - - public EntitySection(Class entityClass, Visibility status) { - this.chunkStatus = status; -@@ -20,10 +24,24 @@ public class EntitySection { - } - - public void add(T entity) { -+ // Paper start -+ if (entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ this.itemCount++; -+ } else if (entity instanceof net.minecraft.world.Container) { -+ this.inventoryEntityCount++; -+ } -+ // Paper end - this.storage.add(entity); - } - - public boolean remove(T entity) { -+ // Paper start -+ if (entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ this.itemCount--; -+ } else if (entity instanceof net.minecraft.world.Container) { -+ this.inventoryEntityCount--; -+ } -+ // Paper end - return this.storage.remove(entity); - } - -diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java -index 4c1e7c219e1ca153be4423347bd239ebaec4a31d..f54ca6383298848b2ee7108c41fcea593f924881 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java -@@ -112,13 +112,20 @@ public class EntitySectionStorage { - } - - public void getEntities(AABB box, Consumer action) { -+ // Paper start -+ this.getEntities(box, action, false); -+ } -+ public void getEntities(AABB box, Consumer action, boolean isContainerSearch) { -+ // Paper end - this.forEachAccessibleNonEmptySection(box, (section) -> { -+ if (isContainerSearch && section.inventoryEntityCount <= 0) return; // Paper - section.getEntities(box, action); - }); - } - - public void getEntities(EntityTypeTest filter, AABB box, Consumer action) { - this.forEachAccessibleNonEmptySection(box, (section) -> { -+ if (filter.getBaseClass() == net.minecraft.world.entity.item.ItemEntity.class && section.itemCount <= 0) return; // Paper - section.getEntities(filter, box, action); - }); - } -diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java -index 9723a0ad61548c8c6c4c5ef20a150d5b17d80afd..da1ad0b2679e392ed81b50c15f012c63cb5c939e 100644 ---- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetter.java -@@ -17,6 +17,7 @@ public interface LevelEntityGetter { - void get(EntityTypeTest filter, Consumer action); - - void get(AABB box, Consumer action); -+ void get(AABB box, Consumer action, boolean isContainerSearch); // Paper - - void get(EntityTypeTest filter, AABB box, Consumer action); - } -diff --git a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java -index d5129c12c79eb6fe6b7e5f8eed4d24226423f5fd..3b13f6ea36a3bfecabe09221eb5c48dddab119db 100644 ---- a/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java -+++ b/src/main/java/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java -@@ -38,7 +38,13 @@ public class LevelEntityGetterAdapter implements LevelEn - - @Override - public void get(AABB box, Consumer action) { -- this.sectionStorage.getEntities(box, action); -+ // Paper start -+ this.get(box, action, false); -+ } -+ @Override -+ public void get(AABB box, Consumer action, boolean isContainerSearch) { -+ this.sectionStorage.getEntities(box, action, isContainerSearch); -+ // Paper end - } - - @Override diff --git a/patches/server/0362-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server/0362-Bees-get-gravity-in-void.-Fixes-MC-167279.patch new file mode 100644 index 0000000000..2b398af935 --- /dev/null +++ b/patches/server/0362-Bees-get-gravity-in-void.-Fixes-MC-167279.patch @@ -0,0 +1,34 @@ +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/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index bdc9911f5a72d2f23a3a01d0420ac9ba6cb78570..d94f045d7fe928c256c5d3e1af02ac73d7897f5a 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -143,7 +143,22 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + public Bee(EntityType type, Level world) { + super(type, world); + this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); +- this.moveControl = new FlyingMoveControl(this, 20, true); ++ // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 ++ class BeeFlyingMoveControl extends FlyingMoveControl { ++ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { ++ super(entity, maxPitchChange, noGravity); ++ } ++ ++ @Override ++ public void tick() { ++ if (this.mob.getY() <= Bee.this.level.getMinBuildHeight()) { ++ this.mob.setNoGravity(false); ++ } ++ super.tick(); ++ } ++ } ++ this.moveControl = new BeeFlyingMoveControl(this, 20, true); ++ // Paper end + this.lookControl = new Bee.BeeLookControl(this); + this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); + this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); diff --git a/patches/server/0363-Bees-get-gravity-in-void.-Fixes-MC-167279.patch b/patches/server/0363-Bees-get-gravity-in-void.-Fixes-MC-167279.patch deleted file mode 100644 index 2b398af935..0000000000 --- a/patches/server/0363-Bees-get-gravity-in-void.-Fixes-MC-167279.patch +++ /dev/null @@ -1,34 +0,0 @@ -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/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index bdc9911f5a72d2f23a3a01d0420ac9ba6cb78570..d94f045d7fe928c256c5d3e1af02ac73d7897f5a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -143,7 +143,22 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - public Bee(EntityType type, Level world) { - super(type, world); - this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); -- this.moveControl = new FlyingMoveControl(this, 20, true); -+ // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 -+ class BeeFlyingMoveControl extends FlyingMoveControl { -+ public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { -+ super(entity, maxPitchChange, noGravity); -+ } -+ -+ @Override -+ public void tick() { -+ if (this.mob.getY() <= Bee.this.level.getMinBuildHeight()) { -+ this.mob.setNoGravity(false); -+ } -+ super.tick(); -+ } -+ } -+ this.moveControl = new BeeFlyingMoveControl(this, 20, true); -+ // Paper end - this.lookControl = new Bee.BeeLookControl(this); - this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); diff --git a/patches/server/0363-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server/0363-Optimise-getChunkAt-calls-for-loaded-chunks.patch new file mode 100644 index 0000000000..2211c9793e --- /dev/null +++ b/patches/server/0363-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/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index f982241898c4aeaf50854e67fe188478f2f2b186..38788e0ab7e881102d38bae53ba9d2d4f62b99d4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -444,6 +444,12 @@ public class ServerChunkCache extends ChunkSource { + return this.getChunk(x, z, leastStatus, create); + }, this.mainThreadProcessor).join(); + } else { ++ // Paper start - optimise for loaded chunks ++ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ // Paper end + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + + gameprofilerfiller.incrementCounter("getChunk"); +@@ -499,39 +505,7 @@ public class ServerChunkCache extends ChunkSource { + if (Thread.currentThread() != this.mainThread) { + return null; + } else { +- this.level.getProfiler().incrementCounter("getChunkNow"); +- long k = ChunkPos.asLong(chunkX, chunkZ); +- +- for (int l = 0; l < 4; ++l) { +- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) { +- ChunkAccess ichunkaccess = this.lastChunk[l]; +- +- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null; +- } +- } +- +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); +- +- if (playerchunk == null) { +- return null; +- } else { +- Either either = (Either) playerchunk.getFutureIfPresent(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error +- +- if (either == null) { +- return null; +- } else { +- ChunkAccess ichunkaccess1 = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error +- +- if (ichunkaccess1 != null) { +- this.storeInCache(k, ichunkaccess1, ChunkStatus.FULL); +- if (ichunkaccess1 instanceof LevelChunk) { +- return (LevelChunk) ichunkaccess1; +- } +- } +- +- return null; +- } +- } ++ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks + } + } + diff --git a/patches/server/0364-Add-debug-for-sync-chunk-loads.patch b/patches/server/0364-Add-debug-for-sync-chunk-loads.patch new file mode 100644 index 0000000000..909d9b70a6 --- /dev/null +++ b/patches/server/0364-Add-debug-for-sync-chunk-loads.patch @@ -0,0 +1,328 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Jul 2019 03:29:14 -0700 +Subject: [PATCH] Add debug for sync chunk loads + +This patch adds a tool to find calls to getChunkAt which would load +chunks, 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 +- To clear clear the currently stored sync load info, use + /paper syncloadinfo clear + +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..0bb4aaa546939b67a5d22865190f30478a9337c1 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -0,0 +1,175 @@ ++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.Level; ++ ++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 clear() { ++ SYNC_LOADS.clear(); ++ } ++ ++ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { ++ if (!ENABLED) { ++ return; ++ } ++ ++ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); ++ ++ SYNC_LOADS.compute(world, (final Level 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 Level 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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index f44ab1d71210e84328661c0feb662989a5635b6d..25f6d1c15cdfb4abdf1edd2ad9bbdc0e37b546f3 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; ++import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; + import it.unimi.dsi.fastutil.Pair; + import java.util.ArrayList; +@@ -44,6 +45,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); ++ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1120aef5b0dd983c467167f77245884e1198552a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java +@@ -0,0 +1,78 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.destroystokyo.paper.io.SyncLoadFinder; ++import com.google.gson.JsonObject; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++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.List; ++import org.bukkit.command.CommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class SyncLoadInfoCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doSyncLoadInfo(sender, args); ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return CommandUtil.getListMatchingLast(sender, args, "clear"); ++ } ++ ++ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { ++ if (!SyncLoadFinder.ENABLED) { ++ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); ++ return; ++ } ++ ++ if (args.length > 0 && args[0].equals("clear")) { ++ SyncLoadFinder.clear(); ++ sender.sendMessage(text("Sync load data cleared.", GRAY)); ++ 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(text("Writing sync load info to " + file, GREEN)); ++ ++ ++ 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(text("Successfully written sync load information!", GREEN)); ++ } catch (Throwable thr) { ++ sender.sendMessage(text("Failed to write sync load information!", RED)); ++ thr.printStackTrace(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 38788e0ab7e881102d38bae53ba9d2d4f62b99d4..471cc00c677b6581ba84c8cac25d2246c2a14bc9 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -478,6 +478,7 @@ public class ServerChunkCache extends ChunkSource { + this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); + // Paper end ++ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info + this.level.timings.syncChunkLoad.startTiming(); // Paper + chunkproviderserver_b.managedBlock(completablefuture::isDone); + com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 5622917a2884e87d43abfaf58e722957931b3178..ed8014ed36f8354aa1ae07689e9315c0c0d8867a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -392,6 +392,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + }; + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; + // Paper end ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; ++ } ++ // Paper end + + // Paper start - optimise getPlayerByUUID + @Nullable diff --git a/patches/server/0364-Optimise-getChunkAt-calls-for-loaded-chunks.patch b/patches/server/0364-Optimise-getChunkAt-calls-for-loaded-chunks.patch deleted file mode 100644 index 2211c9793e..0000000000 --- a/patches/server/0364-Optimise-getChunkAt-calls-for-loaded-chunks.patch +++ /dev/null @@ -1,66 +0,0 @@ -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/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index f982241898c4aeaf50854e67fe188478f2f2b186..38788e0ab7e881102d38bae53ba9d2d4f62b99d4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -444,6 +444,12 @@ public class ServerChunkCache extends ChunkSource { - return this.getChunk(x, z, leastStatus, create); - }, this.mainThreadProcessor).join(); - } else { -+ // Paper start - optimise for loaded chunks -+ LevelChunk ifLoaded = this.getChunkAtIfLoadedMainThread(x, z); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ // Paper end - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - - gameprofilerfiller.incrementCounter("getChunk"); -@@ -499,39 +505,7 @@ public class ServerChunkCache extends ChunkSource { - if (Thread.currentThread() != this.mainThread) { - return null; - } else { -- this.level.getProfiler().incrementCounter("getChunkNow"); -- long k = ChunkPos.asLong(chunkX, chunkZ); -- -- for (int l = 0; l < 4; ++l) { -- if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) { -- ChunkAccess ichunkaccess = this.lastChunk[l]; -- -- return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null; -- } -- } -- -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); -- -- if (playerchunk == null) { -- return null; -- } else { -- Either either = (Either) playerchunk.getFutureIfPresent(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error -- -- if (either == null) { -- return null; -- } else { -- ChunkAccess ichunkaccess1 = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error -- -- if (ichunkaccess1 != null) { -- this.storeInCache(k, ichunkaccess1, ChunkStatus.FULL); -- if (ichunkaccess1 instanceof LevelChunk) { -- return (LevelChunk) ichunkaccess1; -- } -- } -- -- return null; -- } -- } -+ return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks - } - } - diff --git a/patches/server/0365-Add-debug-for-sync-chunk-loads.patch b/patches/server/0365-Add-debug-for-sync-chunk-loads.patch deleted file mode 100644 index 909d9b70a6..0000000000 --- a/patches/server/0365-Add-debug-for-sync-chunk-loads.patch +++ /dev/null @@ -1,328 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 19 Jul 2019 03:29:14 -0700 -Subject: [PATCH] Add debug for sync chunk loads - -This patch adds a tool to find calls to getChunkAt which would load -chunks, 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 -- To clear clear the currently stored sync load info, use - /paper syncloadinfo clear - -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..0bb4aaa546939b67a5d22865190f30478a9337c1 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -@@ -0,0 +1,175 @@ -+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.Level; -+ -+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 clear() { -+ SYNC_LOADS.clear(); -+ } -+ -+ public static void logSyncLoad(final Level world, final int chunkX, final int chunkZ) { -+ if (!ENABLED) { -+ return; -+ } -+ -+ final ThrowableWithEquals stacktrace = new ThrowableWithEquals(Thread.currentThread().getStackTrace()); -+ -+ SYNC_LOADS.compute(world, (final Level 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 Level 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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index f44ab1d71210e84328661c0feb662989a5635b6d..25f6d1c15cdfb4abdf1edd2ad9bbdc0e37b546f3 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.EntityCommand; - import io.papermc.paper.command.subcommands.FixLightCommand; - import io.papermc.paper.command.subcommands.HeapDumpCommand; - import io.papermc.paper.command.subcommands.ReloadCommand; -+import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; - import io.papermc.paper.command.subcommands.VersionCommand; - import it.unimi.dsi.fastutil.Pair; - import java.util.ArrayList; -@@ -44,6 +45,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("version"), new VersionCommand()); - commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); - commands.put(Set.of("fixlight"), new FixLightCommand()); -+ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1120aef5b0dd983c467167f77245884e1198552a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/SyncLoadInfoCommand.java -@@ -0,0 +1,78 @@ -+package io.papermc.paper.command.subcommands; -+ -+import com.destroystokyo.paper.io.SyncLoadFinder; -+import com.google.gson.JsonObject; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonWriter; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+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.List; -+import org.bukkit.command.CommandSender; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+@DefaultQualifier(NonNull.class) -+public final class SyncLoadInfoCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doSyncLoadInfo(sender, args); -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ return CommandUtil.getListMatchingLast(sender, args, "clear"); -+ } -+ -+ private void doSyncLoadInfo(final CommandSender sender, final String[] args) { -+ if (!SyncLoadFinder.ENABLED) { -+ sender.sendMessage(text("This command requires the server startup flag '-Dpaper.debug-sync-loads=true' to be set.", RED)); -+ return; -+ } -+ -+ if (args.length > 0 && args[0].equals("clear")) { -+ SyncLoadFinder.clear(); -+ sender.sendMessage(text("Sync load data cleared.", GRAY)); -+ 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(text("Writing sync load info to " + file, GREEN)); -+ -+ -+ 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(text("Successfully written sync load information!", GREEN)); -+ } catch (Throwable thr) { -+ sender.sendMessage(text("Failed to write sync load information!", RED)); -+ thr.printStackTrace(); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 38788e0ab7e881102d38bae53ba9d2d4f62b99d4..471cc00c677b6581ba84c8cac25d2246c2a14bc9 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -478,6 +478,7 @@ public class ServerChunkCache extends ChunkSource { - this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1); - // Paper end -+ com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info - this.level.timings.syncChunkLoad.startTiming(); // Paper - chunkproviderserver_b.managedBlock(completablefuture::isDone); - com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5622917a2884e87d43abfaf58e722957931b3178..ed8014ed36f8354aa1ae07689e9315c0c0d8867a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -392,6 +392,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - }; - public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; - // Paper end -+ // Paper start -+ @Override -+ public boolean hasChunk(int chunkX, int chunkZ) { -+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; -+ } -+ // Paper end - - // Paper start - optimise getPlayerByUUID - @Nullable diff --git a/patches/server/0365-Remove-garbage-Java-version-check.patch b/patches/server/0365-Remove-garbage-Java-version-check.patch new file mode 100644 index 0000000000..026d55fd76 --- /dev/null +++ b/patches/server/0365-Remove-garbage-Java-version-check.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 16 Mar 2022 13:58:16 +0100 +Subject: [PATCH] Remove garbage Java version check + + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index fdbb6ab345833d8163b7d365d03b641d8a09d008..08e74f41516a545a2371a7418d995ab288831834 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -196,10 +196,6 @@ public class Main { + System.err.println("Unsupported Java detected (" + javaVersion + "). This version of Minecraft requires at least Java 17. Check your Java version with the command 'java -version'."); + return; + } +- if (javaVersion > 62.0) { +- System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 18 is supported."); +- return; +- } + + try { + // Paper start - Handled by TerminalConsoleAppender diff --git a/patches/server/0366-Add-ThrownEggHatchEvent.patch b/patches/server/0366-Add-ThrownEggHatchEvent.patch new file mode 100644 index 0000000000..c026427598 --- /dev/null +++ b/patches/server/0366-Add-ThrownEggHatchEvent.patch @@ -0,0 +1,27 @@ +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/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +index 4e083dcd07e5975c7379035e72ac2f3469e919fd..77941e3981e49cf5662b3e3c86a9c419080b17c8 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java +@@ -77,6 +77,14 @@ public class ThrownEgg extends ThrowableItemProjectile { + 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 = level.getWorld().createEntity(new org.bukkit.Location(level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); diff --git a/patches/server/0366-Remove-garbage-Java-version-check.patch b/patches/server/0366-Remove-garbage-Java-version-check.patch deleted file mode 100644 index 026d55fd76..0000000000 --- a/patches/server/0366-Remove-garbage-Java-version-check.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 16 Mar 2022 13:58:16 +0100 -Subject: [PATCH] Remove garbage Java version check - - -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index fdbb6ab345833d8163b7d365d03b641d8a09d008..08e74f41516a545a2371a7418d995ab288831834 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -196,10 +196,6 @@ public class Main { - System.err.println("Unsupported Java detected (" + javaVersion + "). This version of Minecraft requires at least Java 17. Check your Java version with the command 'java -version'."); - return; - } -- if (javaVersion > 62.0) { -- System.err.println("Unsupported Java detected (" + javaVersion + "). Only up to Java 18 is supported."); -- return; -- } - - try { - // Paper start - Handled by TerminalConsoleAppender diff --git a/patches/server/0367-Add-ThrownEggHatchEvent.patch b/patches/server/0367-Add-ThrownEggHatchEvent.patch deleted file mode 100644 index c026427598..0000000000 --- a/patches/server/0367-Add-ThrownEggHatchEvent.patch +++ /dev/null @@ -1,27 +0,0 @@ -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/ThrownEgg.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -index 4e083dcd07e5975c7379035e72ac2f3469e919fd..77941e3981e49cf5662b3e3c86a9c419080b17c8 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEgg.java -@@ -77,6 +77,14 @@ public class ThrownEgg extends ThrowableItemProjectile { - 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 = level.getWorld().createEntity(new org.bukkit.Location(level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); diff --git a/patches/server/0367-Entity-Jump-API.patch b/patches/server/0367-Entity-Jump-API.patch new file mode 100644 index 0000000000..eb51ab24b2 --- /dev/null +++ b/patches/server/0367-Entity-Jump-API.patch @@ -0,0 +1,73 @@ +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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 7e7128973153f4c3a737c1e956e41bab0e85c69a..a5b532a6051f9313bb5042bf61712015768b5426 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3240,8 +3240,10 @@ public abstract class LivingEntity extends Entity { + } else if (this.isInLava() && (!this.onGround || d7 > d8)) { + this.jumpInLiquid(FluidTags.LAVA); + } else if ((this.onGround || flag && d7 <= d8) && this.noJumpDelay == 0) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + this.jumpFromGround(); + this.noJumpDelay = 10; ++ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop + } + } else { + this.noJumpDelay = 0; +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index dbcf70dacf99b61c0147b43edd6a374b31809733..9c07e3f5554b3b9cf2a2c4d9239a72342567d7f1 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -515,7 +515,9 @@ public class Panda extends Animal { + Panda entitypanda = (Panda) iterator.next(); + + if (!entitypanda.isBaby() && entitypanda.onGround && !entitypanda.isInWater() && entitypanda.canPerformAction()) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + entitypanda.jumpFromGround(); ++ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +index f747aa85beab98fbecdbe15b188be6614478bac6..a0eee7dc73bd4a96d9a1aa9555093820c5f7c49c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java +@@ -177,7 +177,9 @@ public class Ravager extends Raider { + } + + if (!flag && this.onGround) { ++ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper + this.jumpFromGround(); ++ } 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 fb92f55ae3c8c54edce7565b27fb84f50ee85702..ee6783220de6c3142810744a68fda51367589bd1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -870,5 +870,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public org.bukkit.inventory.EquipmentSlot getHandRaised() { + return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; + } ++ ++ @Override ++ public boolean isJumping() { ++ return getHandle().jumping; ++ } ++ ++ @Override ++ public void setJumping(boolean jumping) { ++ getHandle().setJumping(jumping); ++ if (jumping && getHandle() instanceof Mob) { ++ // this is needed to actually make a mob jump ++ ((Mob) getHandle()).getJumpControl().jump(); ++ } ++ } + // Paper end + } diff --git a/patches/server/0368-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0368-Add-option-to-nerf-pigmen-from-nether-portals.patch new file mode 100644 index 0000000000..a2cd3658b8 --- /dev/null +++ b/patches/server/0368-Add-option-to-nerf-pigmen-from-nether-portals.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 7 Feb 2020 14:36:56 -0600 +Subject: [PATCH] Add option to nerf pigmen from nether portals + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f94462c3aeec3a71b30de1834fb72934e77c35ac..152c2f10a71c231a43a21e2016b040c785417589 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -386,6 +386,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // Paper start + public long activatedImmunityTick = Integer.MIN_VALUE; // Paper + public boolean isTemporarilyActive = false; // Paper ++ public boolean fromNetherPortal; // Paper + protected int numCollisions = 0; // Paper + public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one + @javax.annotation.Nullable +@@ -1992,6 +1993,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (spawnedViaMobSpawner) { + nbt.putBoolean("Paper.FromMobSpawner", true); + } ++ if (fromNetherPortal) { ++ nbt.putBoolean("Paper.FromNetherPortal", true); ++ } + // Paper end + return nbt; + } catch (Throwable throwable) { +@@ -2133,6 +2137,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status ++ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); + if (nbt.contains("Paper.SpawnReason")) { + String spawnReasonName = nbt.getString("Paper.SpawnReason"); + try { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index 33078e2199c8eb0ce012a72eeb4421df12817b84..8c97cae63b4b373f1d67e797b9fe1064b5205da5 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -66,6 +66,8 @@ public class NetherPortalBlock extends Block { + + if (entity != null) { + entity.setPortalCooldown(); ++ entity.fromNetherPortal = true; // Paper ++ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper + } + } + } diff --git a/patches/server/0368-Entity-Jump-API.patch b/patches/server/0368-Entity-Jump-API.patch deleted file mode 100644 index 20d793790b..0000000000 --- a/patches/server/0368-Entity-Jump-API.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Sat, 8 Feb 2020 23:26:11 -0600 -Subject: [PATCH] Entity Jump API - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 383a892a5c34ec5e8f7d102f5a9bec11ae193c0e..878dd05e0a67a2c6bf48eab7f2d58bbfaf53d2d7 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3218,8 +3218,10 @@ public abstract class LivingEntity extends Entity { - } else if (this.isInLava() && (!this.onGround || d7 > d8)) { - this.jumpInLiquid(FluidTags.LAVA); - } else if ((this.onGround || flag && d7 <= d8) && this.noJumpDelay == 0) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - this.jumpFromGround(); - this.noJumpDelay = 10; -+ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop - } - } else { - this.noJumpDelay = 0; -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index dbcf70dacf99b61c0147b43edd6a374b31809733..9c07e3f5554b3b9cf2a2c4d9239a72342567d7f1 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -515,7 +515,9 @@ public class Panda extends Animal { - Panda entitypanda = (Panda) iterator.next(); - - if (!entitypanda.isBaby() && entitypanda.onGround && !entitypanda.isInWater() && entitypanda.canPerformAction()) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - entitypanda.jumpFromGround(); -+ } else { this.setJumping(false); } // Paper - setJumping(false) stops a potential loop - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index f747aa85beab98fbecdbe15b188be6614478bac6..a0eee7dc73bd4a96d9a1aa9555093820c5f7c49c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -177,7 +177,9 @@ public class Ravager extends Raider { - } - - if (!flag && this.onGround) { -+ if (new com.destroystokyo.paper.event.entity.EntityJumpEvent(getBukkitLivingEntity()).callEvent()) { // Paper - this.jumpFromGround(); -+ } 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 ebebbd2536b9641b5b01d0e3fc060f89861eecdb..9887c98e3bc1c940f787328bfa2f6fcc22cbce1f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -829,5 +829,19 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public org.bukkit.inventory.EquipmentSlot getHandRaised() { - return getHandle().getUsedItemHand() == net.minecraft.world.InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND; - } -+ -+ @Override -+ public boolean isJumping() { -+ return getHandle().jumping; -+ } -+ -+ @Override -+ public void setJumping(boolean jumping) { -+ getHandle().setJumping(jumping); -+ if (jumping && getHandle() instanceof Mob) { -+ // this is needed to actually make a mob jump -+ ((Mob) getHandle()).getJumpControl().jump(); -+ } -+ } - // Paper end - } diff --git a/patches/server/0369-Add-option-to-nerf-pigmen-from-nether-portals.patch b/patches/server/0369-Add-option-to-nerf-pigmen-from-nether-portals.patch deleted file mode 100644 index 8f2b02bbbc..0000000000 --- a/patches/server/0369-Add-option-to-nerf-pigmen-from-nether-portals.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 7 Feb 2020 14:36:56 -0600 -Subject: [PATCH] Add option to nerf pigmen from nether portals - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 240650cee26fc907f632e0c8ef3559a36460a3ba..185f6dd93f325b638289acd723c6cbbbedac80e1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -386,6 +386,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - // Paper start - public long activatedImmunityTick = Integer.MIN_VALUE; // Paper - public boolean isTemporarilyActive = false; // Paper -+ public boolean fromNetherPortal; // Paper - protected int numCollisions = 0; // Paper - public boolean spawnedViaMobSpawner; // Paper - Yes this name is similar to above, upstream took the better one - @javax.annotation.Nullable -@@ -1978,6 +1979,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (spawnedViaMobSpawner) { - nbt.putBoolean("Paper.FromMobSpawner", true); - } -+ if (fromNetherPortal) { -+ nbt.putBoolean("Paper.FromNetherPortal", true); -+ } - // Paper end - return nbt; - } catch (Throwable throwable) { -@@ -2119,6 +2123,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - spawnedViaMobSpawner = nbt.getBoolean("Paper.FromMobSpawner"); // Restore entity's from mob spawner status -+ fromNetherPortal = nbt.getBoolean("Paper.FromNetherPortal"); - if (nbt.contains("Paper.SpawnReason")) { - String spawnReasonName = nbt.getString("Paper.SpawnReason"); - try { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 33078e2199c8eb0ce012a72eeb4421df12817b84..8c97cae63b4b373f1d67e797b9fe1064b5205da5 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -66,6 +66,8 @@ public class NetherPortalBlock extends Block { - - if (entity != null) { - entity.setPortalCooldown(); -+ entity.fromNetherPortal = true; // Paper -+ if (world.paperConfig().entities.behavior.nerfPigmenFromNetherPortals) ((net.minecraft.world.entity.Mob) entity).aware = false; // Paper - } - } - } diff --git a/patches/server/0369-Make-the-GUI-graph-fancier.patch b/patches/server/0369-Make-the-GUI-graph-fancier.patch new file mode 100644 index 0000000000..b97ce20f84 --- /dev/null +++ b/patches/server/0369-Make-the-GUI-graph-fancier.patch @@ -0,0 +1,398 @@ +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..537bc6213545e8ff1b7b51bc4b27fd5b2a740883 +--- /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 close() { ++ 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..23239679d6584f1088b2b94c46eb9a5c1f9ad91d +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +@@ -0,0 +1,73 @@ ++package com.destroystokyo.paper.gui; ++ ++import net.minecraft.Util; ++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 = Util.make(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.tickTimes)) + " 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/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +index 75083eeb9b413e6dd5375007360dce6857a08fff..66464c10a6b33414c6d1b67b926a66c343d5f887 100644 +--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +@@ -95,7 +95,7 @@ public class MinecraftServerGui extends JComponent { + + private JComponent buildInfoPanel() { + JPanel jpanel = new JPanel(new BorderLayout()); +- StatsComponent guistatscomponent = new StatsComponent(this.server); ++ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper + Collection collection = this.finalizers; // CraftBukkit - decompile error + + Objects.requireNonNull(guistatscomponent); diff --git a/patches/server/0370-Make-the-GUI-graph-fancier.patch b/patches/server/0370-Make-the-GUI-graph-fancier.patch deleted file mode 100644 index b97ce20f84..0000000000 --- a/patches/server/0370-Make-the-GUI-graph-fancier.patch +++ /dev/null @@ -1,398 +0,0 @@ -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..537bc6213545e8ff1b7b51bc4b27fd5b2a740883 ---- /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 close() { -+ 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..23239679d6584f1088b2b94c46eb9a5c1f9ad91d ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java -@@ -0,0 +1,73 @@ -+package com.destroystokyo.paper.gui; -+ -+import net.minecraft.Util; -+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 = Util.make(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.tickTimes)) + " 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/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -index 75083eeb9b413e6dd5375007360dce6857a08fff..66464c10a6b33414c6d1b67b926a66c343d5f887 100644 ---- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -@@ -95,7 +95,7 @@ public class MinecraftServerGui extends JComponent { - - private JComponent buildInfoPanel() { - JPanel jpanel = new JPanel(new BorderLayout()); -- StatsComponent guistatscomponent = new StatsComponent(this.server); -+ com.destroystokyo.paper.gui.GuiStatsComponent guistatscomponent = new com.destroystokyo.paper.gui.GuiStatsComponent(this.server); // Paper - Collection collection = this.finalizers; // CraftBukkit - decompile error - - Objects.requireNonNull(guistatscomponent); diff --git a/patches/server/0370-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0370-add-hand-to-BlockMultiPlaceEvent.patch new file mode 100644 index 0000000000..486014f2be --- /dev/null +++ b/patches/server/0370-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 64cd5b2bc4032bfb0c917cc33884062ddba2738f..3aa82c7d26ef9fed3d4b670ac330204b55609396 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -345,13 +345,18 @@ public class CraftEventFactory { + } + + org.bukkit.inventory.ItemStack item; ++ // Paper start - add hand to BlockMultiPlaceEvent ++ EquipmentSlot equipmentSlot; + if (hand == InteractionHand.MAIN_HAND) { + item = player.getInventory().getItemInMainHand(); ++ equipmentSlot = EquipmentSlot.HAND; + } else { + item = player.getInventory().getItemInOffHand(); ++ equipmentSlot = EquipmentSlot.OFF_HAND; + } + +- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); ++ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); ++ // Paper end + craftServer.getPluginManager().callEvent(event); + + return event; diff --git a/patches/server/0371-Validate-tripwire-hook-placement-before-update.patch b/patches/server/0371-Validate-tripwire-hook-placement-before-update.patch new file mode 100644 index 0000000000..fd8a74807c --- /dev/null +++ b/patches/server/0371-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/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index a5f8c7d9d9998eebce7f15e01c157651b9831516..4a516828e5c6abd63511ee7c93fcff11203cf8d0 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -175,6 +175,7 @@ public class TripWireHookBlock extends Block { + + this.emitState(world, pos, flag4, flag5, flag2, flag3); + if (!beingRemoved) { ++ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - validate + world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); + if (flag1) { + this.notifyNeighbors(world, pos, enumdirection); diff --git a/patches/server/0371-add-hand-to-BlockMultiPlaceEvent.patch b/patches/server/0371-add-hand-to-BlockMultiPlaceEvent.patch deleted file mode 100644 index 486014f2be..0000000000 --- a/patches/server/0371-add-hand-to-BlockMultiPlaceEvent.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sun, 1 Mar 2020 22:43:24 +0100 -Subject: [PATCH] add hand to BlockMultiPlaceEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 64cd5b2bc4032bfb0c917cc33884062ddba2738f..3aa82c7d26ef9fed3d4b670ac330204b55609396 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -345,13 +345,18 @@ public class CraftEventFactory { - } - - org.bukkit.inventory.ItemStack item; -+ // Paper start - add hand to BlockMultiPlaceEvent -+ EquipmentSlot equipmentSlot; - if (hand == InteractionHand.MAIN_HAND) { - item = player.getInventory().getItemInMainHand(); -+ equipmentSlot = EquipmentSlot.HAND; - } else { - item = player.getInventory().getItemInOffHand(); -+ equipmentSlot = EquipmentSlot.OFF_HAND; - } - -- BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild); -+ BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, blockClicked, item, player, canBuild, equipmentSlot); -+ // Paper end - craftServer.getPluginManager().callEvent(event); - - return event; diff --git a/patches/server/0372-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server/0372-Add-option-to-allow-iron-golems-to-spawn-in-air.patch new file mode 100644 index 0000000000..f058d157d0 --- /dev/null +++ b/patches/server/0372-Add-option-to-allow-iron-golems-to-spawn-in-air.patch @@ -0,0 +1,19 @@ +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/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +index d2864615588aadf81ac9763ba402ede0c1eebb23..e73acfa2f5a4066fa1beee1758082a2fe97a43b3 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java ++++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +@@ -334,7 +334,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { + BlockPos blockposition1 = blockposition.below(); + BlockState iblockdata = world.getBlockState(blockposition1); + +- if (!iblockdata.entityCanStandOn(world, blockposition1, this)) { ++ if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !level.paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper + return false; + } else { + for (int i = 1; i < 3; ++i) { diff --git a/patches/server/0372-Validate-tripwire-hook-placement-before-update.patch b/patches/server/0372-Validate-tripwire-hook-placement-before-update.patch deleted file mode 100644 index fd8a74807c..0000000000 --- a/patches/server/0372-Validate-tripwire-hook-placement-before-update.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -index a5f8c7d9d9998eebce7f15e01c157651b9831516..4a516828e5c6abd63511ee7c93fcff11203cf8d0 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -175,6 +175,7 @@ public class TripWireHookBlock extends Block { - - this.emitState(world, pos, flag4, flag5, flag2, flag3); - if (!beingRemoved) { -+ if (world.getBlockState(pos).getBlock() == Blocks.TRIPWIRE_HOOK) // Paper - validate - world.setBlock(pos, (BlockState) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3); - if (flag1) { - this.notifyNeighbors(world, pos, enumdirection); diff --git a/patches/server/0373-Add-option-to-allow-iron-golems-to-spawn-in-air.patch b/patches/server/0373-Add-option-to-allow-iron-golems-to-spawn-in-air.patch deleted file mode 100644 index f058d157d0..0000000000 --- a/patches/server/0373-Add-option-to-allow-iron-golems-to-spawn-in-air.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -index d2864615588aadf81ac9763ba402ede0c1eebb23..e73acfa2f5a4066fa1beee1758082a2fe97a43b3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -@@ -334,7 +334,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - BlockPos blockposition1 = blockposition.below(); - BlockState iblockdata = world.getBlockState(blockposition1); - -- if (!iblockdata.entityCanStandOn(world, blockposition1, this)) { -+ if (!iblockdata.entityCanStandOn(world, blockposition1, this) && !level.paperConfig().entities.spawning.ironGolemsCanSpawnInAir) { // Paper - return false; - } else { - for (int i = 1; i < 3; ++i) { diff --git a/patches/server/0373-Configurable-chance-of-villager-zombie-infection.patch b/patches/server/0373-Configurable-chance-of-villager-zombie-infection.patch new file mode 100644 index 0000000000..ce51b7964c --- /dev/null +++ b/patches/server/0373-Configurable-chance-of-villager-zombie-infection.patch @@ -0,0 +1,30 @@ +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/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index bf5f4fe68a942dff8c2e2ad4735a529b5a6353e8..3c3095e7e684079bcba0ea5a6b44c8fe2a3f47c4 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -451,10 +451,14 @@ public class Zombie extends Monster { + public boolean wasKilled(ServerLevel world, LivingEntity other) { + boolean flag = super.wasKilled(world, other); + +- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) { +- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { ++ // Paper start ++ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != 0.0 && (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 || world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) { ++ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance == -1.0 && world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { + return flag; + } ++ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > level.paperConfig().entities.behavior.zombieVillagerInfectionChance) { ++ return flag; ++ } // Paper end + + Villager entityvillager = (Villager) other; + // CraftBukkit start diff --git a/patches/server/0374-Configurable-chance-of-villager-zombie-infection.patch b/patches/server/0374-Configurable-chance-of-villager-zombie-infection.patch deleted file mode 100644 index ce51b7964c..0000000000 --- a/patches/server/0374-Configurable-chance-of-villager-zombie-infection.patch +++ /dev/null @@ -1,30 +0,0 @@ -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/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index bf5f4fe68a942dff8c2e2ad4735a529b5a6353e8..3c3095e7e684079bcba0ea5a6b44c8fe2a3f47c4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -451,10 +451,14 @@ public class Zombie extends Monster { - public boolean wasKilled(ServerLevel world, LivingEntity other) { - boolean flag = super.wasKilled(world, other); - -- if ((world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) { -- if (world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { -+ // Paper start -+ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != 0.0 && (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 || world.getDifficulty() == Difficulty.NORMAL || world.getDifficulty() == Difficulty.HARD) && other instanceof Villager) { -+ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance == -1.0 && world.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { - return flag; - } -+ if (level.paperConfig().entities.behavior.zombieVillagerInfectionChance != -1.0 && (this.random.nextDouble() * 100.0) > level.paperConfig().entities.behavior.zombieVillagerInfectionChance) { -+ return flag; -+ } // Paper end - - Villager entityvillager = (Villager) other; - // CraftBukkit start diff --git a/patches/server/0374-Optimise-Chunk-getFluid.patch b/patches/server/0374-Optimise-Chunk-getFluid.patch new file mode 100644 index 0000000000..670a1cca43 --- /dev/null +++ b/patches/server/0374-Optimise-Chunk-getFluid.patch @@ -0,0 +1,61 @@ +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/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 004c8abeafa53cdc3efa0f45742132e3ad492d70..ebe17598d3ef17cc5f0e99b876ed67936d46060d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -381,18 +381,20 @@ public class LevelChunk extends ChunkAccess { + } + + public FluidState getFluidState(int x, int y, int z) { +- try { +- int l = this.getSectionIndex(y); +- +- if (l >= 0 && l < this.sections.length) { +- LevelChunkSection chunksection = this.sections[l]; ++ // try { // Paper - remove try catch ++ // Paper start - reduce the number of ops in this call ++ int index = this.getSectionIndex(y); ++ if (index >= 0 && index < this.sections.length) { ++ LevelChunkSection chunksection = this.sections[index]; + + if (!chunksection.hasOnlyAir()) { +- return chunksection.getFluidState(x & 15, y & 15, z & 15); ++ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); ++ // Paper end + } + } + + return Fluids.EMPTY.defaultFluidState(); ++ /* // Paper - remove try catch + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); + CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); +@@ -402,6 +404,7 @@ public class LevelChunk extends ChunkAccess { + }); + throw new ReportedException(crashreport); + } ++ */ // Paper - remove try catch + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 785fbcf9bafcdec1c5be213de3d8512690023415..066874d27495dcaa3dea254b7328257e46920357 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -54,7 +54,7 @@ public class LevelChunkSection { + } + + public FluidState getFluidState(int x, int y, int z) { +- return ((BlockState) this.states.get(x, y, z)).getFluidState(); ++ return this.states.get(x, y, z).getFluidState(); // 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 acquire() { diff --git a/patches/server/0375-Optimise-Chunk-getFluid.patch b/patches/server/0375-Optimise-Chunk-getFluid.patch deleted file mode 100644 index 670a1cca43..0000000000 --- a/patches/server/0375-Optimise-Chunk-getFluid.patch +++ /dev/null @@ -1,61 +0,0 @@ -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/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 004c8abeafa53cdc3efa0f45742132e3ad492d70..ebe17598d3ef17cc5f0e99b876ed67936d46060d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -381,18 +381,20 @@ public class LevelChunk extends ChunkAccess { - } - - public FluidState getFluidState(int x, int y, int z) { -- try { -- int l = this.getSectionIndex(y); -- -- if (l >= 0 && l < this.sections.length) { -- LevelChunkSection chunksection = this.sections[l]; -+ // try { // Paper - remove try catch -+ // Paper start - reduce the number of ops in this call -+ int index = this.getSectionIndex(y); -+ if (index >= 0 && index < this.sections.length) { -+ LevelChunkSection chunksection = this.sections[index]; - - if (!chunksection.hasOnlyAir()) { -- return chunksection.getFluidState(x & 15, y & 15, z & 15); -+ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState(); -+ // Paper end - } - } - - return Fluids.EMPTY.defaultFluidState(); -+ /* // Paper - remove try catch - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got"); -@@ -402,6 +404,7 @@ public class LevelChunk extends ChunkAccess { - }); - throw new ReportedException(crashreport); - } -+ */ // Paper - remove try catch - } - - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 785fbcf9bafcdec1c5be213de3d8512690023415..066874d27495dcaa3dea254b7328257e46920357 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -54,7 +54,7 @@ public class LevelChunkSection { - } - - public FluidState getFluidState(int x, int y, int z) { -- return ((BlockState) this.states.get(x, y, z)).getFluidState(); -+ return this.states.get(x, y, z).getFluidState(); // 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 acquire() { diff --git a/patches/server/0375-Set-spigots-verbose-world-setting-to-false-by-def.patch b/patches/server/0375-Set-spigots-verbose-world-setting-to-false-by-def.patch new file mode 100644 index 0000000000..45ea278818 --- /dev/null +++ b/patches/server/0375-Set-spigots-verbose-world-setting-to-false-by-def.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +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 9c9723e13b5440d4803a7268057d63cbdc973b77..40984144a062230fd45cc6c707b03e5cd7d89efc 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 = this.getBoolean( "verbose", true ); ++ this.verbose = this.getBoolean( "verbose", false ); // Paper + + this.log( "-------- World Settings For [" + this.worldName + "] --------" ); + SpigotConfig.readConfig( SpigotWorldConfig.class, this ); diff --git a/patches/server/0376-Add-tick-times-API-and-mspt-command.patch b/patches/server/0376-Add-tick-times-API-and-mspt-command.patch new file mode 100644 index 0000000000..b45a036e40 --- /dev/null +++ b/patches/server/0376-Add-tick-times-API-and-mspt-command.patch @@ -0,0 +1,207 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 5 Apr 2020 22:23:14 -0500 +Subject: [PATCH] Add tick times API and /mspt command + + +diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java +@@ -0,0 +1,102 @@ ++package io.papermc.paper.command; ++ ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Location; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++ ++import java.text.DecimalFormat; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GOLD; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++ ++@DefaultQualifier(NonNull.class) ++public final class MSPTCommand extends Command { ++ private static final DecimalFormat DF = new DecimalFormat("########0.0"); ++ private static final Component SLASH = text("/"); ++ ++ public MSPTCommand(final String name) { ++ super(name); ++ this.description = "View server tick times"; ++ this.usageMessage = "/mspt"; ++ this.setPermission("bukkit.command.mspt"); ++ } ++ ++ @Override ++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String commandLabel, String[] args) { ++ if (!testPermission(sender)) return true; ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ List times = new ArrayList<>(); ++ times.addAll(eval(server.tickTimes5s.getTimes())); ++ times.addAll(eval(server.tickTimes10s.getTimes())); ++ times.addAll(eval(server.tickTimes60s.getTimes())); ++ ++ sender.sendMessage(text().content("Server tick times ").color(GOLD) ++ .append(text().color(YELLOW) ++ .append( ++ text("("), ++ text("avg", GRAY), ++ text("/"), ++ text("min", GRAY), ++ text("/"), ++ text("max", GRAY), ++ text(")") ++ ) ++ ).append( ++ text(" from last 5s"), ++ text(",", GRAY), ++ text(" 10s"), ++ text(",", GRAY), ++ text(" 1m"), ++ text(":", YELLOW) ++ ) ++ ); ++ sender.sendMessage(text().content("â—´ ").color(GOLD) ++ .append(text().color(GRAY) ++ .append( ++ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), ++ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), ++ times.get(6), SLASH, times.get(7), SLASH, times.get(8) ++ ) ++ ) ++ ); ++ return true; ++ } ++ ++ private static List eval(long[] times) { ++ long min = Integer.MAX_VALUE; ++ long max = 0L; ++ long total = 0L; ++ for (long value : times) { ++ if (value > 0L && value < min) min = value; ++ if (value > max) max = value; ++ total += value; ++ } ++ double avgD = ((double) total / (double) times.length) * 1.0E-6D; ++ double minD = ((double) min) * 1.0E-6D; ++ double maxD = ((double) max) * 1.0E-6D; ++ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); ++ } ++ ++ private static Component getColor(double avg) { ++ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java +index 6a00f3d38da8107825ab1d405f337fd077b09f72..d31b5ed47cffc61c90c926a0cd2005b72ebddfc5 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommands.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommands.java +@@ -17,6 +17,7 @@ public final class PaperCommands { + private static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("paper", new PaperCommand("paper")); ++ COMMANDS.put("mspt", new MSPTCommand("mspt")); + } + + public static void registerCommands(final MinecraftServer server) { +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 27f19abc22e295a5480dbed5df86f5d885ad3b73..12ca13f5b0556c53fd36d638ee4fa854b89ee8ec 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -237,6 +237,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -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 9c9723e13b5440d4803a7268057d63cbdc973b77..40984144a062230fd45cc6c707b03e5cd7d89efc 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 = this.getBoolean( "verbose", true ); -+ this.verbose = this.getBoolean( "verbose", false ); // Paper - - this.log( "-------- World Settings For [" + this.worldName + "] --------" ); - SpigotConfig.readConfig( SpigotWorldConfig.class, this ); diff --git a/patches/server/0377-Add-tick-times-API-and-mspt-command.patch b/patches/server/0377-Add-tick-times-API-and-mspt-command.patch deleted file mode 100644 index b45a036e40..0000000000 --- a/patches/server/0377-Add-tick-times-API-and-mspt-command.patch +++ /dev/null @@ -1,207 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 5 Apr 2020 22:23:14 -0500 -Subject: [PATCH] Add tick times API and /mspt command - - -diff --git a/src/main/java/io/papermc/paper/command/MSPTCommand.java b/src/main/java/io/papermc/paper/command/MSPTCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b5293b0c696ef21d0101493ffa41b60bf0bc86b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/MSPTCommand.java -@@ -0,0 +1,102 @@ -+package io.papermc.paper.command; -+ -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Location; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+ -+import java.text.DecimalFormat; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Collections; -+import java.util.List; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GOLD; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+ -+@DefaultQualifier(NonNull.class) -+public final class MSPTCommand extends Command { -+ private static final DecimalFormat DF = new DecimalFormat("########0.0"); -+ private static final Component SLASH = text("/"); -+ -+ public MSPTCommand(final String name) { -+ super(name); -+ this.description = "View server tick times"; -+ this.usageMessage = "/mspt"; -+ this.setPermission("bukkit.command.mspt"); -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ -+ List times = new ArrayList<>(); -+ times.addAll(eval(server.tickTimes5s.getTimes())); -+ times.addAll(eval(server.tickTimes10s.getTimes())); -+ times.addAll(eval(server.tickTimes60s.getTimes())); -+ -+ sender.sendMessage(text().content("Server tick times ").color(GOLD) -+ .append(text().color(YELLOW) -+ .append( -+ text("("), -+ text("avg", GRAY), -+ text("/"), -+ text("min", GRAY), -+ text("/"), -+ text("max", GRAY), -+ text(")") -+ ) -+ ).append( -+ text(" from last 5s"), -+ text(",", GRAY), -+ text(" 10s"), -+ text(",", GRAY), -+ text(" 1m"), -+ text(":", YELLOW) -+ ) -+ ); -+ sender.sendMessage(text().content("â—´ ").color(GOLD) -+ .append(text().color(GRAY) -+ .append( -+ times.get(0), SLASH, times.get(1), SLASH, times.get(2), text(", ", YELLOW), -+ times.get(3), SLASH, times.get(4), SLASH, times.get(5), text(", ", YELLOW), -+ times.get(6), SLASH, times.get(7), SLASH, times.get(8) -+ ) -+ ) -+ ); -+ return true; -+ } -+ -+ private static List eval(long[] times) { -+ long min = Integer.MAX_VALUE; -+ long max = 0L; -+ long total = 0L; -+ for (long value : times) { -+ if (value > 0L && value < min) min = value; -+ if (value > max) max = value; -+ total += value; -+ } -+ double avgD = ((double) total / (double) times.length) * 1.0E-6D; -+ double minD = ((double) min) * 1.0E-6D; -+ double maxD = ((double) max) * 1.0E-6D; -+ return Arrays.asList(getColor(avgD), getColor(minD), getColor(maxD)); -+ } -+ -+ private static Component getColor(double avg) { -+ return text(DF.format(avg), avg >= 50 ? RED : avg >= 40 ? YELLOW : GREEN); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java -index 6a00f3d38da8107825ab1d405f337fd077b09f72..d31b5ed47cffc61c90c926a0cd2005b72ebddfc5 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommands.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java -@@ -17,6 +17,7 @@ public final class PaperCommands { - private static final Map COMMANDS = new HashMap<>(); - static { - COMMANDS.put("paper", new PaperCommand("paper")); -+ COMMANDS.put("mspt", new MSPTCommand("mspt")); - } - - public static void registerCommands(final MinecraftServer server) { -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 27f19abc22e295a5480dbed5df86f5d885ad3b73..12ca13f5b0556c53fd36d638ee4fa854b89ee8ec 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -237,6 +237,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Fri, 10 Apr 2020 21:24:12 -0400 +Subject: [PATCH] Expose MinecraftServer#isRunning + +This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a6f40cba559a4ca8c91b4daca0867f3be9cc94e6..a35501ea43bf3589b346b1e684c318b44ca57977 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2678,5 +2678,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/0378-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0378-Add-Raw-Byte-ItemStack-Serialization.patch new file mode 100644 index 0000000000..fb799bc57d --- /dev/null +++ b/patches/server/0378-Add-Raw-Byte-ItemStack-Serialization.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Thu, 30 Apr 2020 16:56:54 +0200 +Subject: [PATCH] Add Raw Byte ItemStack Serialization + +Serializes using NBT which is safer for server data migrations than bukkits format. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 5893b764d3fceccef8704f1f90a5c826d6012166..6fefc65c6f9364d71e4e410972dfd79d97fdae3e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -431,6 +431,53 @@ 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"); ++ ++ return serializeNbtToBytes((item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(new CompoundTag())); ++ } ++ ++ @Override ++ public ItemStack deserializeItem(byte[] data) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ CompoundTag compound = deserializeNbtFromBytes(data); ++ int dataVersion = compound.getInt("DataVersion"); ++ Dynamic converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); ++ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue())); ++ } ++ ++ private byte[] serializeNbtToBytes(CompoundTag compound) { ++ compound.putInt("DataVersion", getDataVersion()); ++ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); ++ try { ++ net.minecraft.nbt.NbtIo.writeCompressed( ++ compound, ++ outputStream ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return outputStream.toByteArray(); ++ } ++ ++ private CompoundTag deserializeNbtFromBytes(byte[] data) { ++ CompoundTag compound; ++ try { ++ compound = net.minecraft.nbt.NbtIo.readCompressed( ++ new java.io.ByteArrayInputStream(data) ++ ); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ int dataVersion = compound.getInt("DataVersion"); ++ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); ++ return compound; ++ } + // Paper end + + /** diff --git a/patches/server/0378-Expose-MinecraftServer-isRunning.patch b/patches/server/0378-Expose-MinecraftServer-isRunning.patch deleted file mode 100644 index 4bacea7d49..0000000000 --- a/patches/server/0378-Expose-MinecraftServer-isRunning.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 10 Apr 2020 21:24:12 -0400 -Subject: [PATCH] Expose MinecraftServer#isRunning - -This allows for plugins to detect if the server is actually turning off in onDisable rather than just plugins reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index a6f40cba559a4ca8c91b4daca0867f3be9cc94e6..a35501ea43bf3589b346b1e684c318b44ca57977 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2678,5 +2678,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/0379-Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/0379-Add-Raw-Byte-ItemStack-Serialization.patch deleted file mode 100644 index fb799bc57d..0000000000 --- a/patches/server/0379-Add-Raw-Byte-ItemStack-Serialization.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Thu, 30 Apr 2020 16:56:54 +0200 -Subject: [PATCH] Add Raw Byte ItemStack Serialization - -Serializes using NBT which is safer for server data migrations than bukkits format. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 5893b764d3fceccef8704f1f90a5c826d6012166..6fefc65c6f9364d71e4e410972dfd79d97fdae3e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -431,6 +431,53 @@ 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"); -+ -+ return serializeNbtToBytes((item instanceof CraftItemStack ? ((CraftItemStack) item).handle : CraftItemStack.asNMSCopy(item)).save(new CompoundTag())); -+ } -+ -+ @Override -+ public ItemStack deserializeItem(byte[] data) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ CompoundTag compound = deserializeNbtFromBytes(data); -+ int dataVersion = compound.getInt("DataVersion"); -+ Dynamic converted = DataFixers.getDataFixer().update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); -+ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue())); -+ } -+ -+ private byte[] serializeNbtToBytes(CompoundTag compound) { -+ compound.putInt("DataVersion", getDataVersion()); -+ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); -+ try { -+ net.minecraft.nbt.NbtIo.writeCompressed( -+ compound, -+ outputStream -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ return outputStream.toByteArray(); -+ } -+ -+ private CompoundTag deserializeNbtFromBytes(byte[] data) { -+ CompoundTag compound; -+ try { -+ compound = net.minecraft.nbt.NbtIo.readCompressed( -+ new java.io.ByteArrayInputStream(data) -+ ); -+ } catch (IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ int dataVersion = compound.getInt("DataVersion"); -+ Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); -+ return compound; -+ } - // Paper end - - /** diff --git a/patches/server/0379-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server/0379-Pillager-patrol-spawn-settings-and-per-player-option.patch new file mode 100644 index 0000000000..0b94225be1 --- /dev/null +++ b/patches/server/0379-Pillager-patrol-spawn-settings-and-per-player-option.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sat, 1 Feb 2020 16:50:39 +0100 +Subject: [PATCH] Pillager patrol spawn settings and per player options + +This adds config options for defining the spawn chance, spawn delay and +spawn start day as well as toggles for handling the spawn delay and +start day per player. (Based on the time played statistic) +When not per player it will use the Vanilla mechanic of one delay per +world and the world age for the start day. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 582e08eaeee8fb6c777ad451ccc0436b02cac000..71608048e941f83516631e2a3b6bf9f672d9f127 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -232,6 +232,7 @@ public class ServerPlayer extends Player { + public boolean wonGame; + 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.ClientboundSetHealthPacket queuedHealthUpdatePacket; +diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +index e5918fa3be107ac3a2fc8831fd78733a7506730a..a908652f1ebb426d265ef614746f70cd1e538268 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java +@@ -25,7 +25,7 @@ public class PatrolSpawner implements CustomSpawner { + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { +- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper ++ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper + if (!spawnMonsters) { + return 0; + } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { +@@ -33,23 +33,51 @@ public class PatrolSpawner implements CustomSpawner { + } else { + RandomSource randomsource = world.random; + +- --this.nextTick; +- if (this.nextTick > 0) { ++ // Paper start - Patrol settings ++ // Random player selection moved up for per player spawning and configuration ++ int j = world.players().size(); ++ if (j < 1) { + return 0; ++ } ++ ++ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); ++ if (entityhuman.isSpectator()) { ++ return 0; ++ } ++ ++ int patrolSpawnDelay; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ --entityhuman.patrolSpawnDelay; ++ patrolSpawnDelay = entityhuman.patrolSpawnDelay; + } else { +- this.nextTick += 12000 + randomsource.nextInt(1200); +- long i = world.getDayTime() / 24000L; ++ this.nextTick--; ++ patrolSpawnDelay = this.nextTick; ++ } ++ ++ if (patrolSpawnDelay > 0) { ++ return 0; ++ } else { ++ long days; ++ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { ++ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang ++ } else { ++ days = world.getDayTime() / 24000L; ++ } ++ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { ++ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } else { ++ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); ++ } + +- if (i >= 5L && world.isDay()) { +- if (randomsource.nextInt(5) != 0) { ++ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { ++ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { ++ // Paper end + return 0; + } else { +- int j = world.players().size(); + + if (j < 1) { + return 0; + } else { +- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); + + if (entityhuman.isSpectator()) { + return 0; diff --git a/patches/server/0380-Pillager-patrol-spawn-settings-and-per-player-option.patch b/patches/server/0380-Pillager-patrol-spawn-settings-and-per-player-option.patch deleted file mode 100644 index 0b94225be1..0000000000 --- a/patches/server/0380-Pillager-patrol-spawn-settings-and-per-player-option.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sat, 1 Feb 2020 16:50:39 +0100 -Subject: [PATCH] Pillager patrol spawn settings and per player options - -This adds config options for defining the spawn chance, spawn delay and -spawn start day as well as toggles for handling the spawn delay and -start day per player. (Based on the time played statistic) -When not per player it will use the Vanilla mechanic of one delay per -world and the world age for the start day. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 582e08eaeee8fb6c777ad451ccc0436b02cac000..71608048e941f83516631e2a3b6bf9f672d9f127 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -232,6 +232,7 @@ public class ServerPlayer extends Player { - public boolean wonGame; - 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.ClientboundSetHealthPacket queuedHealthUpdatePacket; -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -index e5918fa3be107ac3a2fc8831fd78733a7506730a..a908652f1ebb426d265ef614746f70cd1e538268 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PatrolSpawner.java -@@ -25,7 +25,7 @@ public class PatrolSpawner implements CustomSpawner { - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -- if (world.paperConfig().entities.behavior.pillagerPatrols.disable) return 0; // Paper -+ if (world.paperConfig().entities.behavior.pillagerPatrols.disable || world.paperConfig().entities.behavior.pillagerPatrols.spawnChance == 0) return 0; // Paper - if (!spawnMonsters) { - return 0; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_DO_PATROL_SPAWNING)) { -@@ -33,23 +33,51 @@ public class PatrolSpawner implements CustomSpawner { - } else { - RandomSource randomsource = world.random; - -- --this.nextTick; -- if (this.nextTick > 0) { -+ // Paper start - Patrol settings -+ // Random player selection moved up for per player spawning and configuration -+ int j = world.players().size(); -+ if (j < 1) { - return 0; -+ } -+ -+ net.minecraft.server.level.ServerPlayer entityhuman = world.players().get(randomsource.nextInt(j)); -+ if (entityhuman.isSpectator()) { -+ return 0; -+ } -+ -+ int patrolSpawnDelay; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ --entityhuman.patrolSpawnDelay; -+ patrolSpawnDelay = entityhuman.patrolSpawnDelay; - } else { -- this.nextTick += 12000 + randomsource.nextInt(1200); -- long i = world.getDayTime() / 24000L; -+ this.nextTick--; -+ patrolSpawnDelay = this.nextTick; -+ } -+ -+ if (patrolSpawnDelay > 0) { -+ return 0; -+ } else { -+ long days; -+ if (world.paperConfig().entities.behavior.pillagerPatrols.start.perPlayer) { -+ days = entityhuman.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.PLAY_TIME)) / 24000L; // PLAY_ONE_MINUTE is actually counting in ticks, a misnomer by Mojang -+ } else { -+ days = world.getDayTime() / 24000L; -+ } -+ if (world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.perPlayer) { -+ entityhuman.patrolSpawnDelay += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } else { -+ this.nextTick += world.paperConfig().entities.behavior.pillagerPatrols.spawnDelay.ticks + randomsource.nextInt(1200); -+ } - -- if (i >= 5L && world.isDay()) { -- if (randomsource.nextInt(5) != 0) { -+ if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { -+ if (randomsource.nextDouble() >= world.paperConfig().entities.behavior.pillagerPatrols.spawnChance) { -+ // Paper end - return 0; - } else { -- int j = world.players().size(); - - if (j < 1) { - return 0; - } else { -- Player entityhuman = (Player) world.players().get(randomsource.nextInt(j)); - - if (entityhuman.isSpectator()) { - return 0; diff --git a/patches/server/0380-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0380-Remote-Connections-shouldn-t-hold-up-shutdown.patch new file mode 100644 index 0000000000..74098cbac5 --- /dev/null +++ b/patches/server/0380-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 115685012743e775ca3fa031e8a91b6cd2874236..637846c2dda1cf27c194ca4f16da454a62dc3f4b 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -397,11 +397,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (this.rconThread != null) { +- this.rconThread.stop(); ++ //this.remoteControlListener.b(); // Paper - don't wait for remote connections + } + + if (this.queryThreadGs4 != null) { +- this.queryThreadGs4.stop(); ++ //this.remoteStatusListener.b(); // Paper - don't wait for remote connections + } + + System.exit(0); // CraftBukkit diff --git a/patches/server/0381-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server/0381-Do-not-allow-bees-to-load-chunks-for-beehives.patch new file mode 100644 index 0000000000..aa61dfee66 --- /dev/null +++ b/patches/server/0381-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/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index d94f045d7fe928c256c5d3e1af02ac73d7897f5a..d13f3460644f635ded96bf92ddf9ecf8984c8e47 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -409,6 +409,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + if (this.hivePos == null) { + return false; + } else { ++ if (!this.level.isLoadedAndInBounds(hivePos)) return false; // Paper + BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); + + return tileentity instanceof BeehiveBlockEntity && ((BeehiveBlockEntity) tileentity).isFireNearby(); +@@ -442,6 +443,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + } + + private boolean doesHiveHaveSpace(BlockPos pos) { ++ if (!this.level.isLoadedAndInBounds(pos)) return false; // Paper + BlockEntity tileentity = this.level.getBlockEntity(pos); + + return tileentity instanceof BeehiveBlockEntity ? !((BeehiveBlockEntity) tileentity).isFull() : false; +@@ -921,6 +923,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + @Override + public boolean canBeeUse() { + if (Bee.this.hasHive() && Bee.this.wantsToEnterHive() && Bee.this.hivePos.closerToCenterThan(Bee.this.position(), 2.0D)) { ++ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return false; // Paper + BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); + + if (tileentity instanceof BeehiveBlockEntity) { +@@ -944,6 +947,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + @Override + public void start() { ++ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return; // Paper + BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); + + if (tileentity instanceof BeehiveBlockEntity) { diff --git a/patches/server/0381-Remote-Connections-shouldn-t-hold-up-shutdown.patch b/patches/server/0381-Remote-Connections-shouldn-t-hold-up-shutdown.patch deleted file mode 100644 index 74098cbac5..0000000000 --- a/patches/server/0381-Remote-Connections-shouldn-t-hold-up-shutdown.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 31 Mar 2020 03:50:42 -0400 -Subject: [PATCH] Remote Connections shouldn't hold up shutdown - -Bugs in the connection logic appears to leave stale connections even, preventing shutdown - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 115685012743e775ca3fa031e8a91b6cd2874236..637846c2dda1cf27c194ca4f16da454a62dc3f4b 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -397,11 +397,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - } - - if (this.rconThread != null) { -- this.rconThread.stop(); -+ //this.remoteControlListener.b(); // Paper - don't wait for remote connections - } - - if (this.queryThreadGs4 != null) { -- this.queryThreadGs4.stop(); -+ //this.remoteStatusListener.b(); // Paper - don't wait for remote connections - } - - System.exit(0); // CraftBukkit diff --git a/patches/server/0382-Do-not-allow-bees-to-load-chunks-for-beehives.patch b/patches/server/0382-Do-not-allow-bees-to-load-chunks-for-beehives.patch deleted file mode 100644 index aa61dfee66..0000000000 --- a/patches/server/0382-Do-not-allow-bees-to-load-chunks-for-beehives.patch +++ /dev/null @@ -1,42 +0,0 @@ -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/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index d94f045d7fe928c256c5d3e1af02ac73d7897f5a..d13f3460644f635ded96bf92ddf9ecf8984c8e47 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -409,6 +409,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - if (this.hivePos == null) { - return false; - } else { -+ if (!this.level.isLoadedAndInBounds(hivePos)) return false; // Paper - BlockEntity tileentity = this.level.getBlockEntity(this.hivePos); - - return tileentity instanceof BeehiveBlockEntity && ((BeehiveBlockEntity) tileentity).isFireNearby(); -@@ -442,6 +443,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } - - private boolean doesHiveHaveSpace(BlockPos pos) { -+ if (!this.level.isLoadedAndInBounds(pos)) return false; // Paper - BlockEntity tileentity = this.level.getBlockEntity(pos); - - return tileentity instanceof BeehiveBlockEntity ? !((BeehiveBlockEntity) tileentity).isFull() : false; -@@ -921,6 +923,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - @Override - public boolean canBeeUse() { - if (Bee.this.hasHive() && Bee.this.wantsToEnterHive() && Bee.this.hivePos.closerToCenterThan(Bee.this.position(), 2.0D)) { -+ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return false; // Paper - BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); - - if (tileentity instanceof BeehiveBlockEntity) { -@@ -944,6 +947,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - @Override - public void start() { -+ if (!Bee.this.level.isLoadedAndInBounds(Bee.this.hivePos)) return; // Paper - BlockEntity tileentity = Bee.this.level.getBlockEntity(Bee.this.hivePos); - - if (tileentity instanceof BeehiveBlockEntity) { diff --git a/patches/server/0382-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0382-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch new file mode 100644 index 0000000000..0fcb7a0f21 --- /dev/null +++ b/patches/server/0382-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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 9e940920f6ff6a43d7162d965936c171c55ed570..72af82374f47a1effc3d2f45beee1f611b4bb4b6 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1754,6 +1754,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + public void addEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot ++ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server ++ if (!entity.valid || entity.level != this.level || this.entityMap.containsKey(entity.getId())) { ++ new Throwable("[ERROR] Illegal PlayerChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : "")) ++ .printStackTrace(); ++ return; ++ } ++ // Paper end + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); + int i = entitytypes.clientTrackingRange() * 16; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index ed8014ed36f8354aa1ae07689e9315c0c0d8867a..f5a9edf47230a1552a36ee5de4cb7560ea9ba7c4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2294,7 +2294,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public void onTrackingStart(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot +- ServerLevel.this.getChunkSource().addEntity(entity); ++ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - moved down below valid=true + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -2328,6 +2328,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); + entity.valid = true; // CraftBukkit ++ ServerLevel.this.getChunkSource().addEntity(entity); + // Paper start - Set origin location when the entity is being added to the world + if (entity.getOriginVector() == null) { + entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0383-Don-t-tick-dead-players.patch b/patches/server/0383-Don-t-tick-dead-players.patch new file mode 100644 index 0000000000..183a646d3b --- /dev/null +++ b/patches/server/0383-Don-t-tick-dead-players.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 17:16:48 -0400 +Subject: [PATCH] Don't tick dead players + +Causes sync chunk loads and who knows what all else. +This is safe because Spectators are skipped in unloaded chunks too in vanilla. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 71608048e941f83516631e2a3b6bf9f672d9f127..eae65f79bd1b286f35955bf7a0a5a116ecd12520 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -668,7 +668,7 @@ public class ServerPlayer extends Player { + + public void doTick() { + try { +- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { ++ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + diff --git a/patches/server/0383-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0383-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch deleted file mode 100644 index 0fcb7a0f21..0000000000 --- a/patches/server/0383-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 01:42:39 -0400 -Subject: [PATCH] Prevent Double PlayerChunkMap adds crashing server - -Suspected case would be around the technique used in .stopRiding -Stack will identify any causer of this and warn instead of crashing. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 9e940920f6ff6a43d7162d965936c171c55ed570..72af82374f47a1effc3d2f45beee1f611b4bb4b6 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1754,6 +1754,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - public void addEntity(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot -+ // Paper start - ignore and warn about illegal addEntity calls instead of crashing server -+ if (!entity.valid || entity.level != this.level || this.entityMap.containsKey(entity.getId())) { -+ new Throwable("[ERROR] Illegal PlayerChunkMap::addEntity for world " + this.level.getWorld().getName() -+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : "")) -+ .printStackTrace(); -+ return; -+ } -+ // Paper end - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index ed8014ed36f8354aa1ae07689e9315c0c0d8867a..f5a9edf47230a1552a36ee5de4cb7560ea9ba7c4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2294,7 +2294,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTrackingStart(Entity entity) { - org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot -- ServerLevel.this.getChunkSource().addEntity(entity); -+ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - moved down below valid=true - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; - -@@ -2328,6 +2328,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - entity.updateDynamicGameEventListener(DynamicGameEventListener::add); - entity.valid = true; // CraftBukkit -+ ServerLevel.this.getChunkSource().addEntity(entity); - // Paper start - Set origin location when the entity is being added to the world - if (entity.getOriginVector() == null) { - entity.setOrigin(entity.getBukkitEntity().getLocation()); diff --git a/patches/server/0384-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0384-Dead-Player-s-shouldn-t-be-able-to-move.patch new file mode 100644 index 0000000000..83aebd18e6 --- /dev/null +++ b/patches/server/0384-Dead-Player-s-shouldn-t-be-able-to-move.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 19:31:16 -0400 +Subject: [PATCH] Dead Player's shouldn't be able to move + +This fixes a lot of game state issues where packets were delayed for processing +due to 1.15's new queue but processed while dead. + +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index d308c671ec1c4440777bccf1609ceca6670b98a8..0960e5ecc25fad3eb46a871c2749dd176b812460 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1172,7 +1172,7 @@ public abstract class Player extends LivingEntity { + + @Override + protected boolean isImmobile() { +- return super.isImmobile() || this.isSleeping(); ++ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... + } + + @Override diff --git a/patches/server/0384-Don-t-tick-dead-players.patch b/patches/server/0384-Don-t-tick-dead-players.patch deleted file mode 100644 index 183a646d3b..0000000000 --- a/patches/server/0384-Don-t-tick-dead-players.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 17:16:48 -0400 -Subject: [PATCH] Don't tick dead players - -Causes sync chunk loads and who knows what all else. -This is safe because Spectators are skipped in unloaded chunks too in vanilla. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 71608048e941f83516631e2a3b6bf9f672d9f127..eae65f79bd1b286f35955bf7a0a5a116ecd12520 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -668,7 +668,7 @@ public class ServerPlayer extends Player { - - public void doTick() { - try { -- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { -+ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } - diff --git a/patches/server/0385-Dead-Player-s-shouldn-t-be-able-to-move.patch b/patches/server/0385-Dead-Player-s-shouldn-t-be-able-to-move.patch deleted file mode 100644 index 83aebd18e6..0000000000 --- a/patches/server/0385-Dead-Player-s-shouldn-t-be-able-to-move.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 19:31:16 -0400 -Subject: [PATCH] Dead Player's shouldn't be able to move - -This fixes a lot of game state issues where packets were delayed for processing -due to 1.15's new queue but processed while dead. - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index d308c671ec1c4440777bccf1609ceca6670b98a8..0960e5ecc25fad3eb46a871c2749dd176b812460 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1172,7 +1172,7 @@ public abstract class Player extends LivingEntity { - - @Override - protected boolean isImmobile() { -- return super.isImmobile() || this.isSleeping(); -+ return super.isImmobile() || this.isSleeping() || this.isRemoved() || !valid; // Paper - player's who are dead or not in a world shouldn't move... - } - - @Override diff --git a/patches/server/0385-Optimize-Collision-to-not-load-chunks.patch b/patches/server/0385-Optimize-Collision-to-not-load-chunks.patch new file mode 100644 index 0000000000..e483706ced --- /dev/null +++ b/patches/server/0385-Optimize-Collision-to-not-load-chunks.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Thu, 2 Apr 2020 02:37:57 -0400 +Subject: [PATCH] Optimize Collision to not load chunks + +The collision code takes an AABB and generates a cuboid of checks rather +than a cylinder, so at high velocity this can generate a lot of chunk checks. + +Treat an unloaded chunk as a collision for entities, and also for players if +the "prevent moving into unloaded chunks" setting is enabled. + +If that serting is not enabled, collisions will be ignored for players, since +movement will load only the chunk the player enters anyways and avoids loading +massive amounts of surrounding chunks due to large AABB lookups. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index d362c9c83a73b5f49b54563b423c76c8ed78dfc6..256bd12178c8dca2889fbcedb9327cc4a1164cf5 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -797,6 +797,7 @@ public abstract class PlayerList { + entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + // CraftBukkit end + ++ worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper + while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 152c2f10a71c231a43a21e2016b040c785417589..a1f176432b366b5f106a32ca571d7ea617b19fd3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -236,6 +236,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper + + public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper ++ public boolean collisionLoadChunks = false; // Paper + private CraftEntity bukkitEntity; + + public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper +diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java +index 8390ce194ccc692139c0e870c16a7fb76ac8ba68..95e22c91bc701785f4804e5d4e0a6b420b9830fd 100644 +--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java ++++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java +@@ -66,22 +66,41 @@ public class BlockCollisions extends AbstractIterator { + protected VoxelShape computeNext() { + while(true) { + if (this.cursor.advance()) { +- int i = this.cursor.nextX(); +- int j = this.cursor.nextY(); +- int k = this.cursor.nextZ(); ++ int i = this.cursor.nextX(); final int x = i; // Paper ++ int j = this.cursor.nextY(); final int y = j; // Paper ++ int k = this.cursor.nextZ(); final int z = k; // Paper + int l = this.cursor.getNextType(); + if (l == 3) { + continue; + } ++ // Paper start - ensure we don't load chunks ++ final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; ++ boolean far = source != null && net.minecraft.server.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; ++ this.pos.set(x, y, z); + +- BlockGetter blockGetter = this.getChunk(i, k); +- if (blockGetter == null) { ++ BlockState blockState; ++ if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) { ++ BlockGetter blockGetter = this.getChunk(x, z); ++ if (blockGetter == null) { ++ continue; ++ } ++ blockState = blockGetter.getBlockState(this.pos); ++ } else if ((!far && source instanceof net.minecraft.server.level.ServerPlayer) || (source != null && source.collisionLoadChunks)) { ++ blockState = this.collisionGetter.getBlockState(this.pos); ++ } else { ++ blockState = this.collisionGetter.getBlockStateIfLoaded(this.pos); ++ } ++ ++ if (blockState == null) { ++ if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level.paperConfig().chunks.preventMovingIntoUnloadedChunks) { ++ return Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z))); ++ } ++ // Paper end + continue; + } + +- this.pos.set(i, j, k); +- BlockState blockState = blockGetter.getBlockState(this.pos); +- if (this.onlySuffocatingBlocks && !blockState.isSuffocating(blockGetter, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { ++ // Paper - moved up ++ if (/*this.onlySuffocatingBlocks && (!blockState.isSuffocating(blockGetter, this.pos)) ||*/ l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { // Paper - onlySuffocatingBlocks is only true on the client, so we don't care about it here + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java +index 4c373d6c8ddd9f5db88888cd8dbbfc24eb5df793..56d94c94fb0d4dc468bb5d69be655ddd5c6b5360 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java ++++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java +@@ -44,11 +44,13 @@ public interface CollisionGetter extends BlockGetter { + } + + default boolean noCollision(@Nullable Entity entity, AABB box) { ++ try { if (entity != null) entity.collisionLoadChunks = true; // Paper + for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { + if (!voxelShape.isEmpty()) { + return false; + } + } ++ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper + + if (!this.getEntityCollisions(entity, box).isEmpty()) { + return false; diff --git a/patches/server/0386-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0386-Don-t-move-existing-players-to-world-spawn.patch new file mode 100644 index 0000000000..0464df901c --- /dev/null +++ b/patches/server/0386-Don-t-move-existing-players-to-world-spawn.patch @@ -0,0 +1,46 @@ +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/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index eae65f79bd1b286f35955bf7a0a5a116ecd12520..61f97fab1e0e3d6c9206c397cecfff868cf0d9d2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -323,7 +323,7 @@ public class ServerPlayer extends Player { + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); + this.maxUpStep = 1.0F; +- this.fudgeSpawnLocation(world); ++ //this.fudgeSpawnLocation(world); // Paper - don't move to spawn on login, only first join + + this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + +@@ -556,7 +556,7 @@ public class ServerPlayer extends Player { + position = Vec3.atCenterOf(((ServerLevel) world).getSharedSpawnPos()); + } + this.level = world; +- this.setPos(position.x(), position.y(), position.z()); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet + } + this.gameMode.setLevel((ServerLevel) world); + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 256bd12178c8dca2889fbcedb9327cc4a1164cf5..d9175bcd1060dbaee90e50466da3c6d816fb614e 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -216,6 +216,8 @@ public abstract class PlayerList { + worldserver1 = worldserver; + } + ++ if (nbttagcompound == null) player.fudgeSpawnLocation(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... ++ + player.setLevel(worldserver1); + String s1 = "local"; + diff --git a/patches/server/0386-Optimize-Collision-to-not-load-chunks.patch b/patches/server/0386-Optimize-Collision-to-not-load-chunks.patch deleted file mode 100644 index df12155384..0000000000 --- a/patches/server/0386-Optimize-Collision-to-not-load-chunks.patch +++ /dev/null @@ -1,111 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 2 Apr 2020 02:37:57 -0400 -Subject: [PATCH] Optimize Collision to not load chunks - -The collision code takes an AABB and generates a cuboid of checks rather -than a cylinder, so at high velocity this can generate a lot of chunk checks. - -Treat an unloaded chunk as a collision for entities, and also for players if -the "prevent moving into unloaded chunks" setting is enabled. - -If that serting is not enabled, collisions will be ignored for players, since -movement will load only the chunk the player enters anyways and avoids loading -massive amounts of surrounding chunks due to large AABB lookups. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index d362c9c83a73b5f49b54563b423c76c8ed78dfc6..256bd12178c8dca2889fbcedb9327cc4a1164cf5 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -797,6 +797,7 @@ public abstract class PlayerList { - entityplayer1.forceSetPositionRotation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - // CraftBukkit end - -+ worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper - while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 185f6dd93f325b638289acd723c6cbbbedac80e1..001c1d55218671eaa9cee28ae42d756f352ff2fa 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -236,6 +236,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - - public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper -+ public boolean collisionLoadChunks = false; // Paper - private CraftEntity bukkitEntity; - - public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper -diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java -index 8390ce194ccc692139c0e870c16a7fb76ac8ba68..95e22c91bc701785f4804e5d4e0a6b420b9830fd 100644 ---- a/src/main/java/net/minecraft/world/level/BlockCollisions.java -+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java -@@ -66,22 +66,41 @@ public class BlockCollisions extends AbstractIterator { - protected VoxelShape computeNext() { - while(true) { - if (this.cursor.advance()) { -- int i = this.cursor.nextX(); -- int j = this.cursor.nextY(); -- int k = this.cursor.nextZ(); -+ int i = this.cursor.nextX(); final int x = i; // Paper -+ int j = this.cursor.nextY(); final int y = j; // Paper -+ int k = this.cursor.nextZ(); final int z = k; // Paper - int l = this.cursor.getNextType(); - if (l == 3) { - continue; - } -+ // Paper start - ensure we don't load chunks -+ final @Nullable Entity source = this.context instanceof net.minecraft.world.phys.shapes.EntityCollisionContext entityContext ? entityContext.getEntity() : null; -+ boolean far = source != null && net.minecraft.server.MCUtil.distanceSq(source.getX(), y, source.getZ(), x, y, z) > 14; -+ this.pos.set(x, y, z); - -- BlockGetter blockGetter = this.getChunk(i, k); -- if (blockGetter == null) { -+ BlockState blockState; -+ if (this.collisionGetter instanceof net.minecraft.server.level.WorldGenRegion) { -+ BlockGetter blockGetter = this.getChunk(x, z); -+ if (blockGetter == null) { -+ continue; -+ } -+ blockState = blockGetter.getBlockState(this.pos); -+ } else if ((!far && source instanceof net.minecraft.server.level.ServerPlayer) || (source != null && source.collisionLoadChunks)) { -+ blockState = this.collisionGetter.getBlockState(this.pos); -+ } else { -+ blockState = this.collisionGetter.getBlockStateIfLoaded(this.pos); -+ } -+ -+ if (blockState == null) { -+ if (!(source instanceof net.minecraft.server.level.ServerPlayer) || source.level.paperConfig().chunks.preventMovingIntoUnloadedChunks) { -+ return Shapes.create(far ? source.getBoundingBox() : new AABB(new BlockPos(x, y, z))); -+ } -+ // Paper end - continue; - } - -- this.pos.set(i, j, k); -- BlockState blockState = blockGetter.getBlockState(this.pos); -- if (this.onlySuffocatingBlocks && !blockState.isSuffocating(blockGetter, this.pos) || l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { -+ // Paper - moved up -+ if (/*this.onlySuffocatingBlocks && (!blockState.isSuffocating(blockGetter, this.pos)) ||*/ l == 1 && !blockState.hasLargeCollisionShape() || l == 2 && !blockState.is(Blocks.MOVING_PISTON)) { // Paper - onlySuffocatingBlocks is only true on the client, so we don't care about it here - continue; - } - -diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java -index 4c373d6c8ddd9f5db88888cd8dbbfc24eb5df793..56d94c94fb0d4dc468bb5d69be655ddd5c6b5360 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java -+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java -@@ -44,11 +44,13 @@ public interface CollisionGetter extends BlockGetter { - } - - default boolean noCollision(@Nullable Entity entity, AABB box) { -+ try { if (entity != null) entity.collisionLoadChunks = true; // Paper - for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { - if (!voxelShape.isEmpty()) { - return false; - } - } -+ } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper - - if (!this.getEntityCollisions(entity, box).isEmpty()) { - return false; diff --git a/patches/server/0387-Don-t-move-existing-players-to-world-spawn.patch b/patches/server/0387-Don-t-move-existing-players-to-world-spawn.patch deleted file mode 100644 index 0464df901c..0000000000 --- a/patches/server/0387-Don-t-move-existing-players-to-world-spawn.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 9 Apr 2020 21:20:33 -0400 -Subject: [PATCH] Don't move existing players to world spawn - -This can cause a nasty server lag the spawn chunks are not kept loaded -or they aren't finished loading yet, or if the world spawn radius is -larger than the keep loaded range. - -By skipping this, we avoid potential for a large spike on server start. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index eae65f79bd1b286f35955bf7a0a5a116ecd12520..61f97fab1e0e3d6c9206c397cecfff868cf0d9d2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -323,7 +323,7 @@ public class ServerPlayer extends Player { - this.stats = server.getPlayerList().getPlayerStats(this); - this.advancements = server.getPlayerList().getPlayerAdvancements(this); - this.maxUpStep = 1.0F; -- this.fudgeSpawnLocation(world); -+ //this.fudgeSpawnLocation(world); // Paper - don't move to spawn on login, only first join - - this.cachedSingleHashSet = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper - -@@ -556,7 +556,7 @@ public class ServerPlayer extends Player { - position = Vec3.atCenterOf(((ServerLevel) world).getSharedSpawnPos()); - } - this.level = world; -- this.setPos(position.x(), position.y(), position.z()); -+ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet - } - this.gameMode.setLevel((ServerLevel) world); - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 256bd12178c8dca2889fbcedb9327cc4a1164cf5..d9175bcd1060dbaee90e50466da3c6d816fb614e 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -216,6 +216,8 @@ public abstract class PlayerList { - worldserver1 = worldserver; - } - -+ if (nbttagcompound == null) player.fudgeSpawnLocation(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... -+ - player.setLevel(worldserver1); - String s1 = "local"; - diff --git a/patches/server/0387-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/patches/server/0387-Optimize-GoalSelector-Goal.Flag-Set-operations.patch new file mode 100644 index 0000000000..cf8d69b485 --- /dev/null +++ b/patches/server/0387-Optimize-GoalSelector-Goal.Flag-Set-operations.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:53:29 -0700 +Subject: [PATCH] Optimize GoalSelector Goal.Flag Set operations + +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/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..4379b9948f1eecfe6fd7dea98e298ad5f761019a 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -4,7 +4,8 @@ import java.util.EnumSet; + import net.minecraft.util.Mth; + + public abstract class Goal { +- private final EnumSet flags = EnumSet.noneOf(Goal.Flag.class); ++ private final EnumSet flags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + + public abstract boolean canUse(); + +@@ -30,8 +31,10 @@ public abstract class Goal { + } + + public void setFlags(EnumSet controls) { +- this.flags.clear(); +- this.flags.addAll(controls); ++ // Paper start - remove streams from pathfindergoalselector ++ this.goalTypes.clear(); ++ this.goalTypes.addAllUnchecked(controls); ++ // Paper end - remove streams from pathfindergoalselector + } + + @Override +@@ -39,8 +42,10 @@ public abstract class Goal { + return this.getClass().getSimpleName(); + } + +- public EnumSet getFlags() { +- return this.flags; ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getFlags() { ++ return this.goalTypes; ++ // Paper end - remove streams from pathfindergoalselector + } + + protected int adjustedTickDelay(int ticks) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 19ee04dd92b39a775260f832ca8880335d24988b..a910189177da0c1134c954b3d81b9e9bc4bc1420 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -29,10 +29,12 @@ public class GoalSelector { + private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); + public final Set availableGoals = Sets.newLinkedHashSet(); + private final Supplier profiler; +- private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); ++ private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + private int tickCount; + private int newGoalRate = 3; + private int curRate; ++ private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector + + public GoalSelector(Supplier profiler) { + this.profiler = profiler; +@@ -62,26 +64,32 @@ public class GoalSelector { + } + // Paper end + public void removeGoal(Goal goal) { +- this.availableGoals.stream().filter((wrappedGoal) -> { +- return wrappedGoal.getGoal() == goal; +- }).filter(WrappedGoal::isRunning).forEach(WrappedGoal::stop); +- this.availableGoals.removeIf((wrappedGoal) -> { +- return wrappedGoal.getGoal() == goal; +- }); +- } +- +- private static boolean goalContainsAnyFlags(WrappedGoal goal, EnumSet controls) { +- for(Goal.Flag flag : goal.getFlags()) { +- if (controls.contains(flag)) { +- return true; ++ // Paper start - remove streams from pathfindergoalselector ++ for (java.util.Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { ++ WrappedGoal goalWrapped = iterator.next(); ++ if (goalWrapped.getGoal() != goal) { ++ continue; + } ++ if (goalWrapped.isRunning()) { ++ goalWrapped.stop(); ++ } ++ iterator.remove(); + } ++ // Paper end - remove streams from pathfindergoalselector ++ } + +- return false; ++ private static boolean goalContainsAnyFlags(WrappedGoal goal, com.destroystokyo.paper.util.set.OptimizedSmallEnumSet controls) { ++ return goal.getFlags().hasCommonElements(controls); // Paper + } + + private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map goalsByControl) { +- for(Goal.Flag flag : goal.getFlags()) { ++ // Paper start ++ long flagIterator = goal.getFlags().getBackingSet(); ++ int wrappedGoalSize = goal.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(flagIterator); ++ // Paper end + if (!goalsByControl.getOrDefault(flag, NO_GOAL).canBeReplacedBy(goal)) { + return false; + } +@@ -95,7 +103,7 @@ public class GoalSelector { + profilerFiller.push("goalCleanup"); + + for(WrappedGoal wrappedGoal : this.availableGoals) { +- if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.disabledFlags) || !wrappedGoal.canContinueToUse())) { ++ if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { + wrappedGoal.stop(); + } + } +@@ -113,8 +121,14 @@ public class GoalSelector { + profilerFiller.push("goalUpdate"); + + for(WrappedGoal wrappedGoal2 : this.availableGoals) { +- if (!wrappedGoal2.isRunning() && !goalContainsAnyFlags(wrappedGoal2, this.disabledFlags) && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags) && wrappedGoal2.canUse()) { +- for(Goal.Flag flag : wrappedGoal2.getFlags()) { ++ // Paper start ++ if (!wrappedGoal2.isRunning() && !goalContainsAnyFlags(wrappedGoal2, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags) && wrappedGoal2.canUse()) { ++ long flagIterator = wrappedGoal2.getFlags().getBackingSet(); ++ int wrappedGoalSize = wrappedGoal2.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(flagIterator); ++ // Paper end + WrappedGoal wrappedGoal3 = this.lockedFlags.getOrDefault(flag, NO_GOAL); + wrappedGoal3.stop(); + this.lockedFlags.put(flag, wrappedGoal2); +@@ -154,11 +168,11 @@ public class GoalSelector { + } + + public void disableControlFlag(Goal.Flag control) { +- this.disabledFlags.add(control); ++ this.goalTypes.addUnchecked(control); // Paper - remove streams from pathfindergoalselector + } + + public void enableControlFlag(Goal.Flag control) { +- this.disabledFlags.remove(control); ++ this.goalTypes.removeUnchecked(control); // Paper - remove streams from pathfindergoalselector + } + + public void setControlFlag(Goal.Flag control, boolean enabled) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +index 6665ce5f48316e626907e6937d5ef1bc398a7ebd..51deb4455cac055ffa455e4f34aa30858d2fb448 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java +@@ -69,8 +69,10 @@ public class WrappedGoal extends Goal { + } + + @Override +- public EnumSet getFlags() { ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getFlags() { + return this.goal.getFlags(); ++ // Paper end - remove streams from pathfindergoalselector + } + + public boolean isRunning() { diff --git a/patches/server/0388-Improved-Watchdog-Support.patch b/patches/server/0388-Improved-Watchdog-Support.patch new file mode 100644 index 0000000000..4c4b9a4420 --- /dev/null +++ b/patches/server/0388-Improved-Watchdog-Support.patch @@ -0,0 +1,591 @@ +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 5a19e30a9b7e65a70f68a429b8ca741f788a303b..7b1843e16745ca8db2244e17490d291401f22679 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 336795dff742b7c6957fbd3476aff31d25a5e659..30a58229aa6dac5039511d0c0df5f2912ea7de9f 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -230,6 +230,7 @@ public class CrashReport { + } + + public static CrashReport forThrowable(Throwable cause, String title) { ++ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper + while (cause instanceof CompletionException && cause.getCause() != null) { + cause = cause.getCause(); + } +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 12ca13f5b0556c53fd36d638ee4fa854b89ee8ec..3a4222f78a02e10ecccc03df3c580895fbb8059d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -284,7 +284,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); + public int autosavePeriod; + public Commands vanillaCommandDispatcher; +- private boolean forceTicks; ++ public boolean forceTicks; // Paper + // CraftBukkit end + // Spigot start + public static final int TPS = 20; +@@ -295,6 +295,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); + Thread thread = new Thread(() -> { +@@ -867,6 +870,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; ++ } ++ // Paper end + return new TickTask(this.tickCount, runnable); + } + +@@ -1452,6 +1505,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + CompletableFuture completablefuture; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index d9175bcd1060dbaee90e50466da3c6d816fb614e..602a30f7dc51ffad9e5f5cc0b339108a2225aafd 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -518,7 +518,7 @@ public abstract class PlayerList { + this.cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +- entityplayer.doTick(); // SPIGOT-924 ++ if (server.isSameThread()) entityplayer.doTick(); // 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/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 6fefa619299d3202158490630d62c16aef71e831..7a4ade1a4190bf4fbb048919ae2be230f7b80fff 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -152,6 +152,7 @@ public abstract class BlockableEventLoop implements Profiler + try { + task.run(); + } catch (Exception var3) { ++ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper + LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", this.name(), var3); + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index ef4e48127168acdd614bbdcac97a98da5240928e..4c93c2ee69c2728d798a750981275f4fc6840525 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -794,6 +794,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + try { + tickConsumer.accept(entity); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index ebe17598d3ef17cc5f0e99b876ed67936d46060d..c8444f4bfd127b7d8194aaa984505eff249ae094 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -1085,6 +1085,7 @@ public class LevelChunk extends ChunkAccess { + + gameprofilerfiller.pop(); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index a35501ea43bf3589b346b1e684c318b44ca57977..4016b31bd020e00c0e79328646f9b5411b312e88 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2075,7 +2075,7 @@ public final class CraftServer implements Server { + + @Override + public boolean isPrimaryThread() { +- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly ++ return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 08e74f41516a545a2371a7418d995ab288831834..cce6886bb3973eed8f0c7ca7b1189547324fd4e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -12,6 +12,8 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import joptsimple.OptionParser; + import joptsimple.OptionSet; ++import net.minecraft.util.ExceptionCollector; ++import net.minecraft.world.level.lighting.LayerLightEventListener; + import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper + + public class Main { +@@ -169,6 +171,36 @@ public class Main { + + OptionSet options = null; + ++ // Paper start - preload logger classes to avoid plugins mixing versions ++ tryPreloadClass("org.apache.logging.log4j.core.Core"); ++ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender"); ++ tryPreloadClass("org.apache.logging.log4j.core.Appender"); ++ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector"); ++ tryPreloadClass("org.apache.logging.log4j.core.Filter"); ++ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.Logger"); ++ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener"); ++ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable"); ++ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.Message"); ++ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory"); ++ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage"); ++ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil"); ++ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo"); ++ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement"); ++ // Paper end + try { + options = parser.parse(args); + } catch (joptsimple.OptionException ex) { +@@ -264,8 +296,64 @@ public class Main { + } catch (Throwable t) { + t.printStackTrace(); + } ++ // Paper start ++ // load some required classes to avoid errors during shutdown if jar is replaced ++ // also to guarantee our version loads over plugins ++ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow"); ++ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator"); ++ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry"); ++ tryPreloadClass("com.google.common.collect.Iterables"); ++ for (int i = 1; i <= 15; i++) { ++ tryPreloadClass("com.google.common.collect.Iterables$" + i, false); ++ } ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean"); ++ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport"); ++ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1"); ++ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking"); ++ tryPreloadClass("co.aikar.timings.TimingHistory"); ++ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11"); ++ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12"); ++ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise"); ++ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1"); ++ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil"); ++ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3"); ++ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4"); ++ tryPreloadClass("org.slf4j.helpers.MessageFormatter"); ++ tryPreloadClass("org.slf4j.helpers.FormattingTuple"); ++ tryPreloadClass("org.slf4j.helpers.BasicMarker"); ++ tryPreloadClass("org.slf4j.helpers.Util"); ++ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent"); ++ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent"); ++ // Minecraft, seen during saving ++ tryPreloadClass(LayerLightEventListener.DummyLightLayerEventListener.class.getName()); ++ tryPreloadClass(LayerLightEventListener.class.getName()); ++ tryPreloadClass(ExceptionCollector.class.getName()); ++ // Paper end ++ } ++ } ++ ++ // Paper start ++ private static void tryPreloadClass(String className) { ++ tryPreloadClass(className, true); ++ } ++ private static void tryPreloadClass(String className, boolean printError) { ++ try { ++ Class.forName(className); ++ } catch (ClassNotFoundException e) { ++ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage()); + } + } ++ // Paper end + + private static List asList(String... params) { + return Arrays.asList(params); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +index b4a19d80bbf71591f25729fd0e98590350cb31d0..e948ec5a573b22645664eb53bc3e9932246544e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -12,12 +12,28 @@ 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; + this.server.close(); ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ // Paper end + } finally { ++ org.apache.logging.log4j.LogManager.shutdown(); // Paper + try { +- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender ++ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop + } catch (Exception e) { + } + } +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index a142a56a920e153ed84c08cece993f10d76f7793..92d97a5810a379b427a99b4c63fb9844d823a84f 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 e62f085de1678568d422ef0eda5b9bfbd8b4d556..f3d60841f7b2e29e667291092b97ec909ba03ab6 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -11,6 +11,7 @@ import org.bukkit.Bukkit; + public 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; +@@ -39,6 +40,7 @@ public class WatchdogThread extends Thread + { + if ( WatchdogThread.instance == null ) + { ++ if (timeoutTime <= 0) timeoutTime = 300; // Paper + WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart ); + WatchdogThread.instance.start(); + } else +@@ -70,12 +72,13 @@ public class WatchdogThread extends Thread + // Paper start + Logger log = Bukkit.getServer().getLogger(); + long currentTime = WatchdogThread.monotonicMillis(); +- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable ++ MinecraftServer server = MinecraftServer.getServer(); ++ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable + { +- boolean isLongTimeout = currentTime > lastTick + timeoutTime; ++ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000); + // Don't spam early warning dumps + if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; +- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... ++ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... + lastEarlyWarning = currentTime; + if (isLongTimeout) { + // Paper end +@@ -117,7 +120,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 + com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper +- WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); ++ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + // Paper start - Only print full dump on long timeouts +@@ -137,9 +140,25 @@ public class WatchdogThread extends Thread + + if ( isLongTimeout ) + { +- if ( this.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.abnormalExit = true; ++ server.safeShutdown(false, restart); ++ try { ++ Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ } ++ if (!server.hasStopped()) { ++ server.close(); ++ } + } + break; + } // Paper end +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 3dc317e466e1b93dff030794dd7f29ca1b266778..d285dbec16272db6b8a71865e05924ad66087407 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -1,5 +1,5 @@ + +- ++ + + + diff --git a/patches/server/0388-Optimize-GoalSelector-Goal.Flag-Set-operations.patch b/patches/server/0388-Optimize-GoalSelector-Goal.Flag-Set-operations.patch deleted file mode 100644 index cf8d69b485..0000000000 --- a/patches/server/0388-Optimize-GoalSelector-Goal.Flag-Set-operations.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 6 Apr 2020 17:53:29 -0700 -Subject: [PATCH] Optimize GoalSelector Goal.Flag Set operations - -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/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -index 6667ecc4b7eded4e20a415cef1e1b1179e6710b8..4379b9948f1eecfe6fd7dea98e298ad5f761019a 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -@@ -4,7 +4,8 @@ import java.util.EnumSet; - import net.minecraft.util.Mth; - - public abstract class Goal { -- private final EnumSet flags = EnumSet.noneOf(Goal.Flag.class); -+ private final EnumSet flags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector - - public abstract boolean canUse(); - -@@ -30,8 +31,10 @@ public abstract class Goal { - } - - public void setFlags(EnumSet controls) { -- this.flags.clear(); -- this.flags.addAll(controls); -+ // Paper start - remove streams from pathfindergoalselector -+ this.goalTypes.clear(); -+ this.goalTypes.addAllUnchecked(controls); -+ // Paper end - remove streams from pathfindergoalselector - } - - @Override -@@ -39,8 +42,10 @@ public abstract class Goal { - return this.getClass().getSimpleName(); - } - -- public EnumSet getFlags() { -- return this.flags; -+ // Paper start - remove streams from pathfindergoalselector -+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getFlags() { -+ return this.goalTypes; -+ // Paper end - remove streams from pathfindergoalselector - } - - protected int adjustedTickDelay(int ticks) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -index 19ee04dd92b39a775260f832ca8880335d24988b..a910189177da0c1134c954b3d81b9e9bc4bc1420 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -29,10 +29,12 @@ public class GoalSelector { - private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); - public final Set availableGoals = Sets.newLinkedHashSet(); - private final Supplier profiler; -- private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.class); -+ private final EnumSet disabledFlags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector - private int tickCount; - private int newGoalRate = 3; - private int curRate; -+ private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from pathfindergoalselector - - public GoalSelector(Supplier profiler) { - this.profiler = profiler; -@@ -62,26 +64,32 @@ public class GoalSelector { - } - // Paper end - public void removeGoal(Goal goal) { -- this.availableGoals.stream().filter((wrappedGoal) -> { -- return wrappedGoal.getGoal() == goal; -- }).filter(WrappedGoal::isRunning).forEach(WrappedGoal::stop); -- this.availableGoals.removeIf((wrappedGoal) -> { -- return wrappedGoal.getGoal() == goal; -- }); -- } -- -- private static boolean goalContainsAnyFlags(WrappedGoal goal, EnumSet controls) { -- for(Goal.Flag flag : goal.getFlags()) { -- if (controls.contains(flag)) { -- return true; -+ // Paper start - remove streams from pathfindergoalselector -+ for (java.util.Iterator iterator = this.availableGoals.iterator(); iterator.hasNext();) { -+ WrappedGoal goalWrapped = iterator.next(); -+ if (goalWrapped.getGoal() != goal) { -+ continue; - } -+ if (goalWrapped.isRunning()) { -+ goalWrapped.stop(); -+ } -+ iterator.remove(); - } -+ // Paper end - remove streams from pathfindergoalselector -+ } - -- return false; -+ private static boolean goalContainsAnyFlags(WrappedGoal goal, com.destroystokyo.paper.util.set.OptimizedSmallEnumSet controls) { -+ return goal.getFlags().hasCommonElements(controls); // Paper - } - - private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map goalsByControl) { -- for(Goal.Flag flag : goal.getFlags()) { -+ // Paper start -+ long flagIterator = goal.getFlags().getBackingSet(); -+ int wrappedGoalSize = goal.getFlags().size(); -+ for (int i = 0; i < wrappedGoalSize; ++i) { -+ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; -+ flagIterator ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(flagIterator); -+ // Paper end - if (!goalsByControl.getOrDefault(flag, NO_GOAL).canBeReplacedBy(goal)) { - return false; - } -@@ -95,7 +103,7 @@ public class GoalSelector { - profilerFiller.push("goalCleanup"); - - for(WrappedGoal wrappedGoal : this.availableGoals) { -- if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.disabledFlags) || !wrappedGoal.canContinueToUse())) { -+ if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { - wrappedGoal.stop(); - } - } -@@ -113,8 +121,14 @@ public class GoalSelector { - profilerFiller.push("goalUpdate"); - - for(WrappedGoal wrappedGoal2 : this.availableGoals) { -- if (!wrappedGoal2.isRunning() && !goalContainsAnyFlags(wrappedGoal2, this.disabledFlags) && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags) && wrappedGoal2.canUse()) { -- for(Goal.Flag flag : wrappedGoal2.getFlags()) { -+ // Paper start -+ if (!wrappedGoal2.isRunning() && !goalContainsAnyFlags(wrappedGoal2, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoal2, this.lockedFlags) && wrappedGoal2.canUse()) { -+ long flagIterator = wrappedGoal2.getFlags().getBackingSet(); -+ int wrappedGoalSize = wrappedGoal2.getFlags().size(); -+ for (int i = 0; i < wrappedGoalSize; ++i) { -+ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; -+ flagIterator ^= io.papermc.paper.util.IntegerUtil.getTrailingBit(flagIterator); -+ // Paper end - WrappedGoal wrappedGoal3 = this.lockedFlags.getOrDefault(flag, NO_GOAL); - wrappedGoal3.stop(); - this.lockedFlags.put(flag, wrappedGoal2); -@@ -154,11 +168,11 @@ public class GoalSelector { - } - - public void disableControlFlag(Goal.Flag control) { -- this.disabledFlags.add(control); -+ this.goalTypes.addUnchecked(control); // Paper - remove streams from pathfindergoalselector - } - - public void enableControlFlag(Goal.Flag control) { -- this.disabledFlags.remove(control); -+ this.goalTypes.removeUnchecked(control); // Paper - remove streams from pathfindergoalselector - } - - public void setControlFlag(Goal.Flag control, boolean enabled) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java -index 6665ce5f48316e626907e6937d5ef1bc398a7ebd..51deb4455cac055ffa455e4f34aa30858d2fb448 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/WrappedGoal.java -@@ -69,8 +69,10 @@ public class WrappedGoal extends Goal { - } - - @Override -- public EnumSet getFlags() { -+ // Paper start - remove streams from pathfindergoalselector -+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getFlags() { - return this.goal.getFlags(); -+ // Paper end - remove streams from pathfindergoalselector - } - - public boolean isRunning() { diff --git a/patches/server/0389-Improved-Watchdog-Support.patch b/patches/server/0389-Improved-Watchdog-Support.patch deleted file mode 100644 index 4c4b9a4420..0000000000 --- a/patches/server/0389-Improved-Watchdog-Support.patch +++ /dev/null @@ -1,591 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 12 Apr 2020 15:50:48 -0400 -Subject: [PATCH] Improved Watchdog Support - -Forced Watchdog Crash support and Improve Async Shutdown - -If the request to shut down the server is received while we are in -a watchdog hang, immediately treat it as a crash and begin the shutdown -process. Shutdown process is now improved to also shutdown cleanly when -not using restart scripts either. - -If a server is deadlocked, a server owner can send SIGUP (or any other signal -the JVM understands to shut down as it currently does) and the watchdog -will no longer need to wait until the full timeout, allowing you to trigger -a close process and try to shut the server down gracefully, saving player and -world data. - -Previously there was no way to trigger this outside of waiting for a full watchdog -timeout, which may be set to a really long time... - -Additionally, fix everything to do with shutting the server down asynchronously. - -Previously, nearly everything about the process was fragile and unsafe. Main might -not have actually been frozen, and might still be manipulating state. - -Or, some reuest might ask main to do something in the shutdown but main is dead. - -Or worse, other things might start closing down items such as the Console or Thread Pool -before we are fully shutdown. - -This change tries to resolve all of these issues by moving everything into the stop -method and guaranteeing only one thread is stopping the server. - -We then issue Thread Death to the main thread of another thread initiates the stop process. -We have to ensure Thread Death propagates correctly though to stop main completely. - -This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save. - -This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they -are properly accounted for and wont trip watchdog on init. - -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index 5a19e30a9b7e65a70f68a429b8ca741f788a303b..7b1843e16745ca8db2244e17490d291401f22679 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 336795dff742b7c6957fbd3476aff31d25a5e659..30a58229aa6dac5039511d0c0df5f2912ea7de9f 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -230,6 +230,7 @@ public class CrashReport { - } - - public static CrashReport forThrowable(Throwable cause, String title) { -+ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper - while (cause instanceof CompletionException && cause.getCause() != null) { - cause = cause.getCause(); - } -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 12ca13f5b0556c53fd36d638ee4fa854b89ee8ec..3a4222f78a02e10ecccc03df3c580895fbb8059d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -284,7 +284,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - public Commands vanillaCommandDispatcher; -- private boolean forceTicks; -+ public boolean forceTicks; // Paper - // CraftBukkit end - // Spigot start - public static final int TPS = 20; -@@ -295,6 +295,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); - Thread thread = new Thread(() -> { -@@ -867,6 +870,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; -+ } -+ // Paper end - return new TickTask(this.tickCount, runnable); - } - -@@ -1452,6 +1505,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - CompletableFuture completablefuture; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index d9175bcd1060dbaee90e50466da3c6d816fb614e..602a30f7dc51ffad9e5f5cc0b339108a2225aafd 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -518,7 +518,7 @@ public abstract class PlayerList { - this.cserver.getPluginManager().callEvent(playerQuitEvent); - entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); - -- entityplayer.doTick(); // SPIGOT-924 -+ if (server.isSameThread()) entityplayer.doTick(); // 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/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -index 6fefa619299d3202158490630d62c16aef71e831..7a4ade1a4190bf4fbb048919ae2be230f7b80fff 100644 ---- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java -@@ -152,6 +152,7 @@ public abstract class BlockableEventLoop implements Profiler - try { - task.run(); - } catch (Exception var3) { -+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper - LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", this.name(), var3); - } - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index ef4e48127168acdd614bbdcac97a98da5240928e..4c93c2ee69c2728d798a750981275f4fc6840525 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -794,6 +794,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - try { - tickConsumer.accept(entity); - } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent tile entity and entity crashes - final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); - MinecraftServer.LOGGER.error(msg, throwable); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index ebe17598d3ef17cc5f0e99b876ed67936d46060d..c8444f4bfd127b7d8194aaa984505eff249ae094 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -1085,6 +1085,7 @@ public class LevelChunk extends ChunkAccess { - - gameprofilerfiller.pop(); - } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent tile entity and entity crashes - final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); - net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index a35501ea43bf3589b346b1e684c318b44ca57977..4016b31bd020e00c0e79328646f9b5411b312e88 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2075,7 +2075,7 @@ public final class CraftServer implements Server { - - @Override - public boolean isPrimaryThread() { -- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly -+ return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario - } - - // Paper start -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 08e74f41516a545a2371a7418d995ab288831834..cce6886bb3973eed8f0c7ca7b1189547324fd4e2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -12,6 +12,8 @@ import java.util.logging.Level; - import java.util.logging.Logger; - import joptsimple.OptionParser; - import joptsimple.OptionSet; -+import net.minecraft.util.ExceptionCollector; -+import net.minecraft.world.level.lighting.LayerLightEventListener; - import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper - - public class Main { -@@ -169,6 +171,36 @@ public class Main { - - OptionSet options = null; - -+ // Paper start - preload logger classes to avoid plugins mixing versions -+ tryPreloadClass("org.apache.logging.log4j.core.Core"); -+ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender"); -+ tryPreloadClass("org.apache.logging.log4j.core.Appender"); -+ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector"); -+ tryPreloadClass("org.apache.logging.log4j.core.Filter"); -+ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler"); -+ tryPreloadClass("org.apache.logging.log4j.core.LogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.core.Logger"); -+ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext"); -+ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener"); -+ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable"); -+ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.Message"); -+ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory"); -+ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage"); -+ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil"); -+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo"); -+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement"); -+ // Paper end - try { - options = parser.parse(args); - } catch (joptsimple.OptionException ex) { -@@ -264,8 +296,64 @@ public class Main { - } catch (Throwable t) { - t.printStackTrace(); - } -+ // Paper start -+ // load some required classes to avoid errors during shutdown if jar is replaced -+ // also to guarantee our version loads over plugins -+ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow"); -+ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator"); -+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry"); -+ tryPreloadClass("com.google.common.collect.Iterables"); -+ for (int i = 1; i <= 15; i++) { -+ tryPreloadClass("com.google.common.collect.Iterables$" + i, false); -+ } -+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean"); -+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt"); -+ tryPreloadClass("org.jline.terminal.impl.MouseSupport"); -+ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1"); -+ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking"); -+ tryPreloadClass("co.aikar.timings.TimingHistory"); -+ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11"); -+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12"); -+ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8"); -+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise"); -+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1"); -+ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil"); -+ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3"); -+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4"); -+ tryPreloadClass("org.slf4j.helpers.MessageFormatter"); -+ tryPreloadClass("org.slf4j.helpers.FormattingTuple"); -+ tryPreloadClass("org.slf4j.helpers.BasicMarker"); -+ tryPreloadClass("org.slf4j.helpers.Util"); -+ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent"); -+ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent"); -+ // Minecraft, seen during saving -+ tryPreloadClass(LayerLightEventListener.DummyLightLayerEventListener.class.getName()); -+ tryPreloadClass(LayerLightEventListener.class.getName()); -+ tryPreloadClass(ExceptionCollector.class.getName()); -+ // Paper end -+ } -+ } -+ -+ // Paper start -+ private static void tryPreloadClass(String className) { -+ tryPreloadClass(className, true); -+ } -+ private static void tryPreloadClass(String className, boolean printError) { -+ try { -+ Class.forName(className); -+ } catch (ClassNotFoundException e) { -+ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage()); - } - } -+ // Paper end - - private static List asList(String... params) { - return Arrays.asList(params); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -index b4a19d80bbf71591f25729fd0e98590350cb31d0..e948ec5a573b22645664eb53bc3e9932246544e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java -@@ -12,12 +12,28 @@ 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; - this.server.close(); -+ while (!server.hasFullyShutdown) Thread.sleep(1000); -+ } catch (InterruptedException e) { -+ e.printStackTrace(); -+ // Paper end - } finally { -+ org.apache.logging.log4j.LogManager.shutdown(); // Paper - try { -- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender -+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop - } catch (Exception e) { - } - } -diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java -index a142a56a920e153ed84c08cece993f10d76f7793..92d97a5810a379b427a99b4c63fb9844d823a84f 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 e62f085de1678568d422ef0eda5b9bfbd8b4d556..f3d60841f7b2e29e667291092b97ec909ba03ab6 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -11,6 +11,7 @@ import org.bukkit.Bukkit; - public 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; -@@ -39,6 +40,7 @@ public class WatchdogThread extends Thread - { - if ( WatchdogThread.instance == null ) - { -+ if (timeoutTime <= 0) timeoutTime = 300; // Paper - WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart ); - WatchdogThread.instance.start(); - } else -@@ -70,12 +72,13 @@ public class WatchdogThread extends Thread - // Paper start - Logger log = Bukkit.getServer().getLogger(); - long currentTime = WatchdogThread.monotonicMillis(); -- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable -+ MinecraftServer server = MinecraftServer.getServer(); -+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable - { -- boolean isLongTimeout = currentTime > lastTick + timeoutTime; -+ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000); - // Don't spam early warning dumps - if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue; -- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... -+ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this... - lastEarlyWarning = currentTime; - if (isLongTimeout) { - // Paper end -@@ -117,7 +120,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 - com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper -- WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); -+ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // - // Paper start - Only print full dump on long timeouts -@@ -137,9 +140,25 @@ public class WatchdogThread extends Thread - - if ( isLongTimeout ) - { -- if ( this.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.abnormalExit = true; -+ server.safeShutdown(false, restart); -+ try { -+ Thread.sleep(1000); -+ } catch (InterruptedException e) { -+ e.printStackTrace(); -+ } -+ if (!server.hasStopped()) { -+ server.close(); -+ } - } - break; - } // Paper end -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 3dc317e466e1b93dff030794dd7f29ca1b266778..d285dbec16272db6b8a71865e05924ad66087407 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -1,5 +1,5 @@ - -- -+ - - - diff --git a/patches/server/0389-Optimize-Pathfinding.patch b/patches/server/0389-Optimize-Pathfinding.patch new file mode 100644 index 0000000000..061629eb03 --- /dev/null +++ b/patches/server/0389-Optimize-Pathfinding.patch @@ -0,0 +1,43 @@ +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/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index 649c2fdba307d986d13916bf90e311c862ccefc1..af53372391d05dd6aa3757556418e8723b8b6d80 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -191,9 +191,29 @@ public abstract class PathNavigation { + return this.moveTo(this.createPath(x, y, z, 1), speed); + } + ++ // Paper start - optimise pathfinding ++ private int lastFailure = 0; ++ private int pathfindFailures = 0; ++ // Paper end ++ + public boolean moveTo(Entity entity, double speed) { ++ // Paper start - Pathfinding optimizations ++ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { ++ return false; ++ } ++ // Paper end + Path path = this.createPath(entity, 1); +- return path != null && this.moveTo(path, speed); ++ // Paper start - Pathfinding optimizations ++ if (path != null && this.moveTo(path, speed)) { ++ this.lastFailure = 0; ++ this.pathfindFailures = 0; ++ return true; ++ } else { ++ this.pathfindFailures++; ++ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; ++ return false; ++ } ++ // Paper end + } + + public boolean moveTo(@Nullable Path path, double speed) { diff --git a/patches/server/0390-Optimize-Pathfinding.patch b/patches/server/0390-Optimize-Pathfinding.patch deleted file mode 100644 index 061629eb03..0000000000 --- a/patches/server/0390-Optimize-Pathfinding.patch +++ /dev/null @@ -1,43 +0,0 @@ -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/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index 649c2fdba307d986d13916bf90e311c862ccefc1..af53372391d05dd6aa3757556418e8723b8b6d80 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -191,9 +191,29 @@ public abstract class PathNavigation { - return this.moveTo(this.createPath(x, y, z, 1), speed); - } - -+ // Paper start - optimise pathfinding -+ private int lastFailure = 0; -+ private int pathfindFailures = 0; -+ // Paper end -+ - public boolean moveTo(Entity entity, double speed) { -+ // Paper start - Pathfinding optimizations -+ if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) { -+ return false; -+ } -+ // Paper end - Path path = this.createPath(entity, 1); -- return path != null && this.moveTo(path, speed); -+ // Paper start - Pathfinding optimizations -+ if (path != null && this.moveTo(path, speed)) { -+ this.lastFailure = 0; -+ this.pathfindFailures = 0; -+ return true; -+ } else { -+ this.pathfindFailures++; -+ this.lastFailure = net.minecraft.server.MinecraftServer.currentTick; -+ return false; -+ } -+ // Paper end - } - - public boolean moveTo(@Nullable Path path, double speed) { diff --git a/patches/server/0390-Reduce-Either-Optional-allocation.patch b/patches/server/0390-Reduce-Either-Optional-allocation.patch new file mode 100644 index 0000000000..0f4641263b --- /dev/null +++ b/patches/server/0390-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/0391-Reduce-Either-Optional-allocation.patch b/patches/server/0391-Reduce-Either-Optional-allocation.patch deleted file mode 100644 index 0f4641263b..0000000000 --- a/patches/server/0391-Reduce-Either-Optional-allocation.patch +++ /dev/null @@ -1,48 +0,0 @@ -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/0391-Reduce-memory-footprint-of-NBTTagCompound.patch b/patches/server/0391-Reduce-memory-footprint-of-NBTTagCompound.patch new file mode 100644 index 0000000000..740fcbecab --- /dev/null +++ b/patches/server/0391-Reduce-memory-footprint-of-NBTTagCompound.patch @@ -0,0 +1,50 @@ +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/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index b965af563f2cb1508d138e4d48e97a44873c4bb9..0778fdd4f47015787f7ffbfb39c31ec0e1c039bd 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -34,7 +34,7 @@ public class CompoundTag implements Tag { + if (i > 512) { + throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); + } else { +- Map map = Maps.newHashMap(); ++ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - reduce memory footprint of NBTTagCompound + + byte b; + while((b = CompoundTag.readNamedTagType(dataInput, nbtAccounter)) != 0) { +@@ -128,7 +128,7 @@ public class CompoundTag implements Tag { + } + + public CompoundTag() { +- this(Maps.newHashMap()); ++ this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - reduce memory footprint of NBTTagCompound + } + + @Override +@@ -434,8 +434,16 @@ public class CompoundTag implements Tag { + + @Override + public CompoundTag copy() { +- Map map = Maps.newHashMap(Maps.transformValues(this.tags, Tag::copy)); +- return new CompoundTag(map); ++ // Paper start - reduce memory footprint of NBTTagCompound ++ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.tags.size(), 0.8f); ++ java.util.Iterator> iterator = (this.tags instanceof it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) ? ((it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap)this.tags).object2ObjectEntrySet().fastIterator() : this.tags.entrySet().iterator(); ++ while (iterator.hasNext()) { ++ Map.Entry entry = iterator.next(); ++ ret.put(entry.getKey(), entry.getValue().copy()); ++ } ++ ++ return new CompoundTag(ret); ++ // Paper end - reduce memory footprint of NBTTagCompound + } + + @Override diff --git a/patches/server/0392-Prevent-opening-inventories-when-frozen.patch b/patches/server/0392-Prevent-opening-inventories-when-frozen.patch new file mode 100644 index 0000000000..5e7ef9485c --- /dev/null +++ b/patches/server/0392-Prevent-opening-inventories-when-frozen.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 13 Apr 2020 07:31:44 +0100 +Subject: [PATCH] Prevent opening inventories when frozen + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 61f97fab1e0e3d6c9206c397cecfff868cf0d9d2..227bcd9c1cfcc6bf8dedacdca28731a8ffc7171a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -637,7 +637,7 @@ public class ServerPlayer extends Player { + containerUpdateDelay = level.paperConfig().tickRates.containerUpdate; + } + // Paper end +- if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) { ++ if (!this.level.isClientSide && this.containerMenu != this.inventoryMenu && (isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper + this.containerMenu = this.inventoryMenu; + } +@@ -1507,7 +1507,7 @@ public class ServerPlayer extends Player { + } else { + // CraftBukkit start + this.containerMenu = container; +- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); ++ if (!isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper + // CraftBukkit end + this.initMenu(container); + return OptionalInt.of(this.containerCounter); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 883b6245f44f3fb82d7678e1092177ca646d484a..f5f30e1408892b4e728053bc5005e551396583a5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -326,7 +326,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper + + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.containerMenu = container; + player.initMenu(container); + } +@@ -400,7 +400,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper + if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper + //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment +- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper ++ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper + player.containerMenu = container; + player.initMenu(container); + } diff --git a/patches/server/0392-Reduce-memory-footprint-of-NBTTagCompound.patch b/patches/server/0392-Reduce-memory-footprint-of-NBTTagCompound.patch deleted file mode 100644 index 740fcbecab..0000000000 --- a/patches/server/0392-Reduce-memory-footprint-of-NBTTagCompound.patch +++ /dev/null @@ -1,50 +0,0 @@ -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/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java -index b965af563f2cb1508d138e4d48e97a44873c4bb9..0778fdd4f47015787f7ffbfb39c31ec0e1c039bd 100644 ---- a/src/main/java/net/minecraft/nbt/CompoundTag.java -+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java -@@ -34,7 +34,7 @@ public class CompoundTag implements Tag { - if (i > 512) { - throw new RuntimeException("Tried to read NBT tag with too high complexity, depth > 512"); - } else { -- Map map = Maps.newHashMap(); -+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap map = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f); // Paper - reduce memory footprint of NBTTagCompound - - byte b; - while((b = CompoundTag.readNamedTagType(dataInput, nbtAccounter)) != 0) { -@@ -128,7 +128,7 @@ public class CompoundTag implements Tag { - } - - public CompoundTag() { -- this(Maps.newHashMap()); -+ this(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(8, 0.8f)); // Paper - reduce memory footprint of NBTTagCompound - } - - @Override -@@ -434,8 +434,16 @@ public class CompoundTag implements Tag { - - @Override - public CompoundTag copy() { -- Map map = Maps.newHashMap(Maps.transformValues(this.tags, Tag::copy)); -- return new CompoundTag(map); -+ // Paper start - reduce memory footprint of NBTTagCompound -+ it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap ret = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(this.tags.size(), 0.8f); -+ java.util.Iterator> iterator = (this.tags instanceof it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap) ? ((it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap)this.tags).object2ObjectEntrySet().fastIterator() : this.tags.entrySet().iterator(); -+ while (iterator.hasNext()) { -+ Map.Entry entry = iterator.next(); -+ ret.put(entry.getKey(), entry.getValue().copy()); -+ } -+ -+ return new CompoundTag(ret); -+ // Paper end - reduce memory footprint of NBTTagCompound - } - - @Override diff --git a/patches/server/0393-Optimise-ArraySetSorted-removeIf.patch b/patches/server/0393-Optimise-ArraySetSorted-removeIf.patch new file mode 100644 index 0000000000..eed4ea8bc2 --- /dev/null +++ b/patches/server/0393-Optimise-ArraySetSorted-removeIf.patch @@ -0,0 +1,88 @@ +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/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 9309ea89a440606be3e56ef634f5048a72b0009e..1d1ea158d095bb69260929e8d84f2632a875c136 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -86,13 +86,27 @@ public abstract class DistanceManager { + protected void purgeStaleTickets() { + ++this.ticketTickCounter; + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); ++ // Paper start - use optimised removeIf ++ long[] currChunk = new long[1]; ++ long ticketCounter = DistanceManager.this.ticketTickCounter; ++ java.util.function.Predicate> removeIf = (ticket) -> { ++ final boolean ret = ticket.timedOut(ticketCounter); ++ if (ret) { ++ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); ++ } ++ return ret; ++ }; ++ // Paper end - use optimised removeIf + + while (objectiterator.hasNext()) { + Entry>> entry = (Entry) objectiterator.next(); +- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); +- boolean flag = false; ++ // Paper start - use optimised removeIf ++ Iterator> iterator = null; ++ currChunk[0] = entry.getLongKey(); ++ boolean flag = entry.getValue().removeIf(removeIf); + +- while (iterator.hasNext()) { ++ while (false && iterator.hasNext()) { ++ // Paper end - use optimised removeIf + Ticket ticket = (Ticket) iterator.next(); + + if (ticket.timedOut(this.ticketTickCounter)) { +diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java +index d1b2ba24ef54e01c6249c3b2ca16e80f03c001a6..5f1c4c6b9e36f2d6ec43b82cc0e2cae24b800dc4 100644 +--- a/src/main/java/net/minecraft/util/SortedArraySet.java ++++ b/src/main/java/net/minecraft/util/SortedArraySet.java +@@ -22,6 +22,41 @@ public class SortedArraySet extends AbstractSet { + this.contents = (T[])castRawArray(new Object[initialCapacity]); + } + } ++ // Paper start - optimise removeIf ++ @Override ++ public boolean removeIf(java.util.function.Predicate filter) { ++ // prev. impl used an iterator, which could be n^2 and creates garbage ++ int i = 0, len = this.size; ++ T[] backingArray = this.contents; ++ ++ for (;;) { ++ if (i >= len) { ++ return false; ++ } ++ if (!filter.test(backingArray[i])) { ++ ++i; ++ continue; ++ } ++ break; ++ } ++ ++ // we only want to write back to backingArray if we really need to ++ ++ int lastIndex = i; // this is where new elements are shifted to ++ ++ for (; i < len; ++i) { ++ T curr = backingArray[i]; ++ if (!filter.test(curr)) { // if test throws we're screwed ++ backingArray[lastIndex++] = curr; ++ } ++ } ++ ++ // cleanup end ++ Arrays.fill(backingArray, lastIndex, len, null); ++ this.size = lastIndex; ++ return true; ++ } ++ // Paper end - optimise removeIf + + public static > SortedArraySet create() { + return create(10); diff --git a/patches/server/0393-Prevent-opening-inventories-when-frozen.patch b/patches/server/0393-Prevent-opening-inventories-when-frozen.patch deleted file mode 100644 index 8c7b292e06..0000000000 --- a/patches/server/0393-Prevent-opening-inventories-when-frozen.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 13 Apr 2020 07:31:44 +0100 -Subject: [PATCH] Prevent opening inventories when frozen - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 61f97fab1e0e3d6c9206c397cecfff868cf0d9d2..227bcd9c1cfcc6bf8dedacdca28731a8ffc7171a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -637,7 +637,7 @@ public class ServerPlayer extends Player { - containerUpdateDelay = level.paperConfig().tickRates.containerUpdate; - } - // Paper end -- if (!this.level.isClientSide && !this.containerMenu.stillValid(this)) { -+ if (!this.level.isClientSide && this.containerMenu != this.inventoryMenu && (isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen - this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - this.containerMenu = this.inventoryMenu; - } -@@ -1507,7 +1507,7 @@ public class ServerPlayer extends Player { - } else { - // CraftBukkit start - this.containerMenu = container; -- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); -+ if (!isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle())); // Paper - // CraftBukkit end - this.initMenu(container); - return OptionalInt.of(this.containerCounter); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 404ed5e8f54d70a50de4232c6ea0f6163b34c2ab..3866c466fcc40f17f88063acb939f9091708a92f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -323,7 +323,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(container.getBukkitView().getTitle()); // Paper - - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - player.containerMenu = container; - player.initMenu(container); - } -@@ -397,7 +397,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - net.kyori.adventure.text.Component adventure$title = inventory.title(); // Paper - if (adventure$title == null) adventure$title = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(inventory.getTitle()); // Paper - //player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, CraftChatMessage.fromString(title)[0])); // Paper - comment -- player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper -+ if (!player.isImmobile()) player.connection.send(new ClientboundOpenScreenPacket(container.containerId, windowType, io.papermc.paper.adventure.PaperAdventure.asVanilla(adventure$title))); // Paper - player.containerMenu = container; - player.initMenu(container); - } diff --git a/patches/server/0394-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0394-Don-t-run-entity-collision-code-if-not-needed.patch new file mode 100644 index 0000000000..11403f5b9a --- /dev/null +++ b/patches/server/0394-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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index a5b532a6051f9313bb5042bf61712015768b5426..9a7d2b0d84ce422afa96b5c07c972e52c6ac00ed 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3337,10 +3337,16 @@ public abstract class LivingEntity extends Entity { + protected void serverAiStep() {} + + protected void pushEntities() { ++ // Paper start - don't run getEntities if we're not going to use its result ++ int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ if (i <= 0 && level.paperConfig().collisions.maxEntityCollisions <= 0) { ++ return; ++ } ++ // Paper end - don't run getEntities if we're not going to use its result + List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); + + if (!list.isEmpty()) { +- int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); ++ // Paper - move up + int j; + + if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { diff --git a/patches/server/0394-Optimise-ArraySetSorted-removeIf.patch b/patches/server/0394-Optimise-ArraySetSorted-removeIf.patch deleted file mode 100644 index eed4ea8bc2..0000000000 --- a/patches/server/0394-Optimise-ArraySetSorted-removeIf.patch +++ /dev/null @@ -1,88 +0,0 @@ -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/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 9309ea89a440606be3e56ef634f5048a72b0009e..1d1ea158d095bb69260929e8d84f2632a875c136 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -86,13 +86,27 @@ public abstract class DistanceManager { - protected void purgeStaleTickets() { - ++this.ticketTickCounter; - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -+ // Paper start - use optimised removeIf -+ long[] currChunk = new long[1]; -+ long ticketCounter = DistanceManager.this.ticketTickCounter; -+ java.util.function.Predicate> removeIf = (ticket) -> { -+ final boolean ret = ticket.timedOut(ticketCounter); -+ if (ret) { -+ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); -+ } -+ return ret; -+ }; -+ // Paper end - use optimised removeIf - - while (objectiterator.hasNext()) { - Entry>> entry = (Entry) objectiterator.next(); -- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator(); -- boolean flag = false; -+ // Paper start - use optimised removeIf -+ Iterator> iterator = null; -+ currChunk[0] = entry.getLongKey(); -+ boolean flag = entry.getValue().removeIf(removeIf); - -- while (iterator.hasNext()) { -+ while (false && iterator.hasNext()) { -+ // Paper end - use optimised removeIf - Ticket ticket = (Ticket) iterator.next(); - - if (ticket.timedOut(this.ticketTickCounter)) { -diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java -index d1b2ba24ef54e01c6249c3b2ca16e80f03c001a6..5f1c4c6b9e36f2d6ec43b82cc0e2cae24b800dc4 100644 ---- a/src/main/java/net/minecraft/util/SortedArraySet.java -+++ b/src/main/java/net/minecraft/util/SortedArraySet.java -@@ -22,6 +22,41 @@ public class SortedArraySet extends AbstractSet { - this.contents = (T[])castRawArray(new Object[initialCapacity]); - } - } -+ // Paper start - optimise removeIf -+ @Override -+ public boolean removeIf(java.util.function.Predicate filter) { -+ // prev. impl used an iterator, which could be n^2 and creates garbage -+ int i = 0, len = this.size; -+ T[] backingArray = this.contents; -+ -+ for (;;) { -+ if (i >= len) { -+ return false; -+ } -+ if (!filter.test(backingArray[i])) { -+ ++i; -+ continue; -+ } -+ break; -+ } -+ -+ // we only want to write back to backingArray if we really need to -+ -+ int lastIndex = i; // this is where new elements are shifted to -+ -+ for (; i < len; ++i) { -+ T curr = backingArray[i]; -+ if (!filter.test(curr)) { // if test throws we're screwed -+ backingArray[lastIndex++] = curr; -+ } -+ } -+ -+ // cleanup end -+ Arrays.fill(backingArray, lastIndex, len, null); -+ this.size = lastIndex; -+ return true; -+ } -+ // Paper end - optimise removeIf - - public static > SortedArraySet create() { - return create(10); diff --git a/patches/server/0395-Don-t-run-entity-collision-code-if-not-needed.patch b/patches/server/0395-Don-t-run-entity-collision-code-if-not-needed.patch deleted file mode 100644 index 17bc39fe8d..0000000000 --- a/patches/server/0395-Don-t-run-entity-collision-code-if-not-needed.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 15 Apr 2020 17:56:07 -0700 -Subject: [PATCH] Don't run entity collision code if not needed - -Will not run if max entity 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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 878dd05e0a67a2c6bf48eab7f2d58bbfaf53d2d7..b3b3b163fb1a506fb048dba173517eeeaf2f8007 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3315,10 +3315,16 @@ public abstract class LivingEntity extends Entity { - protected void serverAiStep() {} - - protected void pushEntities() { -+ // Paper start - don't run getEntities if we're not going to use its result -+ int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ if (i <= 0 && level.paperConfig().collisions.maxEntityCollisions <= 0) { -+ return; -+ } -+ // Paper end - don't run getEntities if we're not going to use its result - List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); - - if (!list.isEmpty()) { -- int i = this.level.getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING); -+ // Paper - move up - int j; - - if (i > 0 && list.size() > i - 1 && this.random.nextInt(4) == 0) { diff --git a/patches/server/0395-Implement-Player-Client-Options-API.patch b/patches/server/0395-Implement-Player-Client-Options-API.patch new file mode 100644 index 0000000000..5a4d2416f5 --- /dev/null +++ b/patches/server/0395-Implement-Player-Client-Options-API.patch @@ -0,0 +1,151 @@ +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/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 227bcd9c1cfcc6bf8dedacdca28731a8ffc7171a..49cb507ddd378b9a16b82e5c2a28531fce9c6708 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1871,6 +1871,7 @@ public class ServerPlayer extends Player { + public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null + public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + public void updateOptions(ServerboundClientInformationPacket packet) { ++ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), packet.language, packet.viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(packet.chatVisibility().name()), packet.chatColors(), new com.destroystokyo.paper.PaperSkinParts(packet.modelCustomisation()), packet.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT).callEvent(); // Paper - settings event + // CraftBukkit start + if (getMainArm() != packet.mainHand()) { + PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 86447a824d9749f85a6378682d4a6f74ca19875d..0fda8c27c717bd030b826c5c7267b880f9d1f6b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -574,6 +574,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void setSendViewDistance(int viewDistance) { + throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO + } ++ ++ @Override ++ public T getClientOption(com.destroystokyo.paper.ClientOption type) { ++ if(com.destroystokyo.paper.ClientOption.SKIN_PARTS.equals(type)) { ++ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); ++ } else if(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED.equals(type)) { ++ return type.getType().cast(getHandle().canChatInColor()); ++ } else if(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY.equals(type)) { ++ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); ++ } else if(com.destroystokyo.paper.ClientOption.LOCALE.equals(type)) { ++ return type.getType().cast(getLocale()); ++ } else if(com.destroystokyo.paper.ClientOption.MAIN_HAND.equals(type)) { ++ return type.getType().cast(getMainHand()); ++ } else if(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE.equals(type)) { ++ return type.getType().cast(getClientViewDistance()); ++ } ++ throw new RuntimeException("Unknown settings type"); ++ } + // Paper end + + @Override +diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fab3063fffa959ac7f0eb5937f2fae94d11b6591 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.world; ++ ++import com.destroystokyo.paper.ClientOption; ++import net.minecraft.world.entity.player.ChatVisiblity; ++import org.bukkit.Difficulty; ++import org.junit.Assert; ++import org.junit.Test; ++ ++public class TranslationKeyTest { ++ ++ @Test ++ public void testChatVisibilityKeys() { ++ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { ++ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; ++ Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey()); ++ } ++ } ++} diff --git a/patches/server/0396-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0396-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch new file mode 100644 index 0000000000..48eea37bf3 --- /dev/null +++ b/patches/server/0396-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 18 Apr 2020 15:59:41 -0400 +Subject: [PATCH] Don't crash if player is attempted to be removed from + untracked chunk. + +I suspect it deals with teleporting as it uses players current x/y/z + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 1d1ea158d095bb69260929e8d84f2632a875c136..f456ba4bf699e1f6bd15726a037a0047b6ca7380 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -440,8 +440,8 @@ public abstract class DistanceManager { + ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); + if (objectset == null) return; // CraftBukkit - SPIGOT-6208 + +- objectset.remove(player); +- if (objectset.isEmpty()) { ++ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. ++ if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0396-Implement-Player-Client-Options-API.patch b/patches/server/0396-Implement-Player-Client-Options-API.patch deleted file mode 100644 index 5a4d2416f5..0000000000 --- a/patches/server/0396-Implement-Player-Client-Options-API.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Mon, 20 Jan 2020 21:38:15 +0100 -Subject: [PATCH] Implement Player Client Options API - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperSkinParts.java b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6f4400df3d8ec7e06a996de54f8cabba57885e1 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperSkinParts.java -@@ -0,0 +1,74 @@ -+package com.destroystokyo.paper; -+ -+import com.google.common.base.Objects; -+ -+import java.util.StringJoiner; -+ -+public class PaperSkinParts implements SkinParts { -+ -+ private final int raw; -+ -+ public PaperSkinParts(int raw) { -+ this.raw = raw; -+ } -+ -+ public boolean hasCapeEnabled() { -+ return (raw & 1) == 1; -+ } -+ -+ public boolean hasJacketEnabled() { -+ return (raw >> 1 & 1) == 1; -+ } -+ -+ public boolean hasLeftSleeveEnabled() { -+ return (raw >> 2 & 1) == 1; -+ } -+ -+ public boolean hasRightSleeveEnabled() { -+ return (raw >> 3 & 1) == 1; -+ } -+ -+ public boolean hasLeftPantsEnabled() { -+ return (raw >> 4 & 1) == 1; -+ } -+ -+ public boolean hasRightPantsEnabled() { -+ return (raw >> 5 & 1) == 1; -+ } -+ -+ public boolean hasHatsEnabled() { -+ return (raw >> 6 & 1) == 1; -+ } -+ -+ @Override -+ public int getRaw() { -+ return raw; -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ PaperSkinParts that = (PaperSkinParts) o; -+ return raw == that.raw; -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hashCode(raw); -+ } -+ -+ @Override -+ public String toString() { -+ return new StringJoiner(", ", PaperSkinParts.class.getSimpleName() + "[", "]") -+ .add("raw=" + raw) -+ .add("cape=" + hasCapeEnabled()) -+ .add("jacket=" + hasJacketEnabled()) -+ .add("leftSleeve=" + hasLeftSleeveEnabled()) -+ .add("rightSleeve=" + hasRightSleeveEnabled()) -+ .add("leftPants=" + hasLeftPantsEnabled()) -+ .add("rightPants=" + hasRightPantsEnabled()) -+ .add("hats=" + hasHatsEnabled()) -+ .toString(); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 227bcd9c1cfcc6bf8dedacdca28731a8ffc7171a..49cb507ddd378b9a16b82e5c2a28531fce9c6708 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1871,6 +1871,7 @@ public class ServerPlayer extends Player { - public String locale = null; // CraftBukkit - add, lowercase // Paper - default to null - public java.util.Locale adventure$locale = java.util.Locale.US; // Paper - public void updateOptions(ServerboundClientInformationPacket packet) { -+ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(getBukkitEntity(), packet.language, packet.viewDistance, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(packet.chatVisibility().name()), packet.chatColors(), new com.destroystokyo.paper.PaperSkinParts(packet.modelCustomisation()), packet.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT).callEvent(); // Paper - settings event - // CraftBukkit start - if (getMainArm() != packet.mainHand()) { - PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 86447a824d9749f85a6378682d4a6f74ca19875d..0fda8c27c717bd030b826c5c7267b880f9d1f6b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -574,6 +574,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - public void setSendViewDistance(int viewDistance) { - throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO - } -+ -+ @Override -+ public T getClientOption(com.destroystokyo.paper.ClientOption type) { -+ if(com.destroystokyo.paper.ClientOption.SKIN_PARTS.equals(type)) { -+ return type.getType().cast(new com.destroystokyo.paper.PaperSkinParts(getHandle().getEntityData().get(net.minecraft.world.entity.player.Player.DATA_PLAYER_MODE_CUSTOMISATION))); -+ } else if(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED.equals(type)) { -+ return type.getType().cast(getHandle().canChatInColor()); -+ } else if(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY.equals(type)) { -+ return type.getType().cast(getHandle().getChatVisibility() == null ? com.destroystokyo.paper.ClientOption.ChatVisibility.UNKNOWN : com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(getHandle().getChatVisibility().name())); -+ } else if(com.destroystokyo.paper.ClientOption.LOCALE.equals(type)) { -+ return type.getType().cast(getLocale()); -+ } else if(com.destroystokyo.paper.ClientOption.MAIN_HAND.equals(type)) { -+ return type.getType().cast(getMainHand()); -+ } else if(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE.equals(type)) { -+ return type.getType().cast(getClientViewDistance()); -+ } -+ throw new RuntimeException("Unknown settings type"); -+ } - // Paper end - - @Override -diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fab3063fffa959ac7f0eb5937f2fae94d11b6591 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.ClientOption; -+import net.minecraft.world.entity.player.ChatVisiblity; -+import org.bukkit.Difficulty; -+import org.junit.Assert; -+import org.junit.Test; -+ -+public class TranslationKeyTest { -+ -+ @Test -+ public void testChatVisibilityKeys() { -+ for (ClientOption.ChatVisibility chatVisibility : ClientOption.ChatVisibility.values()) { -+ if (chatVisibility == ClientOption.ChatVisibility.UNKNOWN) continue; -+ Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey()); -+ } -+ } -+} diff --git a/patches/server/0397-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0397-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch deleted file mode 100644 index 48eea37bf3..0000000000 --- a/patches/server/0397-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 18 Apr 2020 15:59:41 -0400 -Subject: [PATCH] Don't crash if player is attempted to be removed from - untracked chunk. - -I suspect it deals with teleporting as it uses players current x/y/z - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 1d1ea158d095bb69260929e8d84f2632a875c136..f456ba4bf699e1f6bd15726a037a0047b6ca7380 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -440,8 +440,8 @@ public abstract class DistanceManager { - ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); - if (objectset == null) return; // CraftBukkit - SPIGOT-6208 - -- objectset.remove(player); -- if (objectset.isEmpty()) { -+ if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. -+ if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); - this.playerTicketManager.update(i, Integer.MAX_VALUE, false); diff --git a/patches/server/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch b/patches/server/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch new file mode 100644 index 0000000000..13219b5e56 --- /dev/null +++ b/patches/server/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch @@ -0,0 +1,73 @@ +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 boundaries 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 reproducible crash +for heightmaps. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index fcc9dd6e1c54e4ca16102150aa4c12ecc7de06df..aed3da6ef2d498d3f1c9c64177bf1ba6b8157493 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -193,6 +193,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }; + // 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<>(); + +@@ -1132,16 +1133,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + }); + CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { + return either.mapLeft((list) -> { +- return (LevelChunk) list.get(list.size() / 2); +- }); +- }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +- }).thenApplyAsync((either) -> { +- return either.ifLeft((chunk) -> { ++ // Paper start - revert 1.18.2 diff ++ final LevelChunk chunk = (LevelChunk) list.get(list.size() / 2); + chunk.postProcessGeneration(); + this.level.startTickingChunk(chunk); ++ return chunk; + }); +- }, this.mainThreadExecutor); ++ }, (runnable) -> { ++ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.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. ++ }); // Paper end - revert 1.18.2 diff + + completablefuture1.thenAcceptAsync((either) -> { + either.ifLeft((chunk) -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 471cc00c677b6581ba84c8cac25d2246c2a14bc9..497827822a64eeff2a4901f0e7c62f0f2c359b48 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -994,6 +994,7 @@ public class ServerChunkCache extends ChunkSource { + return super.pollTask() || execChunkTask; // Paper + } + } finally { ++ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter + chunkMap.callbackExecutor.run(); + } + // CraftBukkit end diff --git a/patches/server/0398-Fix-Chunk-Post-Processing-deadlock-risk.patch b/patches/server/0398-Fix-Chunk-Post-Processing-deadlock-risk.patch deleted file mode 100644 index 13219b5e56..0000000000 --- a/patches/server/0398-Fix-Chunk-Post-Processing-deadlock-risk.patch +++ /dev/null @@ -1,73 +0,0 @@ -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 boundaries 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 reproducible crash -for heightmaps. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index fcc9dd6e1c54e4ca16102150aa4c12ecc7de06df..aed3da6ef2d498d3f1c9c64177bf1ba6b8157493 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -193,6 +193,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }; - // 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<>(); - -@@ -1132,16 +1133,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - CompletableFuture> completablefuture1 = completablefuture.thenApplyAsync((either) -> { - return either.mapLeft((list) -> { -- return (LevelChunk) list.get(list.size() / 2); -- }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }).thenApplyAsync((either) -> { -- return either.ifLeft((chunk) -> { -+ // Paper start - revert 1.18.2 diff -+ final LevelChunk chunk = (LevelChunk) list.get(list.size() / 2); - chunk.postProcessGeneration(); - this.level.startTickingChunk(chunk); -+ return chunk; - }); -- }, this.mainThreadExecutor); -+ }, (runnable) -> { -+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.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. -+ }); // Paper end - revert 1.18.2 diff - - completablefuture1.thenAcceptAsync((either) -> { - either.ifLeft((chunk) -> { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 471cc00c677b6581ba84c8cac25d2246c2a14bc9..497827822a64eeff2a4901f0e7c62f0f2c359b48 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -994,6 +994,7 @@ public class ServerChunkCache extends ChunkSource { - return super.pollTask() || execChunkTask; // Paper - } - } finally { -+ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter - chunkMap.callbackExecutor.run(); - } - // CraftBukkit end diff --git a/patches/server/0398-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/patches/server/0398-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch new file mode 100644 index 0000000000..de10cb2f1f --- /dev/null +++ b/patches/server/0398-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch @@ -0,0 +1,110 @@ +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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index aed3da6ef2d498d3f1c9c64177bf1ba6b8157493..ea224e68b680c5e9ab38998f7709a4dbe3471b86 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1762,6 +1762,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + .printStackTrace(); + return; + } ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets + // Paper end + if (!(entity instanceof EnderDragonPart)) { + EntityType entitytypes = entity.getType(); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 49cb507ddd378b9a16b82e5c2a28531fce9c6708..53dcb40f5217d560ee42da3d73d1e66e2620067a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -255,6 +255,7 @@ public class ServerPlayer extends Player { + public double maxHealthCache; + public boolean joining = true; + public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper + public Integer clientViewDistance; + public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // 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 602a30f7dc51ffad9e5f5cc0b339108a2225aafd..a7bc7e83b9355ef920d94fff8572528965352fb6 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -282,6 +282,12 @@ public abstract class PlayerList { + this.playersByUUID.put(player.getUUID(), player); + // this.broadcastAll(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 ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); ++ mountSavedVehicle(player, worldserver1, nbttagcompound); ++ // Paper end + // CraftBukkit start + CraftPlayer bukkitPlayer = player.getBukkitEntity(); + +@@ -320,6 +326,8 @@ public abstract class PlayerList { + player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[]{entityplayer1})); + } + player.sentListPacket = true; ++ player.supressTrackerForLogin = false; // Paper ++ ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now + // CraftBukkit end + + player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn +@@ -345,6 +353,11 @@ public abstract class PlayerList { + playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); + } + ++ // Paper start - move vehicle into method so it can be called above - short circuit around that code ++ onPlayerJoinFinish(player, worldserver1, s1); ++ } ++ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { ++ // Paper end + if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) { + CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); + // CraftBukkit start +@@ -393,6 +406,10 @@ public abstract class PlayerList { + } + } + ++ // Paper start ++ } ++ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) { ++ // Paper end + player.initInventoryMenu(); + // CraftBukkit - Moved from above, added world + // Paper start - Add to collideRule team if needed +@@ -402,6 +419,7 @@ public abstract class PlayerList { + scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); + } + // Paper end ++ // CraftBukkit - Moved from above, added world + PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); + } + diff --git a/patches/server/0399-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch b/patches/server/0399-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch deleted file mode 100644 index de10cb2f1f..0000000000 --- a/patches/server/0399-Fix-Longstanding-Broken-behavior-of-PlayerJoinEvent.patch +++ /dev/null @@ -1,110 +0,0 @@ -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/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index aed3da6ef2d498d3f1c9c64177bf1ba6b8157493..ea224e68b680c5e9ab38998f7709a4dbe3471b86 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1762,6 +1762,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - .printStackTrace(); - return; - } -+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets - // Paper end - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 49cb507ddd378b9a16b82e5c2a28531fce9c6708..53dcb40f5217d560ee42da3d73d1e66e2620067a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -255,6 +255,7 @@ public class ServerPlayer extends Player { - public double maxHealthCache; - public boolean joining = true; - public boolean sentListPacket = false; -+ public boolean supressTrackerForLogin = false; // Paper - public Integer clientViewDistance; - public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // 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 602a30f7dc51ffad9e5f5cc0b339108a2225aafd..a7bc7e83b9355ef920d94fff8572528965352fb6 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -282,6 +282,12 @@ public abstract class PlayerList { - this.playersByUUID.put(player.getUUID(), player); - // this.broadcastAll(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 -+ player.supressTrackerForLogin = true; -+ worldserver1.addNewPlayer(player); -+ this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below worldserver.addPlayerJoin(entityplayer); -+ mountSavedVehicle(player, worldserver1, nbttagcompound); -+ // Paper end - // CraftBukkit start - CraftPlayer bukkitPlayer = player.getBukkitEntity(); - -@@ -320,6 +326,8 @@ public abstract class PlayerList { - player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.ADD_PLAYER, new ServerPlayer[]{entityplayer1})); - } - player.sentListPacket = true; -+ player.supressTrackerForLogin = false; // Paper -+ ((ServerLevel)player.level).getChunkSource().chunkMap.addEntity(player); // Paper - track entity now - // CraftBukkit end - - player.connection.send(new ClientboundSetEntityDataPacket(player.getId(), player.getEntityData(), true)); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn -@@ -345,6 +353,11 @@ public abstract class PlayerList { - playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); - } - -+ // Paper start - move vehicle into method so it can be called above - short circuit around that code -+ onPlayerJoinFinish(player, worldserver1, s1); -+ } -+ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { -+ // Paper end - if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) { - CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle"); - // CraftBukkit start -@@ -393,6 +406,10 @@ public abstract class PlayerList { - } - } - -+ // Paper start -+ } -+ public void onPlayerJoinFinish(ServerPlayer player, ServerLevel worldserver1, String s1) { -+ // Paper end - player.initInventoryMenu(); - // CraftBukkit - Moved from above, added world - // Paper start - Add to collideRule team if needed -@@ -402,6 +419,7 @@ public abstract class PlayerList { - scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); - } - // Paper end -+ // CraftBukkit - Moved from above, added world - PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); - } - diff --git a/patches/server/0399-Load-Chunks-for-Login-Asynchronously.patch b/patches/server/0399-Load-Chunks-for-Login-Asynchronously.patch new file mode 100644 index 0000000000..0495ef9ab0 --- /dev/null +++ b/patches/server/0399-Load-Chunks-for-Login-Asynchronously.patch @@ -0,0 +1,280 @@ +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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index f5a9edf47230a1552a36ee5de4cb7560ea9ba7c4..a778b4b5b2413c25c2f0f0efc72ba1d362d89acf 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -170,6 +170,7 @@ import org.bukkit.event.world.GenericGameEvent; + import org.bukkit.event.world.TimeSkipEvent; + // CraftBukkit end + import it.unimi.dsi.fastutil.ints.IntArrayList; // Paper ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper + + public class ServerLevel extends Level implements WorldGenLevel { + +@@ -406,6 +407,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + return this.getServer().getPlayerList().getPlayer(uuid); + } + // Paper end ++ public final ReferenceOpenHashSet pendingLogin = new ReferenceOpenHashSet<>(); // Paper + + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 53dcb40f5217d560ee42da3d73d1e66e2620067a..971f72c1dd713077c128279a78ed37f15aedeff6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -182,6 +182,7 @@ public class ServerPlayer extends Player { + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; + public ServerGamePacketListenerImpl connection; ++ public net.minecraft.network.Connection networkManager; // Paper + public final MinecraftServer server; + public final ServerPlayerGameMode gameMode; + private final PlayerAdvancements advancements; +@@ -256,6 +257,7 @@ public class ServerPlayer extends Player { + public boolean joining = true; + public boolean sentListPacket = false; + public boolean supressTrackerForLogin = false; // Paper ++ public boolean didPlayerJoinEvent = false; // Paper + public Integer clientViewDistance; + public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 478109526cff7ceb0565cea3b5e97b9a07fbf8d1..3c1698ba0d3bc412ab957777d9b5211dbc555208 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 FORCED = TicketType.create("forced", Comparator.comparingLong(ChunkPos::toLong)); + public static final TicketType LIGHT = TicketType.create("light", Comparator.comparingLong(ChunkPos::toLong)); + public static final TicketType PORTAL = TicketType.create("portal", Vec3i::compareTo, 300); ++ public static final TicketType LOGIN = create("login", Long::compareTo, 100); // Paper + public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compareTo, 5); + public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3af9f2d86cf2a9566e22865689101245647d05a5..fe722106e20e199eb914a09f8dbc1409e27f1d69 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -346,6 +346,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + @Override + public void tick() { ++ // Paper start - login async ++ // Don't tick if not valid (dead), otherwise we load chunks below ++ if (this.player.valid) { ++ // Paper end + if (this.ackBlockChangesUpTo > -1) { + this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); + this.ackBlockChangesUpTo = -1; +@@ -392,7 +396,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.lastVehicle = null; + this.clientVehicleIsFloating = false; + this.aboveGroundVehicleTickCount = 0; +- } ++ }} // Paper - end if (valid) + + this.server.getProfiler().push("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/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index c83395364edb4f2ba8515326b19c4f1a436a0502..c99266d4782c5d58339e63f7564c28b4b5b7ac1d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -90,7 +90,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + } + // Paper end + } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { +- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); ++ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper + + if (entityplayer == null) { + this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; +@@ -199,7 +199,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + } + + this.connection.send(new ClientboundGameProfilePacket(this.gameProfile)); +- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); ++ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper + + try { + ServerPlayer entityplayer1 = this.server.getPlayerList().getPlayerForLogin(this.gameProfile, s); // CraftBukkit - add player reference +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a7bc7e83b9355ef920d94fff8572528965352fb6..32ab0cd6cb42b0ab8a14f790dfcf4b155c945d6d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -139,6 +139,7 @@ public abstract class PlayerList { + private final IpBanList ipBans; + private final ServerOpList ops; + private final UserWhiteList whitelist; ++ private final Map pendingPlayers = Maps.newHashMap(); // Paper + // CraftBukkit start + // private final Map stats; + // private final Map advancements; +@@ -180,6 +181,13 @@ public abstract class PlayerList { + public void placeNewPlayer(Connection connection, ServerPlayer player) { + player.isRealPlayer = true; // Paper - Chunk priority + player.loginTime = System.currentTimeMillis(); // Paper ++ // Paper start ++ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player); ++ if (prev != null) { ++ disconnectPendingPlayer(prev); ++ } ++ player.networkManager = connection; ++ // Paper end + GameProfile gameprofile = player.getGameProfile(); + GameProfileCache usercache = this.server.getProfileCache(); + Optional optional = usercache.get(gameprofile.getId()); +@@ -192,7 +200,7 @@ public abstract class PlayerList { + if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { + CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); + s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; +- } ++ }String lastKnownName = s; // Paper + // CraftBukkit end + + if (nbttagcompound != null) { +@@ -219,11 +227,15 @@ public abstract class PlayerList { + if (nbttagcompound == null) player.fudgeSpawnLocation(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... + + player.setLevel(worldserver1); +- String s1 = "local"; ++ // Paper start - make s1 final ++ final String s1; + + if (connection.getRemoteAddress() != null) { + s1 = connection.getRemoteAddress().toString(); ++ } else { ++ s1 = "local"; + } ++ // Paper end + + // Spigot start - spawn location event + Player spawnPlayer = player.getBukkitEntity(); +@@ -265,6 +277,56 @@ public abstract class PlayerList { + player.getRecipeBook().sendInitialRecipeBook(player); + this.updateEntireScoreboard(worldserver1.getScoreboard(), player); + this.server.invalidateStatus(); ++ // Paper start - async load spawn in chunk ++ ServerLevel finalWorldserver = worldserver1; ++ finalWorldserver.pendingLogin.add(player); ++ int chunkX = loc.getBlockX() >> 4; ++ int chunkZ = loc.getBlockZ() >> 4; ++ final net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ); ++ net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; ++ net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager; ++ net.minecraft.server.ChunkSystem.scheduleTickingState( ++ worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true, ++ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST, ++ (chunk) -> { ++ MinecraftServer.getServer().scheduleOnMain(() -> { ++ try { ++ if (!playerconnection.connection.isConnected()) { ++ return; ++ } ++ PlayerList.this.postChunkLoadJoin( ++ player, finalWorldserver, connection, playerconnection, ++ nbttagcompound, s1, lastKnownName ++ ); ++ distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong()); ++ } finally { ++ finalWorldserver.pendingLogin.remove(player); ++ } ++ }); ++ } ++ ); ++ } ++ ++ public ServerPlayer getActivePlayer(UUID uuid) { ++ ServerPlayer player = this.playersByUUID.get(uuid); ++ return player != null ? player : pendingPlayers.get(uuid); ++ } ++ ++ void disconnectPendingPlayer(ServerPlayer entityplayer) { ++ Component msg = Component.translatable("multiplayer.disconnect.duplicate_login"); ++ entityplayer.networkManager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(msg), net.minecraft.network.PacketSendListener.thenRun(() -> { ++ entityplayer.networkManager.disconnect(msg); ++ entityplayer.networkManager = null; ++ })); ++ } ++ ++ private void postChunkLoadJoin(ServerPlayer player, ServerLevel worldserver1, Connection networkmanager, ServerGamePacketListenerImpl playerconnection, CompoundTag nbttagcompound, String s1, String s) { ++ pendingPlayers.remove(player.getUUID(), player); ++ if (!networkmanager.isConnected()) { ++ return; ++ } ++ player.didPlayerJoinEvent = true; ++ // Paper end + MutableComponent ichatmutablecomponent; + + if (player.getGameProfile().getName().equalsIgnoreCase(s)) { +@@ -506,6 +568,7 @@ public abstract class PlayerList { + + protected void save(ServerPlayer player) { + if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit ++ if (!player.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.playerIo.save(player); + ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + +@@ -533,7 +596,7 @@ public abstract class PlayerList { + } + + PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); +- this.cserver.getPluginManager().callEvent(playerQuitEvent); ++ if (entityplayer.didPlayerJoinEvent) this.cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + + if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) +@@ -578,6 +641,13 @@ public abstract class PlayerList { + // this.advancements.remove(uuid); + // CraftBukkit end + } ++ // Paper start ++ entityplayer1 = pendingPlayers.get(uuid); ++ if (entityplayer1 == entityplayer) { ++ pendingPlayers.remove(uuid); ++ } ++ entityplayer.networkManager = null; ++ // Paper end + + // CraftBukkit start + // this.broadcastAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer})); +@@ -595,7 +665,7 @@ public abstract class PlayerList { + this.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 +@@ -614,6 +684,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/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4016b31bd020e00c0e79328646f9b5411b312e88..c8c11ec3c5c4c4d1ed09163aa6d3a4275e497e11 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1219,7 +1219,7 @@ public final class CraftServer implements Server { + return false; + } + +- if (handle.players().size() > 0) { ++ if (handle.players().size() > 0 || handle.pendingLogin.size() > 0) { // Paper + return false; + } + diff --git a/patches/server/0400-Load-Chunks-for-Login-Asynchronously.patch b/patches/server/0400-Load-Chunks-for-Login-Asynchronously.patch deleted file mode 100644 index 8b3c658648..0000000000 --- a/patches/server/0400-Load-Chunks-for-Login-Asynchronously.patch +++ /dev/null @@ -1,280 +0,0 @@ -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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index f5a9edf47230a1552a36ee5de4cb7560ea9ba7c4..a778b4b5b2413c25c2f0f0efc72ba1d362d89acf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -170,6 +170,7 @@ import org.bukkit.event.world.GenericGameEvent; - import org.bukkit.event.world.TimeSkipEvent; - // CraftBukkit end - import it.unimi.dsi.fastutil.ints.IntArrayList; // Paper -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper - - public class ServerLevel extends Level implements WorldGenLevel { - -@@ -406,6 +407,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - return this.getServer().getPlayerList().getPlayer(uuid); - } - // Paper end -+ public final ReferenceOpenHashSet pendingLogin = new ReferenceOpenHashSet<>(); // Paper - - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 53dcb40f5217d560ee42da3d73d1e66e2620067a..971f72c1dd713077c128279a78ed37f15aedeff6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -182,6 +182,7 @@ public class ServerPlayer extends Player { - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; - public ServerGamePacketListenerImpl connection; -+ public net.minecraft.network.Connection networkManager; // Paper - public final MinecraftServer server; - public final ServerPlayerGameMode gameMode; - private final PlayerAdvancements advancements; -@@ -256,6 +257,7 @@ public class ServerPlayer extends Player { - public boolean joining = true; - public boolean sentListPacket = false; - public boolean supressTrackerForLogin = false; // Paper -+ public boolean didPlayerJoinEvent = false; // Paper - public Integer clientViewDistance; - public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 478109526cff7ceb0565cea3b5e97b9a07fbf8d1..3c1698ba0d3bc412ab957777d9b5211dbc555208 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 FORCED = TicketType.create("forced", Comparator.comparingLong(ChunkPos::toLong)); - public static final TicketType LIGHT = TicketType.create("light", Comparator.comparingLong(ChunkPos::toLong)); - public static final TicketType PORTAL = TicketType.create("portal", Vec3i::compareTo, 300); -+ public static final TicketType LOGIN = create("login", Long::compareTo, 100); // Paper - public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compareTo, 5); - public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0df93b204ddf55a2a3b8af33d6a3273697eea91e..88d4c2a9c628d32f1633a376eff37c3e6ae33a72 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -346,6 +346,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - @Override - public void tick() { -+ // Paper start - login async -+ // Don't tick if not valid (dead), otherwise we load chunks below -+ if (this.player.valid) { -+ // Paper end - if (this.ackBlockChangesUpTo > -1) { - this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); - this.ackBlockChangesUpTo = -1; -@@ -392,7 +396,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.lastVehicle = null; - this.clientVehicleIsFloating = false; - this.aboveGroundVehicleTickCount = 0; -- } -+ }} // Paper - end if (valid) - - this.server.getProfiler().push("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/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index c83395364edb4f2ba8515326b19c4f1a436a0502..c99266d4782c5d58339e63f7564c28b4b5b7ac1d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -90,7 +90,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - } - // Paper end - } else if (this.state == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) { -- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); -+ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper - - if (entityplayer == null) { - this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; -@@ -199,7 +199,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - } - - this.connection.send(new ClientboundGameProfilePacket(this.gameProfile)); -- ServerPlayer entityplayer = this.server.getPlayerList().getPlayer(this.gameProfile.getId()); -+ ServerPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.gameProfile.getId()); // Paper - - try { - ServerPlayer entityplayer1 = this.server.getPlayerList().getPlayerForLogin(this.gameProfile, s); // CraftBukkit - add player reference -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index a7bc7e83b9355ef920d94fff8572528965352fb6..32ab0cd6cb42b0ab8a14f790dfcf4b155c945d6d 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -139,6 +139,7 @@ public abstract class PlayerList { - private final IpBanList ipBans; - private final ServerOpList ops; - private final UserWhiteList whitelist; -+ private final Map pendingPlayers = Maps.newHashMap(); // Paper - // CraftBukkit start - // private final Map stats; - // private final Map advancements; -@@ -180,6 +181,13 @@ public abstract class PlayerList { - public void placeNewPlayer(Connection connection, ServerPlayer player) { - player.isRealPlayer = true; // Paper - Chunk priority - player.loginTime = System.currentTimeMillis(); // Paper -+ // Paper start -+ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player); -+ if (prev != null) { -+ disconnectPendingPlayer(prev); -+ } -+ player.networkManager = connection; -+ // Paper end - GameProfile gameprofile = player.getGameProfile(); - GameProfileCache usercache = this.server.getProfileCache(); - Optional optional = usercache.get(gameprofile.getId()); -@@ -192,7 +200,7 @@ public abstract class PlayerList { - if (nbttagcompound != null && nbttagcompound.contains("bukkit")) { - CompoundTag bukkit = nbttagcompound.getCompound("bukkit"); - s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s; -- } -+ }String lastKnownName = s; // Paper - // CraftBukkit end - - if (nbttagcompound != null) { -@@ -219,11 +227,15 @@ public abstract class PlayerList { - if (nbttagcompound == null) player.fudgeSpawnLocation(worldserver1); // Paper - only move to spawn on first login, otherwise, stay where you are.... - - player.setLevel(worldserver1); -- String s1 = "local"; -+ // Paper start - make s1 final -+ final String s1; - - if (connection.getRemoteAddress() != null) { - s1 = connection.getRemoteAddress().toString(); -+ } else { -+ s1 = "local"; - } -+ // Paper end - - // Spigot start - spawn location event - Player spawnPlayer = player.getBukkitEntity(); -@@ -265,6 +277,56 @@ public abstract class PlayerList { - player.getRecipeBook().sendInitialRecipeBook(player); - this.updateEntireScoreboard(worldserver1.getScoreboard(), player); - this.server.invalidateStatus(); -+ // Paper start - async load spawn in chunk -+ ServerLevel finalWorldserver = worldserver1; -+ finalWorldserver.pendingLogin.add(player); -+ int chunkX = loc.getBlockX() >> 4; -+ int chunkZ = loc.getBlockZ() >> 4; -+ final net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ); -+ net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap; -+ net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager; -+ net.minecraft.server.ChunkSystem.scheduleTickingState( -+ worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST, -+ (chunk) -> { -+ MinecraftServer.getServer().scheduleOnMain(() -> { -+ try { -+ if (!playerconnection.connection.isConnected()) { -+ return; -+ } -+ PlayerList.this.postChunkLoadJoin( -+ player, finalWorldserver, connection, playerconnection, -+ nbttagcompound, s1, lastKnownName -+ ); -+ distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong()); -+ } finally { -+ finalWorldserver.pendingLogin.remove(player); -+ } -+ }); -+ } -+ ); -+ } -+ -+ public ServerPlayer getActivePlayer(UUID uuid) { -+ ServerPlayer player = this.playersByUUID.get(uuid); -+ return player != null ? player : pendingPlayers.get(uuid); -+ } -+ -+ void disconnectPendingPlayer(ServerPlayer entityplayer) { -+ Component msg = Component.translatable("multiplayer.disconnect.duplicate_login"); -+ entityplayer.networkManager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(msg), net.minecraft.network.PacketSendListener.thenRun(() -> { -+ entityplayer.networkManager.disconnect(msg); -+ entityplayer.networkManager = null; -+ })); -+ } -+ -+ private void postChunkLoadJoin(ServerPlayer player, ServerLevel worldserver1, Connection networkmanager, ServerGamePacketListenerImpl playerconnection, CompoundTag nbttagcompound, String s1, String s) { -+ pendingPlayers.remove(player.getUUID(), player); -+ if (!networkmanager.isConnected()) { -+ return; -+ } -+ player.didPlayerJoinEvent = true; -+ // Paper end - MutableComponent ichatmutablecomponent; - - if (player.getGameProfile().getName().equalsIgnoreCase(s)) { -@@ -506,6 +568,7 @@ public abstract class PlayerList { - - protected void save(ServerPlayer player) { - if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit -+ if (!player.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.playerIo.save(player); - ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit - -@@ -533,7 +596,7 @@ public abstract class PlayerList { - } - - PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); -- this.cserver.getPluginManager().callEvent(playerQuitEvent); -+ if (entityplayer.didPlayerJoinEvent) this.cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit - entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); - - if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog) -@@ -578,6 +641,13 @@ public abstract class PlayerList { - // this.advancements.remove(uuid); - // CraftBukkit end - } -+ // Paper start -+ entityplayer1 = pendingPlayers.get(uuid); -+ if (entityplayer1 == entityplayer) { -+ pendingPlayers.remove(uuid); -+ } -+ entityplayer.networkManager = null; -+ // Paper end - - // CraftBukkit start - // this.broadcastAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer})); -@@ -595,7 +665,7 @@ public abstract class PlayerList { - this.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 -@@ -614,6 +684,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/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4016b31bd020e00c0e79328646f9b5411b312e88..c8c11ec3c5c4c4d1ed09163aa6d3a4275e497e11 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1219,7 +1219,7 @@ public final class CraftServer implements Server { - return false; - } - -- if (handle.players().size() > 0) { -+ if (handle.players().size() > 0 || handle.pendingLogin.size() > 0) { // Paper - return false; - } - diff --git a/patches/server/0400-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0400-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch new file mode 100644 index 0000000000..bd671c5504 --- /dev/null +++ b/patches/server/0400-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 a1f176432b366b5f106a32ca571d7ea617b19fd3..69aef07129201308ae275434b754977c6ece5dd7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2110,9 +2110,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + bworld = server.getWorld(worldName); + } + +- if (bworld == null) { +- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.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 + + ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); + } diff --git a/patches/server/0401-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0401-Add-PlayerAttackEntityCooldownResetEvent.patch new file mode 100644 index 0000000000..9c571cf10f --- /dev/null +++ b/patches/server/0401-Add-PlayerAttackEntityCooldownResetEvent.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nossr50 +Date: Thu, 26 Mar 2020 19:44:50 -0700 +Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 9a7d2b0d84ce422afa96b5c07c972e52c6ac00ed..e3d686ea473c1bd38af9ed181020110ee3d94f64 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2112,7 +2112,16 @@ public abstract class LivingEntity 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 net.minecraft.world.entity.player.Player) { +- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired ++ // Paper start - PlayerAttackEntityCooldownResetEvent ++ if (damagesource.getEntity() instanceof ServerPlayer) { ++ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); ++ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { ++ player.resetAttackStrengthTicker(); ++ } ++ } else { ++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); ++ } ++ // Paper end + } + if (event.isCancelled()) { + return false; diff --git a/patches/server/0401-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch b/patches/server/0401-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch deleted file mode 100644 index 0a1150e1ea..0000000000 --- a/patches/server/0401-Move-player-to-spawn-point-if-spawn-in-unloaded-worl.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: 2277 <38501234+2277@users.noreply.github.com> -Date: Tue, 31 Mar 2020 10:33:55 +0100 -Subject: [PATCH] Move player to spawn point if spawn in unloaded world - -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 001c1d55218671eaa9cee28ae42d756f352ff2fa..1b8f7bd5dd85136788b02cef64353d581cdf2108 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2096,9 +2096,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - bworld = server.getWorld(worldName); - } - -- if (bworld == null) { -- bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.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 - - ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle()); - } diff --git a/patches/server/0402-Add-PlayerAttackEntityCooldownResetEvent.patch b/patches/server/0402-Add-PlayerAttackEntityCooldownResetEvent.patch deleted file mode 100644 index 98d462b590..0000000000 --- a/patches/server/0402-Add-PlayerAttackEntityCooldownResetEvent.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nossr50 -Date: Thu, 26 Mar 2020 19:44:50 -0700 -Subject: [PATCH] Add PlayerAttackEntityCooldownResetEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b3b3b163fb1a506fb048dba173517eeeaf2f8007..1eb53dd9c0ddef725e96aa7d5f6e335fb48eec9e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2090,7 +2090,16 @@ public abstract class LivingEntity 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 net.minecraft.world.entity.player.Player) { -- ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired -+ // Paper start - PlayerAttackEntityCooldownResetEvent -+ if (damagesource.getEntity() instanceof ServerPlayer) { -+ ServerPlayer player = (ServerPlayer) damagesource.getEntity(); -+ if (new com.destroystokyo.paper.event.player.PlayerAttackEntityCooldownResetEvent(player.getBukkitEntity(), this.getBukkitEntity(), player.getAttackStrengthScale(0F)).callEvent()) { -+ player.resetAttackStrengthTicker(); -+ } -+ } else { -+ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); -+ } -+ // Paper end - } - if (event.isCancelled()) { - return false; diff --git a/patches/server/0402-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0402-Don-t-fire-BlockFade-on-worldgen-threads.patch new file mode 100644 index 0000000000..b9aecc5313 --- /dev/null +++ b/patches/server/0402-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/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 2188cfc34ab4bd67fac9aedd861a597c137a5c40..5ce5902b13ebb9438433d189f2c03677e4cb54b3 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -100,6 +100,7 @@ public class FireBlock extends BaseFireBlock { + @Override + public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { + // CraftBukkit start ++ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation + if (!this.canSurvive(state, world, pos)) { + // Suppress during worldgen + if (!(world instanceof Level)) { +@@ -115,7 +116,7 @@ public class FireBlock extends BaseFireBlock { + return blockState.getHandle(); + } + } +- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); ++ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - diff on change, see "don't fire events in world generation" + // CraftBukkit end + } + diff --git a/patches/server/0403-Add-phantom-creative-and-insomniac-controls.patch b/patches/server/0403-Add-phantom-creative-and-insomniac-controls.patch new file mode 100644 index 0000000000..3b9eaafdbf --- /dev/null +++ b/patches/server/0403-Add-phantom-creative-and-insomniac-controls.patch @@ -0,0 +1,43 @@ +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/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index b91a61be7c4829fce0ff8da290eab580e20bb78d..22f36cd3df49160f1b6668befdd05c2268edaa49 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -27,6 +27,7 @@ public final class EntitySelector { + return !entity.isSpectator(); + }; + public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); ++ public static Predicate isInsomniac = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper + + private EntitySelector() {} + // Paper start +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index 84400bb44d5deb7c79295a83c4c3c6aac88f3175..180bcdeb262d61c56193dbf99f1c11f3a6889145 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -558,6 +558,7 @@ public class Phantom extends FlyingMob implements Enemy { + Player entityhuman = (Player) iterator.next(); + + if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) { ++ if (!level.paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.isInsomniac.test(entityhuman)) // Paper + Phantom.this.setTarget(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/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +index e9d25aef08103ccdbc6a35c3ab67c1d921e9f45d..6b5c31470499e25d01936106839c2fff21b113c8 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +@@ -51,7 +51,7 @@ public class PhantomSpawner implements CustomSpawner { + while (iterator.hasNext()) { + Player entityhuman = (Player) iterator.next(); + +- if (!entityhuman.isSpectator()) { ++ if (!entityhuman.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityhuman.isCreative())) { // Paper + BlockPos blockposition = entityhuman.blockPosition(); + + if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { diff --git a/patches/server/0403-Don-t-fire-BlockFade-on-worldgen-threads.patch b/patches/server/0403-Don-t-fire-BlockFade-on-worldgen-threads.patch deleted file mode 100644 index b9aecc5313..0000000000 --- a/patches/server/0403-Don-t-fire-BlockFade-on-worldgen-threads.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Thu, 23 Apr 2020 01:36:39 -0400 -Subject: [PATCH] Don't fire BlockFade on worldgen threads - -Caused a deadlock - -diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index 2188cfc34ab4bd67fac9aedd861a597c137a5c40..5ce5902b13ebb9438433d189f2c03677e4cb54b3 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -100,6 +100,7 @@ public class FireBlock extends BaseFireBlock { - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - // CraftBukkit start -+ if (!(world instanceof ServerLevel)) return this.canSurvive(state, world, pos) ? (BlockState) this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState(); // Paper - don't fire events in world generation - if (!this.canSurvive(state, world, pos)) { - // Suppress during worldgen - if (!(world instanceof Level)) { -@@ -115,7 +116,7 @@ public class FireBlock extends BaseFireBlock { - return blockState.getHandle(); - } - } -- return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); -+ return this.getStateWithAge(world, pos, (Integer) state.getValue(FireBlock.AGE)); // Paper - diff on change, see "don't fire events in world generation" - // CraftBukkit end - } - diff --git a/patches/server/0404-Add-phantom-creative-and-insomniac-controls.patch b/patches/server/0404-Add-phantom-creative-and-insomniac-controls.patch deleted file mode 100644 index 3b9eaafdbf..0000000000 --- a/patches/server/0404-Add-phantom-creative-and-insomniac-controls.patch +++ /dev/null @@ -1,43 +0,0 @@ -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/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index b91a61be7c4829fce0ff8da290eab580e20bb78d..22f36cd3df49160f1b6668befdd05c2268edaa49 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -27,6 +27,7 @@ public final class EntitySelector { - return !entity.isSpectator(); - }; - public static final Predicate CAN_BE_COLLIDED_WITH = EntitySelector.NO_SPECTATORS.and(Entity::canBeCollidedWith); -+ public static Predicate isInsomniac = (player) -> net.minecraft.util.Mth.clamp(((net.minecraft.server.level.ServerPlayer) player).getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper - - private EntitySelector() {} - // Paper start -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index 84400bb44d5deb7c79295a83c4c3c6aac88f3175..180bcdeb262d61c56193dbf99f1c11f3a6889145 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -558,6 +558,7 @@ public class Phantom extends FlyingMob implements Enemy { - Player entityhuman = (Player) iterator.next(); - - if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) { -+ if (!level.paperConfig().entities.behavior.phantomsOnlyAttackInsomniacs || EntitySelector.isInsomniac.test(entityhuman)) // Paper - Phantom.this.setTarget(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/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index e9d25aef08103ccdbc6a35c3ab67c1d921e9f45d..6b5c31470499e25d01936106839c2fff21b113c8 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -51,7 +51,7 @@ public class PhantomSpawner implements CustomSpawner { - while (iterator.hasNext()) { - Player entityhuman = (Player) iterator.next(); - -- if (!entityhuman.isSpectator()) { -+ if (!entityhuman.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityhuman.isCreative())) { // Paper - BlockPos blockposition = entityhuman.blockPosition(); - - if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { diff --git a/patches/server/0404-Fix-numerous-item-duplication-issues-and-teleport-is.patch b/patches/server/0404-Fix-numerous-item-duplication-issues-and-teleport-is.patch new file mode 100644 index 0000000000..2cb660b24c --- /dev/null +++ b/patches/server/0404-Fix-numerous-item-duplication-issues-and-teleport-is.patch @@ -0,0 +1,167 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 25 Apr 2020 06:46:35 -0400 +Subject: [PATCH] Fix 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 69aef07129201308ae275434b754977c6ece5dd7..8bdcf56a5a1c29f78e10f66de5adf5a3786ac092 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2240,11 +2240,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { +- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); ++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later + return null; + } + // CraftBukkit end +- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); ++ ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - clone so we can destroy original ++ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); + // CraftBukkit start +@@ -3008,6 +3009,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + @Nullable + public Entity teleportTo(ServerLevel worldserver, PositionImpl 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.level instanceof ServerLevel && !this.isRemoved()) { + this.level.getProfiler().push("changeDimension"); + // CraftBukkit start +@@ -3034,6 +3041,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // CraftBukkit end + + this.level.getProfiler().popPush("reloading"); ++ // Paper start - Change lead drop timing to prevent dupe ++ if (this instanceof Mob) { ++ ((Mob) this).dropLeash(true, true); // Paper drop lead ++ } ++ // Paper end + Entity entity = this.getType().create(worldserver); + + if (entity != null) { +@@ -3047,10 +3059,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // CraftBukkit start - Forward the CraftEntity to the new entity + this.getBukkitEntity().setHandle(entity); + entity.bukkitEntity = this.getBukkitEntity(); +- +- if (this instanceof Mob) { +- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads. +- } + // CraftBukkit end + } + +@@ -3171,7 +3179,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public boolean canChangeDimensions() { +- return true; ++ return isAlive() && valid; // Paper + } + + public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e3d686ea473c1bd38af9ed181020110ee3d94f64..37d51104a7d38c2d16ae38a9adcbe37597c94fe2 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1643,9 +1643,9 @@ public abstract class LivingEntity extends Entity { + // Paper start + org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); + if (deathEvent == null || !deathEvent.isCancelled()) { +- if (this.deathScore >= 0 && entityliving != null) { +- entityliving.awardKillScore(this, this.deathScore, damageSource); +- } ++ // if (this.deathScore >= 0 && entityliving != null) { // Paper moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent ++ // entityliving.awardKillScore(this, this.deathScore, damageSource); ++ // } + // Paper start - clear equipment if event is not cancelled + if (this instanceof Mob) { + for (EquipmentSlot slot : this.clearedEquipmentSlots) { +@@ -1743,8 +1743,13 @@ public abstract class LivingEntity extends Entity { + this.dropCustomDeathLoot(source, i, flag); + this.clearEquipmentSlots = prev; // Paper + } +- // CraftBukkit start - Call death event +- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper ++ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> { ++ final LivingEntity entityliving = this.getKillCredit(); ++ if (this.deathScore >= 0 && entityliving != null) { ++ entityliving.awardKillScore(this, this.deathScore, source); ++ } ++ }); // Paper end + this.postDeathDropItems(deathEvent); // Paper + this.drops = new ArrayList<>(); + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index cd54fa8f7bbcb6036e90f4ef7cdc01d7af835a13..808e564789d826c1778c053ab91038e3d4d81b7f 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -608,7 +608,7 @@ public class ArmorStand extends LivingEntity { + 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.EMPTY); + } + } +@@ -616,7 +616,7 @@ public class ArmorStand extends LivingEntity { + 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.EMPTY); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 3aa82c7d26ef9fed3d4b670ac330204b55609396..22da112f45ddb20d113550eae67ac08eb2fcb727 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -819,6 +819,11 @@ public class CraftEventFactory { + } + + public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { ++ // Paper start ++ return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); ++ } ++ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { ++ // Paper end + CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); + EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); + populateFields(victim, event); // Paper - make cancellable +@@ -832,11 +837,13 @@ public class CraftEventFactory { + playDeathSound(victim, event); + // Paper end + victim.expToDrop = event.getDroppedExp(); ++ lootCheck.run(); // Paper - advancement triggers before destroying items + + for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { + if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; + +- world.dropItem(entity.getLocation(), stack); ++ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS ++ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items + } + + return event; diff --git a/patches/server/0405-Fix-numerous-item-duplication-issues-and-teleport-is.patch b/patches/server/0405-Fix-numerous-item-duplication-issues-and-teleport-is.patch deleted file mode 100644 index e5cb8f7d91..0000000000 --- a/patches/server/0405-Fix-numerous-item-duplication-issues-and-teleport-is.patch +++ /dev/null @@ -1,167 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 25 Apr 2020 06:46:35 -0400 -Subject: [PATCH] Fix 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 1b8f7bd5dd85136788b02cef64353d581cdf2108..74e13ae8b1e6a9365d46f5684ee58e42658d2341 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2226,11 +2226,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } else { - // CraftBukkit start - Capture drops for death event - if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) { -- ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack)); -+ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack)); // Paper - mirror so we can destroy it later - return null; - } - // CraftBukkit end -- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack); -+ ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY() + (double) yOffset, this.getZ(), stack.copy()); // Paper - clone so we can destroy original -+ stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); - // CraftBukkit start -@@ -2994,6 +2995,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - @Nullable - public Entity teleportTo(ServerLevel worldserver, PositionImpl 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.level instanceof ServerLevel && !this.isRemoved()) { - this.level.getProfiler().push("changeDimension"); - // CraftBukkit start -@@ -3020,6 +3027,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - // CraftBukkit end - - this.level.getProfiler().popPush("reloading"); -+ // Paper start - Change lead drop timing to prevent dupe -+ if (this instanceof Mob) { -+ ((Mob) this).dropLeash(true, true); // Paper drop lead -+ } -+ // Paper end - Entity entity = this.getType().create(worldserver); - - if (entity != null) { -@@ -3033,10 +3045,6 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - // CraftBukkit start - Forward the CraftEntity to the new entity - this.getBukkitEntity().setHandle(entity); - entity.bukkitEntity = this.getBukkitEntity(); -- -- if (this instanceof Mob) { -- ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads. -- } - // CraftBukkit end - } - -@@ -3157,7 +3165,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public boolean canChangeDimensions() { -- return true; -+ return isAlive() && valid; // Paper - } - - public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 1eb53dd9c0ddef725e96aa7d5f6e335fb48eec9e..4a2a8566c9d68f21a98774fcecac0f4fa43d88c4 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1643,9 +1643,9 @@ public abstract class LivingEntity extends Entity { - // Paper start - org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); - if (deathEvent == null || !deathEvent.isCancelled()) { -- if (this.deathScore >= 0 && entityliving != null) { -- entityliving.awardKillScore(this, this.deathScore, damageSource); -- } -+ // if (this.deathScore >= 0 && entityliving != null) { // Paper moved to be run earlier in #dropAllDeathLoot before destroying the drop items in CraftEventFactory#callEntityDeathEvent -+ // entityliving.awardKillScore(this, this.deathScore, damageSource); -+ // } - // Paper start - clear equipment if event is not cancelled - if (this instanceof Mob) { - for (EquipmentSlot slot : this.clearedEquipmentSlots) { -@@ -1743,8 +1743,13 @@ public abstract class LivingEntity extends Entity { - this.dropCustomDeathLoot(source, i, flag); - this.clearEquipmentSlots = prev; // Paper - } -- // CraftBukkit start - Call death event -- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper -+ // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> { -+ final LivingEntity entityliving = this.getKillCredit(); -+ if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, source); -+ } -+ }); // Paper end - this.postDeathDropItems(deathEvent); // Paper - this.drops = new ArrayList<>(); - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index cd54fa8f7bbcb6036e90f4ef7cdc01d7af835a13..808e564789d826c1778c053ab91038e3d4d81b7f 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -608,7 +608,7 @@ public class ArmorStand extends LivingEntity { - 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.EMPTY); - } - } -@@ -616,7 +616,7 @@ public class ArmorStand extends LivingEntity { - 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.EMPTY); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 3aa82c7d26ef9fed3d4b670ac330204b55609396..22da112f45ddb20d113550eae67ac08eb2fcb727 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -819,6 +819,11 @@ public class CraftEventFactory { - } - - public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops) { -+ // Paper start -+ return CraftEventFactory.callEntityDeathEvent(victim, drops, com.google.common.util.concurrent.Runnables.doNothing()); -+ } -+ public static EntityDeathEvent callEntityDeathEvent(net.minecraft.world.entity.LivingEntity victim, List drops, Runnable lootCheck) { -+ // Paper end - CraftLivingEntity entity = (CraftLivingEntity) victim.getBukkitEntity(); - EntityDeathEvent event = new EntityDeathEvent(entity, drops, victim.getExpReward()); - populateFields(victim, event); // Paper - make cancellable -@@ -832,11 +837,13 @@ public class CraftEventFactory { - playDeathSound(victim, event); - // Paper end - victim.expToDrop = event.getDroppedExp(); -+ lootCheck.run(); // Paper - advancement triggers before destroying items - - for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { - if (stack == null || stack.getType() == Material.AIR || stack.getAmount() == 0) continue; - -- world.dropItem(entity.getLocation(), stack); -+ world.dropItem(entity.getLocation(), stack); // Paper - note: dropItem already clones due to this being bukkit -> NMS -+ if (stack instanceof CraftItemStack) stack.setAmount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe, but don't nuke bukkit stacks of manually added items - } - - return event; diff --git a/patches/server/0405-Villager-Restocks-API.patch b/patches/server/0405-Villager-Restocks-API.patch new file mode 100644 index 0000000000..d4af2305a8 --- /dev/null +++ b/patches/server/0405-Villager-Restocks-API.patch @@ -0,0 +1,29 @@ +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/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 1400f8c0cac3d653465b3750078de4d2691ac2a1..1a8a49bd269ed52879866ff3853e131d04aa8bba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +@@ -91,6 +91,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + this.getHandle().setVillagerXp(experience); + } + ++ // Paper start ++ @Override ++ public int getRestocksToday() { ++ return getHandle().numberOfRestocksToday; ++ } ++ ++ @Override ++ public void setRestocksToday(int restocksToday) { ++ getHandle().numberOfRestocksToday = restocksToday; ++ } ++ // Paper end ++ + @Override + public boolean sleep(Location location) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0406-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0406-Validate-PickItem-Packet-and-kick-for-invalid.patch new file mode 100644 index 0000000000..a84ea8c5a4 --- /dev/null +++ b/patches/server/0406-Validate-PickItem-Packet-and-kick-for-invalid.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 2 May 2020 03:09:46 -0400 +Subject: [PATCH] Validate PickItem Packet and kick for invalid + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index fe722106e20e199eb914a09f8dbc1409e27f1d69..77cc62cc942687a40371741904525301a4ed5240 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -967,7 +967,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + @Override + public void handlePickItem(ServerboundPickItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +- this.player.getInventory().pickSlot(packet.getSlot()); ++ // Paper start - validate pick item position ++ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { ++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); ++ return; ++ } ++ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed ++ // Paper end + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); + this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); diff --git a/patches/server/0406-Villager-Restocks-API.patch b/patches/server/0406-Villager-Restocks-API.patch deleted file mode 100644 index d4af2305a8..0000000000 --- a/patches/server/0406-Villager-Restocks-API.patch +++ /dev/null @@ -1,29 +0,0 @@ -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/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 1400f8c0cac3d653465b3750078de4d2691ac2a1..1a8a49bd269ed52879866ff3853e131d04aa8bba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -91,6 +91,18 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - this.getHandle().setVillagerXp(experience); - } - -+ // Paper start -+ @Override -+ public int getRestocksToday() { -+ return getHandle().numberOfRestocksToday; -+ } -+ -+ @Override -+ public void setRestocksToday(int restocksToday) { -+ getHandle().numberOfRestocksToday = restocksToday; -+ } -+ // Paper end -+ - @Override - public boolean sleep(Location location) { - Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0407-Expose-game-version.patch b/patches/server/0407-Expose-game-version.patch new file mode 100644 index 0000000000..ba5a794206 --- /dev/null +++ b/patches/server/0407-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 c8c11ec3c5c4c4d1ed09163aa6d3a4275e497e11..aac16630c83034cdb1ce14e04bfc24c39ff65454 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -579,6 +579,13 @@ public final class CraftServer implements Server { + return this.bukkitVersion; + } + ++ // Paper start - expose game version ++ @Override ++ public String getMinecraftVersion() { ++ return console.getServerVersion(); ++ } ++ // Paper end ++ + @Override + public List getOnlinePlayers() { + return this.playerView; diff --git a/patches/server/0407-Validate-PickItem-Packet-and-kick-for-invalid.patch b/patches/server/0407-Validate-PickItem-Packet-and-kick-for-invalid.patch deleted file mode 100644 index 73612dfd5b..0000000000 --- a/patches/server/0407-Validate-PickItem-Packet-and-kick-for-invalid.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 2 May 2020 03:09:46 -0400 -Subject: [PATCH] Validate PickItem Packet and kick for invalid - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 88d4c2a9c628d32f1633a376eff37c3e6ae33a72..dac18223143ea10054787f8954744311be7df7aa 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -967,7 +967,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - @Override - public void handlePickItem(ServerboundPickItemPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); -- this.player.getInventory().pickSlot(packet.getSlot()); -+ // Paper start - validate pick item position -+ if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -+ this.disconnect("Invalid hotbar selection (Hacking?)"); -+ return; -+ } -+ this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -+ // Paper end - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); - this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); - this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); diff --git a/patches/server/0408-Expose-game-version.patch b/patches/server/0408-Expose-game-version.patch deleted file mode 100644 index ba5a794206..0000000000 --- a/patches/server/0408-Expose-game-version.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Fri, 1 May 2020 17:39:26 +0300 -Subject: [PATCH] Expose game version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index c8c11ec3c5c4c4d1ed09163aa6d3a4275e497e11..aac16630c83034cdb1ce14e04bfc24c39ff65454 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -579,6 +579,13 @@ public final class CraftServer implements Server { - return this.bukkitVersion; - } - -+ // Paper start - expose game version -+ @Override -+ public String getMinecraftVersion() { -+ return console.getServerVersion(); -+ } -+ // Paper end -+ - @Override - public List getOnlinePlayers() { - return this.playerView; diff --git a/patches/server/0408-Optimize-Voxel-Shape-Merging.patch b/patches/server/0408-Optimize-Voxel-Shape-Merging.patch new file mode 100644 index 0000000000..7bfe849396 --- /dev/null +++ b/patches/server/0408-Optimize-Voxel-Shape-Merging.patch @@ -0,0 +1,121 @@ +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/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +index 9e0afab2329e560c4b2512548dd4b02dd1a2e69f..06662dbff8180751a8684841aa35f709007078ae 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java +@@ -10,12 +10,33 @@ public class IndirectMerger implements IndexMerger { + private final int[] firstIndices; + private final int[] secondIndices; + private final int resultLength; ++ // Paper start ++ private static final int[] INFINITE_B_1 = new int[]{1, 1}; ++ private static final int[] INFINITE_B_0 = new int[]{0, 0}; ++ private static final int[] INFINITE_C = new int[]{0, 1}; ++ // Paper end + + public IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) { + double d = Double.NaN; + int i = first.size(); + int j = second.size(); + int k = i + j; ++ // Paper start - optimize common path of infinity doublelist ++ int size = first.size(); ++ double tail = first.getDouble(size - 1); ++ double head = first.getDouble(0); ++ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) { ++ this.result = second.toDoubleArray(); ++ this.resultLength = second.size(); ++ if (size == 2) { ++ this.firstIndices = INFINITE_B_0; ++ } else { ++ this.firstIndices = INFINITE_B_1; ++ } ++ this.secondIndices = INFINITE_C; ++ return; ++ } ++ // Paper end + this.result = new double[k]; + this.firstIndices = new int[k]; + this.secondIndices = new int[k]; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 2d273be8145bbd86ffdf33358629da7fc08b4d4c..9176735c08a75854209f24113b0e78332249dc4d 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -247,9 +247,21 @@ public final class Shapes { + } + + @VisibleForTesting +- protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { ++ private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // 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 (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) { ++ return new IndirectMerger(first, second, includeFirst, includeSecond); ++ } ++ // Split out rest to hopefully inline the above ++ return lessCommonMerge(size, first, second, includeFirst, includeSecond); ++ } ++ ++ private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { + int i = first.size() - 1; + int j = second.size() - 1; ++ // Paper note - Rewrite below as optimized order if instead of nasty ternary + if (first instanceof CubePointRange && second instanceof CubePointRange) { + long l = lcm(i, j); + if ((long)size * l <= 256L) { +@@ -257,13 +269,22 @@ public final class Shapes { + } + } + +- if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) { ++ // Paper start - Identical happens more often than Disjoint ++ if (i == j && Objects.equals(first, second)) { ++ if (first instanceof IdenticalMerger) { ++ return (IndexMerger) first; ++ } else if (second instanceof IdenticalMerger) { ++ return (IndexMerger) second; ++ } ++ return new IdenticalMerger(first); ++ } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) { + return new NonOverlappingMerger(first, second, false); + } else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7D) { + return new NonOverlappingMerger(second, first, true); + } else { +- return (IndexMerger)(i == j && Objects.equals(first, second) ? new IdenticalMerger(first) : new IndirectMerger(first, second, includeFirst, includeSecond)); ++ return new IndirectMerger(first, second, includeFirst, includeSecond); + } ++ // Paper end + } + + public interface DoubleLineConsumer { diff --git a/patches/server/0409-Optimize-Voxel-Shape-Merging.patch b/patches/server/0409-Optimize-Voxel-Shape-Merging.patch deleted file mode 100644 index 7bfe849396..0000000000 --- a/patches/server/0409-Optimize-Voxel-Shape-Merging.patch +++ /dev/null @@ -1,121 +0,0 @@ -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/IndirectMerger.java b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java -index 9e0afab2329e560c4b2512548dd4b02dd1a2e69f..06662dbff8180751a8684841aa35f709007078ae 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/IndirectMerger.java -@@ -10,12 +10,33 @@ public class IndirectMerger implements IndexMerger { - private final int[] firstIndices; - private final int[] secondIndices; - private final int resultLength; -+ // Paper start -+ private static final int[] INFINITE_B_1 = new int[]{1, 1}; -+ private static final int[] INFINITE_B_0 = new int[]{0, 0}; -+ private static final int[] INFINITE_C = new int[]{0, 1}; -+ // Paper end - - public IndirectMerger(DoubleList first, DoubleList second, boolean includeFirstOnly, boolean includeSecondOnly) { - double d = Double.NaN; - int i = first.size(); - int j = second.size(); - int k = i + j; -+ // Paper start - optimize common path of infinity doublelist -+ int size = first.size(); -+ double tail = first.getDouble(size - 1); -+ double head = first.getDouble(0); -+ if (head == Double.NEGATIVE_INFINITY && tail == Double.POSITIVE_INFINITY && !includeFirstOnly && !includeSecondOnly && (size == 2 || size == 4)) { -+ this.result = second.toDoubleArray(); -+ this.resultLength = second.size(); -+ if (size == 2) { -+ this.firstIndices = INFINITE_B_0; -+ } else { -+ this.firstIndices = INFINITE_B_1; -+ } -+ this.secondIndices = INFINITE_C; -+ return; -+ } -+ // Paper end - this.result = new double[k]; - this.firstIndices = new int[k]; - this.secondIndices = new int[k]; -diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 2d273be8145bbd86ffdf33358629da7fc08b4d4c..9176735c08a75854209f24113b0e78332249dc4d 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -@@ -247,9 +247,21 @@ public final class Shapes { - } - - @VisibleForTesting -- protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { -+ private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // 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 (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) { -+ return new IndirectMerger(first, second, includeFirst, includeSecond); -+ } -+ // Split out rest to hopefully inline the above -+ return lessCommonMerge(size, first, second, includeFirst, includeSecond); -+ } -+ -+ private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { - int i = first.size() - 1; - int j = second.size() - 1; -+ // Paper note - Rewrite below as optimized order if instead of nasty ternary - if (first instanceof CubePointRange && second instanceof CubePointRange) { - long l = lcm(i, j); - if ((long)size * l <= 256L) { -@@ -257,13 +269,22 @@ public final class Shapes { - } - } - -- if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) { -+ // Paper start - Identical happens more often than Disjoint -+ if (i == j && Objects.equals(first, second)) { -+ if (first instanceof IdenticalMerger) { -+ return (IndexMerger) first; -+ } else if (second instanceof IdenticalMerger) { -+ return (IndexMerger) second; -+ } -+ return new IdenticalMerger(first); -+ } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) { - return new NonOverlappingMerger(first, second, false); - } else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7D) { - return new NonOverlappingMerger(second, first, true); - } else { -- return (IndexMerger)(i == j && Objects.equals(first, second) ? new IdenticalMerger(first) : new IndirectMerger(first, second, includeFirst, includeSecond)); -+ return new IndirectMerger(first, second, includeFirst, includeSecond); - } -+ // Paper end - } - - public interface DoubleLineConsumer { diff --git a/patches/server/0409-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server/0409-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch new file mode 100644 index 0000000000..6e0cb176e9 --- /dev/null +++ b/patches/server/0409-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 cce6886bb3973eed8f0c7ca7b1189547324fd4e2..0aef4fc4a89e627bc80504d7402f1ca2cdc95a74 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -28,6 +28,7 @@ public class Main { + } + // Paper end + // 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(Main.asList("?", "help"), "Show the help"); diff --git a/patches/server/0410-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch b/patches/server/0410-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch deleted file mode 100644 index 6c54f83680..0000000000 --- a/patches/server/0410-Set-cap-on-JDK-per-thread-native-byte-buffer-cache.patch +++ /dev/null @@ -1,30 +0,0 @@ -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 987bc4577190d827718b5144656aaddf22599cab..5b48047242e20c216b110502935763ea433c55cd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -28,6 +28,7 @@ public class Main { - } - // Paper end - // 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(Main.asList("?", "help"), "Show the help"); diff --git a/patches/server/0410-misc-debugging-dumps.patch b/patches/server/0410-misc-debugging-dumps.patch new file mode 100644 index 0000000000..1709001c6e --- /dev/null +++ b/patches/server/0410-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 3a4222f78a02e10ecccc03df3c580895fbb8059d..ff94b07246b5e17be53f4e7294557c6744c62248 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -871,6 +871,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 3 Mar 2020 05:26:40 +0000 +Subject: [PATCH] Prevent teleporting dead entities + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 77cc62cc942687a40371741904525301a4ed5240..bbed54d5a0e3c363614d694950688f8edc02841d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1632,6 +1632,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set, boolean flag) { ++ // Paper start ++ if (player.isRemoved()) { ++ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); ++ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); ++ return; ++ } ++ // Paper end + // CraftBukkit start + if (Float.isNaN(f)) { + f = 0; diff --git a/patches/server/0411-misc-debugging-dumps.patch b/patches/server/0411-misc-debugging-dumps.patch deleted file mode 100644 index 1709001c6e..0000000000 --- a/patches/server/0411-misc-debugging-dumps.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 18 Feb 2021 20:23:28 +0000 -Subject: [PATCH] misc debugging dumps - - -diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2d5494d2813b773e60ddba6790b750a9a08f21f8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Bukkit; -+ -+public final class TraceUtil { -+ -+ public static void dumpTraceForThread(Thread thread, String reason) { -+ Bukkit.getLogger().warning(thread.getName() + ": " + reason); -+ StackTraceElement[] trace = thread.getStackTrace(); -+ for (StackTraceElement traceElement : trace) { -+ Bukkit.getLogger().warning("\tat " + traceElement); -+ } -+ } -+ -+ public static void dumpTraceForThread(String reason) { -+ new Throwable(reason).printStackTrace(); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3a4222f78a02e10ecccc03df3c580895fbb8059d..ff94b07246b5e17be53f4e7294557c6744c62248 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -871,6 +871,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 20 Jun 2021 18:19:09 -0700 +Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and + etc. + + +diff --git a/build.gradle.kts b/build.gradle.kts +index e260462933a9f7065b2360e6bf9e4ee56069a705..a1a8c4778742584125d6084fa761b1bc86f6a842 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -1,4 +1,6 @@ ++import io.papermc.paperweight.tasks.BaseTask + import io.papermc.paperweight.util.* ++import java.nio.file.Files + + plugins { + java +@@ -19,12 +21,14 @@ dependencies { + Scanning takes about 1-2 seconds so adding this speeds up the server start. + */ + implementation("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - implementation ++ annotationProcessor("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - Needed to generate meta for our Log4j plugins + // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper + implementation("org.ow2.asm:asm:9.3") + implementation("org.ow2.asm:asm-commons:9.3") // Paper - ASM event executor generation + implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files + implementation("commons-lang:commons-lang:2.6") ++ implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation + runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3") + runtimeOnly("mysql:mysql-connector-java:8.0.29") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper +@@ -104,6 +108,45 @@ tasks.check { + } + // Paper end + ++// Paper start - include reobf mappings in jar for stacktrace deobfuscation ++abstract class IncludeMappings : BaseTask() { ++ @get:InputFile ++ abstract val inputJar: RegularFileProperty ++ ++ @get:InputFile ++ abstract val mappings: RegularFileProperty ++ ++ @get:OutputFile ++ abstract val outputJar: RegularFileProperty ++ ++ override fun init() { ++ super.init() ++ outputJar.convention(defaultOutput()) ++ } ++ ++ @TaskAction ++ private fun addMappings() { ++ outputJar.get().asFile.parentFile.mkdirs() ++ inputJar.get().asFile.copyTo(outputJar.get().asFile, overwrite = true) ++ outputJar.get().path.openZip().use { fs -> ++ val dir = fs.getPath("META-INF/mappings/") ++ Files.createDirectories(dir) ++ val target = dir.resolve("reobf.tiny") ++ Files.copy(mappings.path, target) ++ } ++ } ++} ++ ++val includeMappings = tasks.register("includeMappings") { ++ inputJar.set(tasks.fixJarForReobf.flatMap { it.outputJar }) ++ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile }) ++} ++ ++tasks.reobfJar { ++ inputJar.set(includeMappings.flatMap { it.outputJar }) ++} ++// Paper end - include reobf mappings in jar for stacktrace deobfuscation ++ + tasks.test { + exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") + } +diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +index 0bb4aaa546939b67a5d22865190f30478a9337c1..d3e619655382e50e9ac9323ed942502d85c9599c 100644 +--- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java ++++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java +@@ -91,7 +91,7 @@ public class SyncLoadFinder { + + final JsonArray traces = new JsonArray(); + +- for (StackTraceElement element : pair.getFirst().stacktrace) { ++ for (StackTraceElement element : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(pair.getFirst().stacktrace)) { + traces.add(String.valueOf(element)); + } + +diff --git a/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c701ef3c287f62aa0ebfbdbd6da6ed82a1c7793c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +@@ -0,0 +1,38 @@ ++package io.papermc.paper.logging; ++ ++import io.papermc.paper.util.StacktraceDeobfuscator; ++import org.apache.logging.log4j.core.Core; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.config.plugins.PluginFactory; ++import org.apache.logging.log4j.core.impl.Log4jLogEvent; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++@Plugin( ++ name = "StacktraceDeobfuscatingRewritePolicy", ++ category = Core.CATEGORY_NAME, ++ elementType = "rewritePolicy", ++ printObject = true ++) ++public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { ++ private StacktraceDeobfuscatingRewritePolicy() { ++ } ++ ++ @Override ++ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) { ++ final Throwable thrown = rewrite.getThrown(); ++ if (thrown != null) { ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thrown); ++ return new Log4jLogEvent.Builder(rewrite) ++ .setThrownProxy(null) ++ .build(); ++ } ++ return rewrite; ++ } ++ ++ @PluginFactory ++ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() { ++ return new StacktraceDeobfuscatingRewritePolicy(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b8b17d046f836c8652ab094db00ab1af84971b2c +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ObfHelper.java +@@ -0,0 +1,146 @@ ++package io.papermc.paper.util; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.nio.charset.StandardCharsets; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Function; ++import java.util.stream.Collectors; ++import net.fabricmc.mappingio.MappingReader; ++import net.fabricmc.mappingio.format.MappingFormat; ++import net.fabricmc.mappingio.tree.MappingTree; ++import net.fabricmc.mappingio.tree.MemoryMappingTree; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public enum ObfHelper { ++ INSTANCE; ++ ++ public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn"; ++ public static final String SPIGOT_NAMESPACE = "spigot"; ++ ++ private final @Nullable Map mappingsByObfName; ++ private final @Nullable Map mappingsByMojangName; ++ ++ ObfHelper() { ++ final @Nullable Set maps = loadMappingsIfPresent(); ++ if (maps != null) { ++ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map)); ++ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map)); ++ } else { ++ this.mappingsByObfName = null; ++ this.mappingsByMojangName = null; ++ } ++ } ++ ++ public @Nullable Map mappingsByObfName() { ++ return this.mappingsByObfName; ++ } ++ ++ public @Nullable Map mappingsByMojangName() { ++ return this.mappingsByMojangName; ++ } ++ ++ /** ++ * Attempts to get the obf name for a given class by its Mojang name. Will ++ * return the input string if mappings are not present. ++ * ++ * @param fullyQualifiedMojangName fully qualified class name (dotted) ++ * @return mapped or original fully qualified (dotted) class name ++ */ ++ public String reobfClassName(final String fullyQualifiedMojangName) { ++ if (this.mappingsByMojangName == null) { ++ return fullyQualifiedMojangName; ++ } ++ ++ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName); ++ if (map == null) { ++ return fullyQualifiedMojangName; ++ } ++ ++ return map.obfName(); ++ } ++ ++ /** ++ * Attempts to get the Mojang name for a given class by its obf name. Will ++ * return the input string if mappings are not present. ++ * ++ * @param fullyQualifiedObfName fully qualified class name (dotted) ++ * @return mapped or original fully qualified (dotted) class name ++ */ ++ public String deobfClassName(final String fullyQualifiedObfName) { ++ if (this.mappingsByObfName == null) { ++ return fullyQualifiedObfName; ++ } ++ ++ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName); ++ if (map == null) { ++ return fullyQualifiedObfName; ++ } ++ ++ return map.mojangName(); ++ } ++ ++ private static @Nullable Set loadMappingsIfPresent() { ++ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) { ++ if (mappingsInputStream == null) { ++ return null; ++ } ++ final MemoryMappingTree tree = new MemoryMappingTree(); ++ MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree); ++ final Set classes = new HashSet<>(); ++ ++ final StringPool pool = new StringPool(); ++ for (final MappingTree.ClassMapping cls : tree.getClasses()) { ++ final Map methods = new HashMap<>(); ++ ++ for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) { ++ methods.put( ++ pool.string(methodKey( ++ methodMapping.getName(SPIGOT_NAMESPACE), ++ methodMapping.getDesc(SPIGOT_NAMESPACE) ++ )), ++ pool.string(methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE)) ++ ); ++ } ++ ++ final ClassMapping map = new ClassMapping( ++ cls.getName(SPIGOT_NAMESPACE).replace('/', '.'), ++ cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'), ++ Map.copyOf(methods) ++ ); ++ classes.add(map); ++ } ++ ++ return Set.copyOf(classes); ++ } catch (final IOException ex) { ++ System.err.println("Failed to load mappings for stacktrace deobfuscation."); ++ ex.printStackTrace(); ++ return null; ++ } ++ } ++ ++ public static String methodKey(final String obfName, final String obfDescriptor) { ++ return obfName + obfDescriptor; ++ } ++ ++ private static final class StringPool { ++ private final Map pool = new HashMap<>(); ++ ++ public String string(final String string) { ++ return this.pool.computeIfAbsent(string, Function.identity()); ++ } ++ } ++ ++ public record ClassMapping( ++ String obfName, ++ String mojangName, ++ Map methodsByObf ++ ) {} ++} +diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..eb910d4abf91488fa7cf1f5d47e0ee916c47f512 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +@@ -0,0 +1,163 @@ ++package io.papermc.paper.util; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntList; ++import java.io.IOException; ++import java.io.InputStream; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.LinkedHashMap; ++import java.util.Map; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.objectweb.asm.ClassReader; ++import org.objectweb.asm.ClassVisitor; ++import org.objectweb.asm.Label; ++import org.objectweb.asm.MethodVisitor; ++import org.objectweb.asm.Opcodes; ++ ++@DefaultQualifier(NonNull.class) ++public enum StacktraceDeobfuscator { ++ INSTANCE; ++ ++ private final Map, Map> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) { ++ @Override ++ protected boolean removeEldestEntry(final Map.Entry, Map> eldest) { ++ return this.size() > 127; ++ } ++ }); ++ ++ public void deobfuscateThrowable(final Throwable throwable) { ++ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true ++ return; ++ } ++ ++ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace())); ++ final Throwable cause = throwable.getCause(); ++ if (cause != null) { ++ this.deobfuscateThrowable(cause); ++ } ++ for (final Throwable suppressed : throwable.getSuppressed()) { ++ this.deobfuscateThrowable(suppressed); ++ } ++ } ++ ++ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { ++ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true ++ return traceElements; ++ } ++ ++ final @Nullable Map mappings = ObfHelper.INSTANCE.mappingsByObfName(); ++ if (mappings == null || traceElements.length == 0) { ++ return traceElements; ++ } ++ final StackTraceElement[] result = new StackTraceElement[traceElements.length]; ++ for (int i = 0; i < traceElements.length; i++) { ++ final StackTraceElement element = traceElements[i]; ++ ++ final String className = element.getClassName(); ++ final String methodName = element.getMethodName(); ++ ++ final ObfHelper.ClassMapping classMapping = mappings.get(className); ++ if (classMapping == null) { ++ result[i] = element; ++ continue; ++ } ++ ++ final Class clazz; ++ try { ++ clazz = Class.forName(className); ++ } catch (final ClassNotFoundException ex) { ++ throw new RuntimeException(ex); ++ } ++ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber()); ++ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey); ++ ++ result[i] = new StackTraceElement( ++ element.getClassLoaderName(), ++ element.getModuleName(), ++ element.getModuleVersion(), ++ classMapping.mojangName(), ++ mappedMethodName != null ? mappedMethodName : methodName, ++ sourceFileName(classMapping.mojangName()), ++ element.getLineNumber() ++ ); ++ } ++ return result; ++ } ++ ++ private @Nullable String determineMethodForLine(final Class clazz, final int lineNumber) { ++ final Map lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap); ++ for (final var entry : lineMap.entrySet()) { ++ final String methodKey = entry.getKey(); ++ final IntList lines = entry.getValue(); ++ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { ++ final int num = lines.getInt(i); ++ if (num == lineNumber) { ++ return methodKey; ++ } ++ } ++ } ++ return null; ++ } ++ ++ private static String sourceFileName(final String fullClassName) { ++ final int dot = fullClassName.lastIndexOf('.'); ++ final String className = dot == -1 ++ ? fullClassName ++ : fullClassName.substring(dot + 1); ++ final String rootClassName = className.split("\\$")[0]; ++ return rootClassName + ".java"; ++ } ++ ++ private static Map buildLineMap(final Class key) { ++ final Map lineMap = new HashMap<>(); ++ final class LineCollectingMethodVisitor extends MethodVisitor { ++ private final IntList lines = new IntArrayList(); ++ private final String name; ++ private final String descriptor; ++ ++ LineCollectingMethodVisitor(String name, String descriptor) { ++ super(Opcodes.ASM9); ++ this.name = name; ++ this.descriptor = descriptor; ++ } ++ ++ @Override ++ public void visitLineNumber(int line, Label start) { ++ super.visitLineNumber(line, start); ++ this.lines.add(line); ++ } ++ ++ @Override ++ public void visitEnd() { ++ super.visitEnd(); ++ lineMap.put(ObfHelper.methodKey(this.name, this.descriptor), this.lines); ++ } ++ } ++ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { ++ @Override ++ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { ++ return new LineCollectingMethodVisitor(name, descriptor); ++ } ++ }; ++ try { ++ final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader() ++ .getResourceAsStream(key.getName().replace('.', '/') + ".class"); ++ if (inputStream == null) { ++ throw new IllegalStateException("Could not find class file: " + key.getName()); ++ } ++ final byte[] classData; ++ try (inputStream) { ++ classData = inputStream.readAllBytes(); ++ } ++ final ClassReader reader = new ClassReader(classData); ++ reader.accept(classVisitor, 0); ++ } catch (final IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return lineMap; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java +index 2d5494d2813b773e60ddba6790b750a9a08f21f8..7695bf44503f161523ea612ef8a884ae574a2e21 100644 +--- a/src/main/java/io/papermc/paper/util/TraceUtil.java ++++ b/src/main/java/io/papermc/paper/util/TraceUtil.java +@@ -6,13 +6,15 @@ public final class TraceUtil { + + public static void dumpTraceForThread(Thread thread, String reason) { + Bukkit.getLogger().warning(thread.getName() + ": " + reason); +- StackTraceElement[] trace = thread.getStackTrace(); ++ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); + for (StackTraceElement traceElement : trace) { + Bukkit.getLogger().warning("\tat " + traceElement); + } + } + + public static void dumpTraceForThread(String reason) { +- new Throwable(reason).printStackTrace(); ++ final Throwable throwable = new Throwable(reason); ++ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(throwable); ++ throwable.printStackTrace(); + } + } +diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java +index 30a58229aa6dac5039511d0c0df5f2912ea7de9f..abe37c7c3c6f5ab73afd738ec78f06d7e4d2ed96 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -32,6 +32,7 @@ public class CrashReport { + private final SystemReport systemReport = new SystemReport(); + + public CrashReport(String message, Throwable cause) { ++ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper + this.title = message; + this.exception = cause; + this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit +diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java +index f114d5dab86aa2cdd59c78406c9d82f9caededca..99fa9f1952ee7ed79b223ff210a658e4b119b3e4 100644 +--- a/src/main/java/net/minecraft/CrashReportCategory.java ++++ b/src/main/java/net/minecraft/CrashReportCategory.java +@@ -104,6 +104,7 @@ public class CrashReportCategory { + } else { + this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount]; + System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length); ++ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper + return this.stackTrace.length; + } + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 31d35af5d0efbd0bd8528c3f05e660a203e67ac9..7f0bcb69a1a14a01bd80ffcba1afb25ceeab19b8 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -62,13 +62,13 @@ public class Connection extends SimpleChannelInboundHandler> { + }); + public static final AttributeKey ATTRIBUTE_PROTOCOL = AttributeKey.valueOf("protocol"); + public static final LazyLoadedValue NETWORK_WORKER_GROUP = new LazyLoadedValue<>(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final LazyLoadedValue NETWORK_EPOLL_WORKER_GROUP = new LazyLoadedValue<>(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final LazyLoadedValue LOCAL_WORKER_GROUP = new LazyLoadedValue<>(() -> { +- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); ++ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + private final PacketFlow receiving; + private final Queue queue = Queues.newConcurrentLinkedQueue(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 648bc209938364a387c3f81dcd073db398e9f864..e42df2956e2d852a5a4c8fdeda395a3efd32c44c 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -200,6 +200,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end + // Paper start ++ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + paperConfigurations.initializeGlobalConfiguration(); + paperConfigurations.initializeWorldDefaultsConfiguration(); + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index a24ef433d0c9d06b86fd612978cfd6d877036791..1b38326c9a709536dc4cccf9af93aede98a1a782 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -54,10 +54,10 @@ public class ServerConnectionListener { + + private static final Logger LOGGER = LogUtils.getLogger(); + public static final LazyLoadedValue SERVER_EVENT_GROUP = new LazyLoadedValue<>(() -> { +- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); ++ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + public static final LazyLoadedValue SERVER_EPOLL_EVENT_GROUP = new LazyLoadedValue<>(() -> { +- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); ++ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper + }); + final MinecraftServer server; + public volatile boolean running; +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +index 3c1992e212a6d6f1db4d5b807b38d71913619fc0..9c1aff17aabd062640e3f451a2ef8c50a7c62f10 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -40,9 +40,9 @@ public class CraftAsyncScheduler extends CraftScheduler { + + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), +- new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); ++ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() +- .setNameFormat("Craft Async Scheduler Management Thread").build()); ++ .setNameFormat("Craft Async Scheduler Management Thread").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index f3d60841f7b2e29e667291092b97ec909ba03ab6..7823e97add506d42161fd86f830e4d988b2113d8 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -105,7 +105,7 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity" ); + log.log( Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated" ); + log.log( Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); +- for ( StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace() ) ++ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) ) // Paper + { + log.log( Level.SEVERE, "\t\t" + stack ); + } +@@ -193,7 +193,7 @@ public class WatchdogThread extends Thread + } + log.log( Level.SEVERE, "\tStack:" ); + // +- for ( StackTraceElement stack : thread.getStackTrace() ) ++ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper + { + log.log( Level.SEVERE, "\t\t" + stack ); + } +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index d285dbec16272db6b8a71865e05924ad66087407..1a05d23ff886b015fb9396f119822c678a47ec6f 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -30,10 +30,14 @@ + + + ++ ++ ++ ++ + + + +- ++ + + + diff --git a/patches/server/0412-Prevent-teleporting-dead-entities.patch b/patches/server/0412-Prevent-teleporting-dead-entities.patch deleted file mode 100644 index 526b593a85..0000000000 --- a/patches/server/0412-Prevent-teleporting-dead-entities.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 3 Mar 2020 05:26:40 +0000 -Subject: [PATCH] Prevent teleporting dead entities - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index dac18223143ea10054787f8954744311be7df7aa..245c1ff9fe02a6f8fe1f320e2a751c7579425b1f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1632,6 +1632,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set, boolean flag) { -+ // Paper start -+ if (player.isRemoved()) { -+ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -+ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); -+ return; -+ } -+ // Paper end - // CraftBukkit start - if (Float.isNaN(f)) { - f = 0; diff --git a/patches/server/0413-Deobfuscate-stacktraces-in-log-messages-crash-report.patch b/patches/server/0413-Deobfuscate-stacktraces-in-log-messages-crash-report.patch deleted file mode 100644 index 3f3a055bc6..0000000000 --- a/patches/server/0413-Deobfuscate-stacktraces-in-log-messages-crash-report.patch +++ /dev/null @@ -1,611 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 20 Jun 2021 18:19:09 -0700 -Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and - etc. - - -diff --git a/build.gradle.kts b/build.gradle.kts -index e260462933a9f7065b2360e6bf9e4ee56069a705..a1a8c4778742584125d6084fa761b1bc86f6a842 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -1,4 +1,6 @@ -+import io.papermc.paperweight.tasks.BaseTask - import io.papermc.paperweight.util.* -+import java.nio.file.Files - - plugins { - java -@@ -19,12 +21,14 @@ dependencies { - Scanning takes about 1-2 seconds so adding this speeds up the server start. - */ - implementation("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - implementation -+ annotationProcessor("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - Needed to generate meta for our Log4j plugins - // Paper end - implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper - implementation("org.ow2.asm:asm:9.3") - implementation("org.ow2.asm:asm-commons:9.3") // Paper - ASM event executor generation - implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files - implementation("commons-lang:commons-lang:2.6") -+ implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation - runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3") - runtimeOnly("mysql:mysql-connector-java:8.0.29") - runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -@@ -104,6 +108,45 @@ tasks.check { - } - // Paper end - -+// Paper start - include reobf mappings in jar for stacktrace deobfuscation -+abstract class IncludeMappings : BaseTask() { -+ @get:InputFile -+ abstract val inputJar: RegularFileProperty -+ -+ @get:InputFile -+ abstract val mappings: RegularFileProperty -+ -+ @get:OutputFile -+ abstract val outputJar: RegularFileProperty -+ -+ override fun init() { -+ super.init() -+ outputJar.convention(defaultOutput()) -+ } -+ -+ @TaskAction -+ private fun addMappings() { -+ outputJar.get().asFile.parentFile.mkdirs() -+ inputJar.get().asFile.copyTo(outputJar.get().asFile, overwrite = true) -+ outputJar.get().path.openZip().use { fs -> -+ val dir = fs.getPath("META-INF/mappings/") -+ Files.createDirectories(dir) -+ val target = dir.resolve("reobf.tiny") -+ Files.copy(mappings.path, target) -+ } -+ } -+} -+ -+val includeMappings = tasks.register("includeMappings") { -+ inputJar.set(tasks.fixJarForReobf.flatMap { it.outputJar }) -+ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile }) -+} -+ -+tasks.reobfJar { -+ inputJar.set(includeMappings.flatMap { it.outputJar }) -+} -+// Paper end - include reobf mappings in jar for stacktrace deobfuscation -+ - tasks.test { - exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") - } -diff --git a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -index 0bb4aaa546939b67a5d22865190f30478a9337c1..d3e619655382e50e9ac9323ed942502d85c9599c 100644 ---- a/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -+++ b/src/main/java/com/destroystokyo/paper/io/SyncLoadFinder.java -@@ -91,7 +91,7 @@ public class SyncLoadFinder { - - final JsonArray traces = new JsonArray(); - -- for (StackTraceElement element : pair.getFirst().stacktrace) { -+ for (StackTraceElement element : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(pair.getFirst().stacktrace)) { - traces.add(String.valueOf(element)); - } - -diff --git a/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c701ef3c287f62aa0ebfbdbd6da6ed82a1c7793c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java -@@ -0,0 +1,38 @@ -+package io.papermc.paper.logging; -+ -+import io.papermc.paper.util.StacktraceDeobfuscator; -+import org.apache.logging.log4j.core.Core; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; -+import org.apache.logging.log4j.core.config.plugins.Plugin; -+import org.apache.logging.log4j.core.config.plugins.PluginFactory; -+import org.apache.logging.log4j.core.impl.Log4jLogEvent; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+@Plugin( -+ name = "StacktraceDeobfuscatingRewritePolicy", -+ category = Core.CATEGORY_NAME, -+ elementType = "rewritePolicy", -+ printObject = true -+) -+public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { -+ private StacktraceDeobfuscatingRewritePolicy() { -+ } -+ -+ @Override -+ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) { -+ final Throwable thrown = rewrite.getThrown(); -+ if (thrown != null) { -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thrown); -+ return new Log4jLogEvent.Builder(rewrite) -+ .setThrownProxy(null) -+ .build(); -+ } -+ return rewrite; -+ } -+ -+ @PluginFactory -+ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() { -+ return new StacktraceDeobfuscatingRewritePolicy(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8b17d046f836c8652ab094db00ab1af84971b2c ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ObfHelper.java -@@ -0,0 +1,146 @@ -+package io.papermc.paper.util; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.InputStreamReader; -+import java.nio.charset.StandardCharsets; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import java.util.function.Function; -+import java.util.stream.Collectors; -+import net.fabricmc.mappingio.MappingReader; -+import net.fabricmc.mappingio.format.MappingFormat; -+import net.fabricmc.mappingio.tree.MappingTree; -+import net.fabricmc.mappingio.tree.MemoryMappingTree; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public enum ObfHelper { -+ INSTANCE; -+ -+ public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn"; -+ public static final String SPIGOT_NAMESPACE = "spigot"; -+ -+ private final @Nullable Map mappingsByObfName; -+ private final @Nullable Map mappingsByMojangName; -+ -+ ObfHelper() { -+ final @Nullable Set maps = loadMappingsIfPresent(); -+ if (maps != null) { -+ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map)); -+ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map)); -+ } else { -+ this.mappingsByObfName = null; -+ this.mappingsByMojangName = null; -+ } -+ } -+ -+ public @Nullable Map mappingsByObfName() { -+ return this.mappingsByObfName; -+ } -+ -+ public @Nullable Map mappingsByMojangName() { -+ return this.mappingsByMojangName; -+ } -+ -+ /** -+ * Attempts to get the obf name for a given class by its Mojang name. Will -+ * return the input string if mappings are not present. -+ * -+ * @param fullyQualifiedMojangName fully qualified class name (dotted) -+ * @return mapped or original fully qualified (dotted) class name -+ */ -+ public String reobfClassName(final String fullyQualifiedMojangName) { -+ if (this.mappingsByMojangName == null) { -+ return fullyQualifiedMojangName; -+ } -+ -+ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName); -+ if (map == null) { -+ return fullyQualifiedMojangName; -+ } -+ -+ return map.obfName(); -+ } -+ -+ /** -+ * Attempts to get the Mojang name for a given class by its obf name. Will -+ * return the input string if mappings are not present. -+ * -+ * @param fullyQualifiedObfName fully qualified class name (dotted) -+ * @return mapped or original fully qualified (dotted) class name -+ */ -+ public String deobfClassName(final String fullyQualifiedObfName) { -+ if (this.mappingsByObfName == null) { -+ return fullyQualifiedObfName; -+ } -+ -+ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName); -+ if (map == null) { -+ return fullyQualifiedObfName; -+ } -+ -+ return map.mojangName(); -+ } -+ -+ private static @Nullable Set loadMappingsIfPresent() { -+ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) { -+ if (mappingsInputStream == null) { -+ return null; -+ } -+ final MemoryMappingTree tree = new MemoryMappingTree(); -+ MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree); -+ final Set classes = new HashSet<>(); -+ -+ final StringPool pool = new StringPool(); -+ for (final MappingTree.ClassMapping cls : tree.getClasses()) { -+ final Map methods = new HashMap<>(); -+ -+ for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) { -+ methods.put( -+ pool.string(methodKey( -+ methodMapping.getName(SPIGOT_NAMESPACE), -+ methodMapping.getDesc(SPIGOT_NAMESPACE) -+ )), -+ pool.string(methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE)) -+ ); -+ } -+ -+ final ClassMapping map = new ClassMapping( -+ cls.getName(SPIGOT_NAMESPACE).replace('/', '.'), -+ cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'), -+ Map.copyOf(methods) -+ ); -+ classes.add(map); -+ } -+ -+ return Set.copyOf(classes); -+ } catch (final IOException ex) { -+ System.err.println("Failed to load mappings for stacktrace deobfuscation."); -+ ex.printStackTrace(); -+ return null; -+ } -+ } -+ -+ public static String methodKey(final String obfName, final String obfDescriptor) { -+ return obfName + obfDescriptor; -+ } -+ -+ private static final class StringPool { -+ private final Map pool = new HashMap<>(); -+ -+ public String string(final String string) { -+ return this.pool.computeIfAbsent(string, Function.identity()); -+ } -+ } -+ -+ public record ClassMapping( -+ String obfName, -+ String mojangName, -+ Map methodsByObf -+ ) {} -+} -diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java -new file mode 100644 -index 0000000000000000000000000000000000000000..eb910d4abf91488fa7cf1f5d47e0ee916c47f512 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java -@@ -0,0 +1,163 @@ -+package io.papermc.paper.util; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntList; -+import java.io.IOException; -+import java.io.InputStream; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.LinkedHashMap; -+import java.util.Map; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+import org.objectweb.asm.ClassReader; -+import org.objectweb.asm.ClassVisitor; -+import org.objectweb.asm.Label; -+import org.objectweb.asm.MethodVisitor; -+import org.objectweb.asm.Opcodes; -+ -+@DefaultQualifier(NonNull.class) -+public enum StacktraceDeobfuscator { -+ INSTANCE; -+ -+ private final Map, Map> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) { -+ @Override -+ protected boolean removeEldestEntry(final Map.Entry, Map> eldest) { -+ return this.size() > 127; -+ } -+ }); -+ -+ public void deobfuscateThrowable(final Throwable throwable) { -+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true -+ return; -+ } -+ -+ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace())); -+ final Throwable cause = throwable.getCause(); -+ if (cause != null) { -+ this.deobfuscateThrowable(cause); -+ } -+ for (final Throwable suppressed : throwable.getSuppressed()) { -+ this.deobfuscateThrowable(suppressed); -+ } -+ } -+ -+ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { -+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true -+ return traceElements; -+ } -+ -+ final @Nullable Map mappings = ObfHelper.INSTANCE.mappingsByObfName(); -+ if (mappings == null || traceElements.length == 0) { -+ return traceElements; -+ } -+ final StackTraceElement[] result = new StackTraceElement[traceElements.length]; -+ for (int i = 0; i < traceElements.length; i++) { -+ final StackTraceElement element = traceElements[i]; -+ -+ final String className = element.getClassName(); -+ final String methodName = element.getMethodName(); -+ -+ final ObfHelper.ClassMapping classMapping = mappings.get(className); -+ if (classMapping == null) { -+ result[i] = element; -+ continue; -+ } -+ -+ final Class clazz; -+ try { -+ clazz = Class.forName(className); -+ } catch (final ClassNotFoundException ex) { -+ throw new RuntimeException(ex); -+ } -+ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber()); -+ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey); -+ -+ result[i] = new StackTraceElement( -+ element.getClassLoaderName(), -+ element.getModuleName(), -+ element.getModuleVersion(), -+ classMapping.mojangName(), -+ mappedMethodName != null ? mappedMethodName : methodName, -+ sourceFileName(classMapping.mojangName()), -+ element.getLineNumber() -+ ); -+ } -+ return result; -+ } -+ -+ private @Nullable String determineMethodForLine(final Class clazz, final int lineNumber) { -+ final Map lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap); -+ for (final var entry : lineMap.entrySet()) { -+ final String methodKey = entry.getKey(); -+ final IntList lines = entry.getValue(); -+ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { -+ final int num = lines.getInt(i); -+ if (num == lineNumber) { -+ return methodKey; -+ } -+ } -+ } -+ return null; -+ } -+ -+ private static String sourceFileName(final String fullClassName) { -+ final int dot = fullClassName.lastIndexOf('.'); -+ final String className = dot == -1 -+ ? fullClassName -+ : fullClassName.substring(dot + 1); -+ final String rootClassName = className.split("\\$")[0]; -+ return rootClassName + ".java"; -+ } -+ -+ private static Map buildLineMap(final Class key) { -+ final Map lineMap = new HashMap<>(); -+ final class LineCollectingMethodVisitor extends MethodVisitor { -+ private final IntList lines = new IntArrayList(); -+ private final String name; -+ private final String descriptor; -+ -+ LineCollectingMethodVisitor(String name, String descriptor) { -+ super(Opcodes.ASM9); -+ this.name = name; -+ this.descriptor = descriptor; -+ } -+ -+ @Override -+ public void visitLineNumber(int line, Label start) { -+ super.visitLineNumber(line, start); -+ this.lines.add(line); -+ } -+ -+ @Override -+ public void visitEnd() { -+ super.visitEnd(); -+ lineMap.put(ObfHelper.methodKey(this.name, this.descriptor), this.lines); -+ } -+ } -+ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { -+ @Override -+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { -+ return new LineCollectingMethodVisitor(name, descriptor); -+ } -+ }; -+ try { -+ final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader() -+ .getResourceAsStream(key.getName().replace('.', '/') + ".class"); -+ if (inputStream == null) { -+ throw new IllegalStateException("Could not find class file: " + key.getName()); -+ } -+ final byte[] classData; -+ try (inputStream) { -+ classData = inputStream.readAllBytes(); -+ } -+ final ClassReader reader = new ClassReader(classData); -+ reader.accept(classVisitor, 0); -+ } catch (final IOException ex) { -+ throw new RuntimeException(ex); -+ } -+ return lineMap; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/util/TraceUtil.java b/src/main/java/io/papermc/paper/util/TraceUtil.java -index 2d5494d2813b773e60ddba6790b750a9a08f21f8..7695bf44503f161523ea612ef8a884ae574a2e21 100644 ---- a/src/main/java/io/papermc/paper/util/TraceUtil.java -+++ b/src/main/java/io/papermc/paper/util/TraceUtil.java -@@ -6,13 +6,15 @@ public final class TraceUtil { - - public static void dumpTraceForThread(Thread thread, String reason) { - Bukkit.getLogger().warning(thread.getName() + ": " + reason); -- StackTraceElement[] trace = thread.getStackTrace(); -+ StackTraceElement[] trace = StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()); - for (StackTraceElement traceElement : trace) { - Bukkit.getLogger().warning("\tat " + traceElement); - } - } - - public static void dumpTraceForThread(String reason) { -- new Throwable(reason).printStackTrace(); -+ final Throwable throwable = new Throwable(reason); -+ StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(throwable); -+ throwable.printStackTrace(); - } - } -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index 30a58229aa6dac5039511d0c0df5f2912ea7de9f..abe37c7c3c6f5ab73afd738ec78f06d7e4d2ed96 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -32,6 +32,7 @@ public class CrashReport { - private final SystemReport systemReport = new SystemReport(); - - public CrashReport(String message, Throwable cause) { -+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper - this.title = message; - this.exception = cause; - this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit -diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java -index f114d5dab86aa2cdd59c78406c9d82f9caededca..99fa9f1952ee7ed79b223ff210a658e4b119b3e4 100644 ---- a/src/main/java/net/minecraft/CrashReportCategory.java -+++ b/src/main/java/net/minecraft/CrashReportCategory.java -@@ -104,6 +104,7 @@ public class CrashReportCategory { - } else { - this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount]; - System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length); -+ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper - return this.stackTrace.length; - } - } -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 31d35af5d0efbd0bd8528c3f05e660a203e67ac9..7f0bcb69a1a14a01bd80ffcba1afb25ceeab19b8 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -62,13 +62,13 @@ public class Connection extends SimpleChannelInboundHandler> { - }); - public static final AttributeKey ATTRIBUTE_PROTOCOL = AttributeKey.valueOf("protocol"); - public static final LazyLoadedValue NETWORK_WORKER_GROUP = new LazyLoadedValue<>(() -> { -- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build()); -+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final LazyLoadedValue NETWORK_EPOLL_WORKER_GROUP = new LazyLoadedValue<>(() -> { -- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build()); -+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final LazyLoadedValue LOCAL_WORKER_GROUP = new LazyLoadedValue<>(() -> { -- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build()); -+ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - private final PacketFlow receiving; - private final Queue queue = Queues.newConcurrentLinkedQueue(); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 648bc209938364a387c3f81dcd073db398e9f864..e42df2956e2d852a5a4c8fdeda395a3efd32c44c 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -200,6 +200,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - org.spigotmc.SpigotConfig.registerCommands(); - // Spigot end - // Paper start -+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. - paperConfigurations.initializeGlobalConfiguration(); - paperConfigurations.initializeWorldDefaultsConfiguration(); - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index a24ef433d0c9d06b86fd612978cfd6d877036791..1b38326c9a709536dc4cccf9af93aede98a1a782 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -54,10 +54,10 @@ public class ServerConnectionListener { - - private static final Logger LOGGER = LogUtils.getLogger(); - public static final LazyLoadedValue SERVER_EVENT_GROUP = new LazyLoadedValue<>(() -> { -- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); -+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final LazyLoadedValue SERVER_EPOLL_EVENT_GROUP = new LazyLoadedValue<>(() -> { -- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); -+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - final MinecraftServer server; - public volatile boolean running; -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -index 3c1992e212a6d6f1db4d5b807b38d71913619fc0..9c1aff17aabd062640e3f451a2ef8c50a7c62f10 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java -@@ -40,9 +40,9 @@ public class CraftAsyncScheduler extends CraftScheduler { - - private final ThreadPoolExecutor executor = new ThreadPoolExecutor( - 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), -- new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); -+ new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper - private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() -- .setNameFormat("Craft Async Scheduler Management Thread").build()); -+ .setNameFormat("Craft Async Scheduler Management Thread").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper - private final List temp = new ArrayList<>(); - - CraftAsyncScheduler() { -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index f3d60841f7b2e29e667291092b97ec909ba03ab6..7823e97add506d42161fd86f830e4d988b2113d8 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -105,7 +105,7 @@ public class WatchdogThread extends Thread - log.log( Level.SEVERE, "During the run of the server, a plugin set an excessive velocity on an entity" ); - log.log( Level.SEVERE, "This may be the cause of the issue, or it may be entirely unrelated" ); - log.log( Level.SEVERE, org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getMessage()); -- for ( StackTraceElement stack : org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace() ) -+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(org.bukkit.craftbukkit.CraftServer.excessiveVelEx.getStackTrace()) ) // Paper - { - log.log( Level.SEVERE, "\t\t" + stack ); - } -@@ -193,7 +193,7 @@ public class WatchdogThread extends Thread - } - log.log( Level.SEVERE, "\tStack:" ); - // -- for ( StackTraceElement stack : thread.getStackTrace() ) -+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper - { - log.log( Level.SEVERE, "\t\t" + stack ); - } -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index d285dbec16272db6b8a71865e05924ad66087407..1a05d23ff886b015fb9396f119822c678a47ec6f 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -30,10 +30,14 @@ - - - -+ -+ -+ -+ - - - -- -+ - - - diff --git a/patches/server/0413-Implement-Mob-Goal-API.patch b/patches/server/0413-Implement-Mob-Goal-API.patch new file mode 100644 index 0000000000..f8748925df --- /dev/null +++ b/patches/server/0413-Implement-Mob-Goal-API.patch @@ -0,0 +1,915 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Fri, 3 Jan 2020 16:26:19 +0100 +Subject: [PATCH] Implement Mob Goal API + + +diff --git a/build.gradle.kts b/build.gradle.kts +index a1a8c4778742584125d6084fa761b1bc86f6a842..a02f53c6ee0111e07d78a718a6ca0ec708f70cfc 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -37,6 +37,7 @@ dependencies { + runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3") + runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3") + ++ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test + testImplementation("junit:junit:4.13.2") + testImplementation("org.hamcrest:hamcrest-library:1.3") + +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..c9901f8c7f3af57cb2e0bec315caa97adcb6f5ea +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,371 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++import io.papermc.paper.util.ObfHelper; ++import java.lang.reflect.Constructor; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import net.minecraft.world.entity.FlyingMob; ++import net.minecraft.world.entity.PathfinderMob; ++import net.minecraft.world.entity.TamableAnimal; ++import net.minecraft.world.entity.ai.goal.Goal; ++import net.minecraft.world.entity.ambient.AmbientCreature; ++import net.minecraft.world.entity.animal.AbstractFish; ++import net.minecraft.world.entity.animal.AbstractGolem; ++import net.minecraft.world.entity.animal.AbstractSchoolingFish; ++import net.minecraft.world.entity.animal.Animal; ++import net.minecraft.world.entity.animal.Pufferfish; ++import net.minecraft.world.entity.animal.ShoulderRidingEntity; ++import net.minecraft.world.entity.animal.SnowGolem; ++import net.minecraft.world.entity.animal.WaterAnimal; ++import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; ++import net.minecraft.world.entity.boss.wither.WitherBoss; ++import net.minecraft.world.entity.monster.AbstractIllager; ++import net.minecraft.world.entity.monster.EnderMan; ++import net.minecraft.world.entity.monster.PatrollingMonster; ++import net.minecraft.world.entity.monster.RangedAttackMob; ++import net.minecraft.world.entity.monster.SpellcasterIllager; ++import net.minecraft.world.entity.monster.ZombifiedPiglin; ++import net.minecraft.world.entity.monster.piglin.AbstractPiglin; ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.AbstractHorse; ++import org.bukkit.entity.AbstractSkeleton; ++import org.bukkit.entity.AbstractVillager; ++import org.bukkit.entity.Ageable; ++import org.bukkit.entity.Ambient; ++import org.bukkit.entity.Animals; ++import org.bukkit.entity.Bat; ++import org.bukkit.entity.Bee; ++import org.bukkit.entity.Blaze; ++import org.bukkit.entity.Cat; ++import org.bukkit.entity.CaveSpider; ++import org.bukkit.entity.ChestedHorse; ++import org.bukkit.entity.Chicken; ++import org.bukkit.entity.Cod; ++import org.bukkit.entity.Cow; ++import org.bukkit.entity.Creature; ++import org.bukkit.entity.Creeper; ++import org.bukkit.entity.Dolphin; ++import org.bukkit.entity.Donkey; ++import org.bukkit.entity.Drowned; ++import org.bukkit.entity.ElderGuardian; ++import org.bukkit.entity.EnderDragon; ++import org.bukkit.entity.Enderman; ++import org.bukkit.entity.Endermite; ++import org.bukkit.entity.Evoker; ++import org.bukkit.entity.Fish; ++import org.bukkit.entity.Flying; ++import org.bukkit.entity.Fox; ++import org.bukkit.entity.Ghast; ++import org.bukkit.entity.Giant; ++import org.bukkit.entity.Golem; ++import org.bukkit.entity.Guardian; ++import org.bukkit.entity.Hoglin; ++import org.bukkit.entity.Horse; ++import org.bukkit.entity.Husk; ++import org.bukkit.entity.Illager; ++import org.bukkit.entity.Illusioner; ++import org.bukkit.entity.IronGolem; ++import org.bukkit.entity.Llama; ++import org.bukkit.entity.MagmaCube; ++import org.bukkit.entity.Mob; ++import org.bukkit.entity.Monster; ++import org.bukkit.entity.Mule; ++import org.bukkit.entity.MushroomCow; ++import org.bukkit.entity.Ocelot; ++import org.bukkit.entity.Panda; ++import org.bukkit.entity.Parrot; ++import org.bukkit.entity.Phantom; ++import org.bukkit.entity.Pig; ++import org.bukkit.entity.PigZombie; ++import org.bukkit.entity.Piglin; ++import org.bukkit.entity.PiglinAbstract; ++import org.bukkit.entity.PiglinBrute; ++import org.bukkit.entity.Pillager; ++import org.bukkit.entity.PolarBear; ++import org.bukkit.entity.PufferFish; ++import org.bukkit.entity.Rabbit; ++import org.bukkit.entity.Raider; ++import org.bukkit.entity.Ravager; ++import org.bukkit.entity.Salmon; ++import org.bukkit.entity.Sheep; ++import org.bukkit.entity.Shulker; ++import org.bukkit.entity.Silverfish; ++import org.bukkit.entity.Skeleton; ++import org.bukkit.entity.SkeletonHorse; ++import org.bukkit.entity.Slime; ++import org.bukkit.entity.Snowman; ++import org.bukkit.entity.Spellcaster; ++import org.bukkit.entity.Spider; ++import org.bukkit.entity.Squid; ++import org.bukkit.entity.Stray; ++import org.bukkit.entity.Strider; ++import org.bukkit.entity.Tameable; ++import org.bukkit.entity.TraderLlama; ++import org.bukkit.entity.TropicalFish; ++import org.bukkit.entity.Turtle; ++import org.bukkit.entity.Vex; ++import org.bukkit.entity.Villager; ++import org.bukkit.entity.Vindicator; ++import org.bukkit.entity.WanderingTrader; ++import org.bukkit.entity.WaterMob; ++import org.bukkit.entity.Witch; ++import org.bukkit.entity.Wither; ++import org.bukkit.entity.WitherSkeleton; ++import org.bukkit.entity.Wolf; ++import org.bukkit.entity.Zoglin; ++import org.bukkit.entity.Zombie; ++import org.bukkit.entity.ZombieHorse; ++import org.bukkit.entity.ZombieVillager; ++ ++public class MobGoalHelper { ++ ++ private static final BiMap deobfuscationMap = HashBiMap.create(); ++ private static final Map, Class> entityClassCache = new HashMap<>(); ++ private static final Map, Class> bukkitMap = new HashMap<>(); ++ ++ static final Set ignored = new HashSet<>(); ++ ++ static { ++ // TODO these kinda should be checked on each release, in case obfuscation changes ++ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); ++ ++ ignored.add("goal_selector_1"); ++ ignored.add("goal_selector_2"); ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); ++ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); ++ bukkitMap.put(AmbientCreature.class, Ambient.class); ++ bukkitMap.put(Animal.class, Animals.class); ++ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); ++ bukkitMap.put(PathfinderMob.class, Creature.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); ++ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EnderMan.class, Enderman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); ++ bukkitMap.put(AbstractFish.class, Fish.class); ++ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough ++ bukkitMap.put(FlyingMob.class, Flying.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); ++ bukkitMap.put(AbstractGolem.class, Golem.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); ++ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); ++ bukkitMap.put(AbstractIllager.class, Illager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); ++ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); ++ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); ++ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough ++ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); ++ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); ++ bukkitMap.put(Pufferfish.class, PufferFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); ++ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); ++ bukkitMap.put(SnowGolem.class, Snowman.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); ++ bukkitMap.put(TamableAnimal.class, Tameable.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); ++ bukkitMap.put(WaterAnimal.class, WaterMob.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); ++ bukkitMap.put(WitherBoss.class, Wither.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); ++ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); ++ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); ++ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); ++ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = ObfHelper.INSTANCE.deobfClassName(clazz.getName()); ++ name = name.substring(name.lastIndexOf(".") + 1); ++ boolean flag = false; ++ // inner classes ++ if (name.contains("$")) { ++ String cut = name.substring(name.indexOf("$") + 1); ++ if (cut.length() <= 2) { ++ name = name.replace("Entity", ""); ++ name = name.replace("$", "_"); ++ flag = true; ++ } else { ++ // mapped, wooo ++ name = cut; ++ } ++ } ++ name = name.replace("PathfinderGoal", ""); ++ name = name.replace("TargetGoal", ""); ++ name = name.replace("Goal", ""); ++ StringBuilder sb = new StringBuilder(); ++ for (char c : name.toCharArray()) { ++ if (c >= 'A' && c <= 'Z') { ++ sb.append("_"); ++ sb.append(Character.toLowerCase(c)); ++ } else { ++ sb.append(c); ++ } ++ } ++ name = sb.toString(); ++ name = name.replaceFirst("_", ""); ++ ++ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { ++ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); ++ } ++ ++ // did we rename this key? ++ return deobfuscationMap.getOrDefault(name, name); ++ } ++ ++ public static EnumSet vanillaToPaper(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(Goal.Flag type) { ++ switch (type) { ++ case MOVE: ++ return GoalType.MOVE; ++ case LOOK: ++ return GoalType.LOOK; ++ case JUMP: ++ return GoalType.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return GoalType.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return GoalType.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); ++ } ++ } ++ ++ public static EnumSet paperToVanilla(EnumSet types) { ++ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static Goal.Flag paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return Goal.Flag.MOVE; ++ case LOOK: ++ return Goal.Flag.LOOK; ++ case JUMP: ++ return Goal.Flag.JUMP; ++ case UNKNOWN_BEHAVIOR: ++ return Goal.Flag.UNKNOWN_BEHAVIOR; ++ case TARGET: ++ return Goal.Flag.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); ++ } ++ } ++ ++ public static GoalKey getKey(Class goalClass) { ++ String name = getUsableName(goalClass); ++ if (ignored.contains(name)) { ++ //noinspection unchecked ++ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); ++ } ++ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); ++ } ++ ++ public static Class getEntity(Class goalClass) { ++ //noinspection unchecked ++ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { ++ for (Constructor ctor : key.getDeclaredConstructors()) { ++ for (int i = 0; i < ctor.getParameterCount(); i++) { ++ Class param = ctor.getParameterTypes()[i]; ++ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (RangedAttackMob.class.isAssignableFrom(param)) { ++ return RangedEntity.class; ++ } ++ } ++ } ++ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? ++ }); ++ } ++ ++ public static Class toBukkitClass(Class nmsClass) { ++ Class bukkitClass = bukkitMap.get(nmsClass); ++ if (bukkitClass == null) { ++ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? ++ } ++ return bukkitClass; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..26c745dd9ccdfdd5c5039f2acc5201b9b91fb274 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,53 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ if (this.getFlags().size() == 0) { ++ this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ ++ @Override ++ public boolean canUse() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean canContinueToUse() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ public GoalKey getKey() { ++ return handle.getKey(); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +new file mode 100644 +index 0000000000000000000000000000000000000000..336cc3c3b43bacf4f3661fa0bb0736b273f65418 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,213 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import net.minecraft.world.entity.ai.goal.GoalSelector; ++import net.minecraft.world.entity.ai.goal.WrappedGoal; ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++ ++public class PaperMobGoals implements MobGoals { ++ ++ @Override ++ public void addGoal(T mob, int priority, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); ++ } ++ ++ @Override ++ public void removeGoal(T mob, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ if (goal instanceof PaperCustomGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).availableGoals) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(g); ++ } ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob) { ++ for (GoalType type : GoalType.values()) { ++ removeAllGoals(mob, type); ++ } ++ } ++ ++ @Override ++ public void removeAllGoals(T mob, GoalType type) { ++ for (Goal goal : getAllGoals(mob, type)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public void removeGoal(T mob, GoalKey key) { ++ for (Goal goal : getGoals(mob, key)) { ++ removeGoal(mob, goal); ++ } ++ } ++ ++ @Override ++ public boolean hasGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public Goal getGoal(T mob, GoalKey key) { ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ return g; ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public Collection> getGoals(T mob, GoalKey key) { ++ Set> goals = new HashSet<>(); ++ for (Goal g : getAllGoals(mob)) { ++ if (g.getKey().equals(key)) { ++ goals.add(g); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getAllGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (WrappedGoal item : getHandle(craftMob, type).availableGoals) { ++ if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getAllGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ for (WrappedGoal item : getHandle(craftMob, internalType).availableGoals) { ++ if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ } ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob) { ++ Set> goals = new HashSet<>(); ++ for (GoalType type : GoalType.values()) { ++ goals.addAll(getRunningGoals(mob, type)); ++ } ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoals(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ getHandle(craftMob, type).getRunningGoals() ++ .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ return goals; ++ } ++ ++ @Override ++ public Collection> getRunningGoalsWithout(T mob, GoalType type) { ++ CraftMob craftMob = (CraftMob) mob; ++ Set> goals = new HashSet<>(); ++ for (GoalType internalType : GoalType.values()) { ++ if (internalType == type) { ++ continue; ++ } ++ getHandle(craftMob, internalType).getRunningGoals() ++ .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ goals.add(item.getGoal().asPaperVanillaGoal()); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private GoalSelector getHandle(CraftMob mob, GoalType type) { ++ if (type == GoalType.TARGET) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d30e0b21b9024df939a9d070bd4a99b217e7c12 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,61 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import java.util.EnumSet; ++import net.minecraft.world.entity.ai.goal.Goal; ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final Goal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(Goal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle.getFlags()); ++ } ++ ++ public Goal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.canUse(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.canContinueToUse(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.stop(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +index 4379b9948f1eecfe6fd7dea98e298ad5f761019a..3f081183521603824430709886a9cc313c28e7cb 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +@@ -7,6 +7,14 @@ public abstract class Goal { + private final EnumSet flags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector + ++ // Paper start make sure goaltypes is never empty ++ public Goal() { ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } ++ } ++ // Paper end ++ + public abstract boolean canUse(); + + public boolean canContinueToUse() { +@@ -34,6 +42,10 @@ public abstract class Goal { + // Paper start - remove streams from pathfindergoalselector + this.goalTypes.clear(); + this.goalTypes.addAllUnchecked(controls); ++ // make sure its never empty ++ if (this.goalTypes.size() == 0) { ++ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); ++ } + // Paper end - remove streams from pathfindergoalselector + } + +@@ -56,7 +68,19 @@ public abstract class Goal { + return Mth.positiveCeilDiv(serverTicks, 2); + } + ++ // Paper start - mob goal api ++ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal = null; ++ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { ++ if(this.vanillaGoal == null) { ++ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); ++ } ++ //noinspection unchecked ++ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; ++ } ++ // Paper end - mob goal api ++ + public static enum Flag { ++ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR + MOVE, + LOOK, + JUMP, +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4870a16251def58c8259d6f63b3ef6b91687665d..6e4d06e0eadf3b97a3fa2b8b8499ce30335a4fff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2691,5 +2691,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..b2d510459bcf90a3611f3d91dae4ccc3d29b4079 +--- /dev/null ++++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +@@ -0,0 +1,103 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++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(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses(); ++ } ++ ++ List> vanillaNames = classes.stream() ++ .filter(VanillaMobGoalTest::hasNoEnclosingClass) ++ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) ++ .filter(clazz -> !net.minecraft.world.entity.ai.goal.WrappedGoal.class.equals(clazz)) // TODO - properly fix ++ .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.Mob").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/0414-Add-villager-reputation-API.patch b/patches/server/0414-Add-villager-reputation-API.patch new file mode 100644 index 0000000000..4c38780334 --- /dev/null +++ b/patches/server/0414-Add-villager-reputation-API.patch @@ -0,0 +1,127 @@ +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/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index 7a7c92f1a026116958ad24312df358a703834369..0de65462956fa734b6405614e047161696e596fb 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -27,7 +27,7 @@ import net.minecraft.util.VisibleForDebug; + + public class GossipContainer { + public static final int DISCARD_THRESHOLD = 2; +- private final Map gossips = Maps.newHashMap(); ++ private final Map gossips = Maps.newHashMap(); public Map getReputations() { return this.gossips; } // Paper - add getter for reputations + + @VisibleForDebug + public Map> getGossipEntries() { +@@ -226,6 +226,28 @@ public class GossipContainer { + public void remove(GossipType gossipType) { + this.entries.removeInt(gossipType); + } ++ ++ // 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()] = entries.getOrDefault(GossipType.MAJOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_NEGATIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_POSITIVE, 0); ++ reputation[com.destroystokyo.paper.entity.villager.ReputationType.TRADING.ordinal()] = entries.getOrDefault(GossipType.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.entries.put(GossipType.MAJOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE)) != 0) this.entries.put(GossipType.MAJOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE)) != 0) this.entries.put(GossipType.MINOR_NEGATIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE)) != 0) this.entries.put(GossipType.MINOR_POSITIVE, val); ++ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.TRADING)) != 0) this.entries.put(GossipType.TRADING, val); ++ } ++ // Paper end + } + + static class GossipEntry { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +index 1a8a49bd269ed52879866ff3853e131d04aa8bba..f0b910df1ee471b4d72d97c6197ab14f2854976e 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.ZombieVillager; + import org.bukkit.event.entity.CreatureSpawnEvent; + ++// 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, net.minecraft.world.entity.npc.Villager entity) { +@@ -146,4 +153,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { + public static VillagerProfession bukkitToNmsProfession(Profession bukkit) { + return Registry.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.GossipContainer.EntityGossips rep = getHandle().getGossips().getReputations().get(uniqueId); ++ if (rep == null) { ++ return new Reputation(Maps.newHashMap()); ++ } ++ ++ return rep.getPaperReputation(); ++ } ++ ++ @Override ++ public Map getReputations() { ++ return getHandle().getGossips().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.GossipContainer.EntityGossips nmsReputation = ++ getHandle().getGossips().getReputations().computeIfAbsent( ++ uniqueId, ++ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips() ++ ); ++ 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().getGossips().getReputations().clear(); ++ } ++ // Paper end + } diff --git a/patches/server/0414-Implement-Mob-Goal-API.patch b/patches/server/0414-Implement-Mob-Goal-API.patch deleted file mode 100644 index f8748925df..0000000000 --- a/patches/server/0414-Implement-Mob-Goal-API.patch +++ /dev/null @@ -1,915 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Fri, 3 Jan 2020 16:26:19 +0100 -Subject: [PATCH] Implement Mob Goal API - - -diff --git a/build.gradle.kts b/build.gradle.kts -index a1a8c4778742584125d6084fa761b1bc86f6a842..a02f53c6ee0111e07d78a718a6ca0ec708f70cfc 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -37,6 +37,7 @@ dependencies { - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3") - runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3") - -+ testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test - testImplementation("junit:junit:4.13.2") - testImplementation("org.hamcrest:hamcrest-library:1.3") - -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..c9901f8c7f3af57cb2e0bec315caa97adcb6f5ea ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java -@@ -0,0 +1,371 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import com.destroystokyo.paper.entity.RangedEntity; -+import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; -+import com.google.common.collect.BiMap; -+import com.google.common.collect.HashBiMap; -+import io.papermc.paper.util.ObfHelper; -+import java.lang.reflect.Constructor; -+import java.util.EnumSet; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import net.minecraft.world.entity.FlyingMob; -+import net.minecraft.world.entity.PathfinderMob; -+import net.minecraft.world.entity.TamableAnimal; -+import net.minecraft.world.entity.ai.goal.Goal; -+import net.minecraft.world.entity.ambient.AmbientCreature; -+import net.minecraft.world.entity.animal.AbstractFish; -+import net.minecraft.world.entity.animal.AbstractGolem; -+import net.minecraft.world.entity.animal.AbstractSchoolingFish; -+import net.minecraft.world.entity.animal.Animal; -+import net.minecraft.world.entity.animal.Pufferfish; -+import net.minecraft.world.entity.animal.ShoulderRidingEntity; -+import net.minecraft.world.entity.animal.SnowGolem; -+import net.minecraft.world.entity.animal.WaterAnimal; -+import net.minecraft.world.entity.animal.horse.AbstractChestedHorse; -+import net.minecraft.world.entity.boss.wither.WitherBoss; -+import net.minecraft.world.entity.monster.AbstractIllager; -+import net.minecraft.world.entity.monster.EnderMan; -+import net.minecraft.world.entity.monster.PatrollingMonster; -+import net.minecraft.world.entity.monster.RangedAttackMob; -+import net.minecraft.world.entity.monster.SpellcasterIllager; -+import net.minecraft.world.entity.monster.ZombifiedPiglin; -+import net.minecraft.world.entity.monster.piglin.AbstractPiglin; -+import org.bukkit.NamespacedKey; -+import org.bukkit.entity.AbstractHorse; -+import org.bukkit.entity.AbstractSkeleton; -+import org.bukkit.entity.AbstractVillager; -+import org.bukkit.entity.Ageable; -+import org.bukkit.entity.Ambient; -+import org.bukkit.entity.Animals; -+import org.bukkit.entity.Bat; -+import org.bukkit.entity.Bee; -+import org.bukkit.entity.Blaze; -+import org.bukkit.entity.Cat; -+import org.bukkit.entity.CaveSpider; -+import org.bukkit.entity.ChestedHorse; -+import org.bukkit.entity.Chicken; -+import org.bukkit.entity.Cod; -+import org.bukkit.entity.Cow; -+import org.bukkit.entity.Creature; -+import org.bukkit.entity.Creeper; -+import org.bukkit.entity.Dolphin; -+import org.bukkit.entity.Donkey; -+import org.bukkit.entity.Drowned; -+import org.bukkit.entity.ElderGuardian; -+import org.bukkit.entity.EnderDragon; -+import org.bukkit.entity.Enderman; -+import org.bukkit.entity.Endermite; -+import org.bukkit.entity.Evoker; -+import org.bukkit.entity.Fish; -+import org.bukkit.entity.Flying; -+import org.bukkit.entity.Fox; -+import org.bukkit.entity.Ghast; -+import org.bukkit.entity.Giant; -+import org.bukkit.entity.Golem; -+import org.bukkit.entity.Guardian; -+import org.bukkit.entity.Hoglin; -+import org.bukkit.entity.Horse; -+import org.bukkit.entity.Husk; -+import org.bukkit.entity.Illager; -+import org.bukkit.entity.Illusioner; -+import org.bukkit.entity.IronGolem; -+import org.bukkit.entity.Llama; -+import org.bukkit.entity.MagmaCube; -+import org.bukkit.entity.Mob; -+import org.bukkit.entity.Monster; -+import org.bukkit.entity.Mule; -+import org.bukkit.entity.MushroomCow; -+import org.bukkit.entity.Ocelot; -+import org.bukkit.entity.Panda; -+import org.bukkit.entity.Parrot; -+import org.bukkit.entity.Phantom; -+import org.bukkit.entity.Pig; -+import org.bukkit.entity.PigZombie; -+import org.bukkit.entity.Piglin; -+import org.bukkit.entity.PiglinAbstract; -+import org.bukkit.entity.PiglinBrute; -+import org.bukkit.entity.Pillager; -+import org.bukkit.entity.PolarBear; -+import org.bukkit.entity.PufferFish; -+import org.bukkit.entity.Rabbit; -+import org.bukkit.entity.Raider; -+import org.bukkit.entity.Ravager; -+import org.bukkit.entity.Salmon; -+import org.bukkit.entity.Sheep; -+import org.bukkit.entity.Shulker; -+import org.bukkit.entity.Silverfish; -+import org.bukkit.entity.Skeleton; -+import org.bukkit.entity.SkeletonHorse; -+import org.bukkit.entity.Slime; -+import org.bukkit.entity.Snowman; -+import org.bukkit.entity.Spellcaster; -+import org.bukkit.entity.Spider; -+import org.bukkit.entity.Squid; -+import org.bukkit.entity.Stray; -+import org.bukkit.entity.Strider; -+import org.bukkit.entity.Tameable; -+import org.bukkit.entity.TraderLlama; -+import org.bukkit.entity.TropicalFish; -+import org.bukkit.entity.Turtle; -+import org.bukkit.entity.Vex; -+import org.bukkit.entity.Villager; -+import org.bukkit.entity.Vindicator; -+import org.bukkit.entity.WanderingTrader; -+import org.bukkit.entity.WaterMob; -+import org.bukkit.entity.Witch; -+import org.bukkit.entity.Wither; -+import org.bukkit.entity.WitherSkeleton; -+import org.bukkit.entity.Wolf; -+import org.bukkit.entity.Zoglin; -+import org.bukkit.entity.Zombie; -+import org.bukkit.entity.ZombieHorse; -+import org.bukkit.entity.ZombieVillager; -+ -+public class MobGoalHelper { -+ -+ private static final BiMap deobfuscationMap = HashBiMap.create(); -+ private static final Map, Class> entityClassCache = new HashMap<>(); -+ private static final Map, Class> bukkitMap = new HashMap<>(); -+ -+ static final Set ignored = new HashSet<>(); -+ -+ static { -+ // TODO these kinda should be checked on each release, in case obfuscation changes -+ deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); -+ -+ ignored.add("goal_selector_1"); -+ ignored.add("goal_selector_2"); -+ ignored.add("selector_1"); -+ ignored.add("selector_2"); -+ ignored.add("wrapped"); -+ -+ bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); -+ bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); -+ bukkitMap.put(AmbientCreature.class, Ambient.class); -+ bukkitMap.put(Animal.class, Animals.class); -+ bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); -+ bukkitMap.put(PathfinderMob.class, Creature.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); -+ bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); -+ bukkitMap.put(EnderMan.class, Enderman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); -+ bukkitMap.put(AbstractFish.class, Fish.class); -+ bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough -+ bukkitMap.put(FlyingMob.class, Flying.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); -+ bukkitMap.put(AbstractGolem.class, Golem.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); -+ bukkitMap.put(AbstractChestedHorse.class, ChestedHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); -+ bukkitMap.put(AbstractIllager.class, Illager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); -+ bukkitMap.put(SpellcasterIllager.class, Spellcaster.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); -+ bukkitMap.put(PatrollingMonster.class, Raider.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); -+ bukkitMap.put(ShoulderRidingEntity.class, Parrot.class); // close enough -+ bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); -+ bukkitMap.put(ZombifiedPiglin.class, PigZombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); -+ bukkitMap.put(Pufferfish.class, PufferFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); -+ bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); -+ bukkitMap.put(SnowGolem.class, Snowman.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); -+ bukkitMap.put(TamableAnimal.class, Tameable.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); -+ bukkitMap.put(WaterAnimal.class, WaterMob.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); -+ bukkitMap.put(WitherBoss.class, Wither.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); -+ bukkitMap.put(AbstractPiglin.class, PiglinAbstract.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); -+ bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, org.bukkit.entity.GlowSquid.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, org.bukkit.entity.Axolotl.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, org.bukkit.entity.Goat.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, org.bukkit.entity.Frog.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, org.bukkit.entity.Tadpole.class); -+ bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, org.bukkit.entity.Warden.class); -+ bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, org.bukkit.entity.Allay.class); -+ } -+ -+ public static String getUsableName(Class clazz) { -+ String name = ObfHelper.INSTANCE.deobfClassName(clazz.getName()); -+ name = name.substring(name.lastIndexOf(".") + 1); -+ boolean flag = false; -+ // inner classes -+ if (name.contains("$")) { -+ String cut = name.substring(name.indexOf("$") + 1); -+ if (cut.length() <= 2) { -+ name = name.replace("Entity", ""); -+ name = name.replace("$", "_"); -+ flag = true; -+ } else { -+ // mapped, wooo -+ name = cut; -+ } -+ } -+ name = name.replace("PathfinderGoal", ""); -+ name = name.replace("TargetGoal", ""); -+ name = name.replace("Goal", ""); -+ StringBuilder sb = new StringBuilder(); -+ for (char c : name.toCharArray()) { -+ if (c >= 'A' && c <= 'Z') { -+ sb.append("_"); -+ sb.append(Character.toLowerCase(c)); -+ } else { -+ sb.append(c); -+ } -+ } -+ name = sb.toString(); -+ name = name.replaceFirst("_", ""); -+ -+ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { -+ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); -+ } -+ -+ // did we rename this key? -+ return deobfuscationMap.getOrDefault(name, name); -+ } -+ -+ public static EnumSet vanillaToPaper(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(Goal.Flag type) { -+ switch (type) { -+ case MOVE: -+ return GoalType.MOVE; -+ case LOOK: -+ return GoalType.LOOK; -+ case JUMP: -+ return GoalType.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return GoalType.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return GoalType.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); -+ } -+ } -+ -+ public static EnumSet paperToVanilla(EnumSet types) { -+ EnumSet goals = EnumSet.noneOf(Goal.Flag.class); -+ for (GoalType type : types) { -+ goals.add(paperToVanilla(type)); -+ } -+ return goals; -+ } -+ -+ public static Goal.Flag paperToVanilla(GoalType type) { -+ switch (type) { -+ case MOVE: -+ return Goal.Flag.MOVE; -+ case LOOK: -+ return Goal.Flag.LOOK; -+ case JUMP: -+ return Goal.Flag.JUMP; -+ case UNKNOWN_BEHAVIOR: -+ return Goal.Flag.UNKNOWN_BEHAVIOR; -+ case TARGET: -+ return Goal.Flag.TARGET; -+ default: -+ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); -+ } -+ } -+ -+ public static GoalKey getKey(Class goalClass) { -+ String name = getUsableName(goalClass); -+ if (ignored.contains(name)) { -+ //noinspection unchecked -+ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); -+ } -+ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); -+ } -+ -+ public static Class getEntity(Class goalClass) { -+ //noinspection unchecked -+ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { -+ for (Constructor ctor : key.getDeclaredConstructors()) { -+ for (int i = 0; i < ctor.getParameterCount(); i++) { -+ Class param = ctor.getParameterTypes()[i]; -+ if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { -+ //noinspection unchecked -+ return toBukkitClass((Class) param); -+ } else if (RangedAttackMob.class.isAssignableFrom(param)) { -+ return RangedEntity.class; -+ } -+ } -+ } -+ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? -+ }); -+ } -+ -+ public static Class toBukkitClass(Class nmsClass) { -+ Class bukkitClass = bukkitMap.get(nmsClass); -+ if (bukkitClass == null) { -+ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? -+ } -+ return bukkitClass; -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..26c745dd9ccdfdd5c5039f2acc5201b9b91fb274 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java -@@ -0,0 +1,53 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps api in vanilla -+ */ -+public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { -+ -+ private final Goal handle; -+ -+ public PaperCustomGoal(Goal handle) { -+ this.handle = handle; -+ -+ this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); -+ if (this.getFlags().size() == 0) { -+ this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR); -+ } -+ } -+ -+ @Override -+ public boolean canUse() { -+ return handle.shouldActivate(); -+ } -+ -+ @Override -+ public boolean canContinueToUse() { -+ return handle.shouldStayActive(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ public GoalKey getKey() { -+ return handle.getKey(); -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -new file mode 100644 -index 0000000000000000000000000000000000000000..336cc3c3b43bacf4f3661fa0bb0736b273f65418 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java -@@ -0,0 +1,213 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.Collection; -+import java.util.EnumSet; -+import java.util.HashSet; -+import java.util.LinkedList; -+import java.util.List; -+import java.util.Set; -+import net.minecraft.world.entity.ai.goal.GoalSelector; -+import net.minecraft.world.entity.ai.goal.WrappedGoal; -+import org.bukkit.craftbukkit.entity.CraftMob; -+import org.bukkit.entity.Mob; -+ -+public class PaperMobGoals implements MobGoals { -+ -+ @Override -+ public void addGoal(T mob, int priority, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); -+ } -+ -+ @Override -+ public void removeGoal(T mob, Goal goal) { -+ CraftMob craftMob = (CraftMob) mob; -+ if (goal instanceof PaperCustomGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); -+ } else if (goal instanceof PaperVanillaGoal) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); -+ } else { -+ List toRemove = new LinkedList<>(); -+ for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).availableGoals) { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { -+ toRemove.add(item.getGoal()); -+ } -+ } -+ } -+ -+ for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { -+ getHandle(craftMob, goal.getTypes()).removeGoal(g); -+ } -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob) { -+ for (GoalType type : GoalType.values()) { -+ removeAllGoals(mob, type); -+ } -+ } -+ -+ @Override -+ public void removeAllGoals(T mob, GoalType type) { -+ for (Goal goal : getAllGoals(mob, type)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public void removeGoal(T mob, GoalKey key) { -+ for (Goal goal : getGoals(mob, key)) { -+ removeGoal(mob, goal); -+ } -+ } -+ -+ @Override -+ public boolean hasGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public Goal getGoal(T mob, GoalKey key) { -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ return g; -+ } -+ } -+ return null; -+ } -+ -+ @Override -+ public Collection> getGoals(T mob, GoalKey key) { -+ Set> goals = new HashSet<>(); -+ for (Goal g : getAllGoals(mob)) { -+ if (g.getKey().equals(key)) { -+ goals.add(g); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getAllGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (WrappedGoal item : getHandle(craftMob, type).availableGoals) { -+ if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getAllGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ for (WrappedGoal item : getHandle(craftMob, internalType).availableGoals) { -+ if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { -+ continue; -+ } -+ -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ } -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob) { -+ Set> goals = new HashSet<>(); -+ for (GoalType type : GoalType.values()) { -+ goals.addAll(getRunningGoals(mob, type)); -+ } -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoals(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ getHandle(craftMob, type).getRunningGoals() -+ .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ return goals; -+ } -+ -+ @Override -+ public Collection> getRunningGoalsWithout(T mob, GoalType type) { -+ CraftMob craftMob = (CraftMob) mob; -+ Set> goals = new HashSet<>(); -+ for (GoalType internalType : GoalType.values()) { -+ if (internalType == type) { -+ continue; -+ } -+ getHandle(craftMob, internalType).getRunningGoals() -+ .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) -+ .forEach(item -> { -+ if (item.getGoal() instanceof PaperCustomGoal) { -+ //noinspection unchecked -+ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); -+ } else { -+ goals.add(item.getGoal().asPaperVanillaGoal()); -+ } -+ }); -+ } -+ return goals; -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, EnumSet types) { -+ if (types.contains(GoalType.TARGET)) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+ -+ private GoalSelector getHandle(CraftMob mob, GoalType type) { -+ if (type == GoalType.TARGET) { -+ return mob.getHandle().targetSelector; -+ } else { -+ return mob.getHandle().goalSelector; -+ } -+ } -+} -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d30e0b21b9024df939a9d070bd4a99b217e7c12 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java -@@ -0,0 +1,61 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+import java.util.EnumSet; -+import net.minecraft.world.entity.ai.goal.Goal; -+import org.bukkit.entity.Mob; -+ -+/** -+ * Wraps vanilla in api -+ */ -+public class PaperVanillaGoal implements VanillaGoal { -+ -+ private final Goal handle; -+ private final GoalKey key; -+ -+ private final EnumSet types; -+ -+ public PaperVanillaGoal(Goal handle) { -+ this.handle = handle; -+ this.key = MobGoalHelper.getKey(handle.getClass()); -+ this.types = MobGoalHelper.vanillaToPaper(handle.getFlags()); -+ } -+ -+ public Goal getHandle() { -+ return handle; -+ } -+ -+ @Override -+ public boolean shouldActivate() { -+ return handle.canUse(); -+ } -+ -+ @Override -+ public boolean shouldStayActive() { -+ return handle.canContinueToUse(); -+ } -+ -+ @Override -+ public void start() { -+ handle.start(); -+ } -+ -+ @Override -+ public void stop() { -+ handle.stop(); -+ } -+ -+ @Override -+ public void tick() { -+ handle.tick(); -+ } -+ -+ @Override -+ public GoalKey getKey() { -+ return key; -+ } -+ -+ @Override -+ public EnumSet getTypes() { -+ return types; -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -index 4379b9948f1eecfe6fd7dea98e298ad5f761019a..3f081183521603824430709886a9cc313c28e7cb 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java -@@ -7,6 +7,14 @@ public abstract class Goal { - private final EnumSet flags = EnumSet.noneOf(Goal.Flag.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 com.destroystokyo.paper.util.set.OptimizedSmallEnumSet goalTypes = new com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from pathfindergoalselector - -+ // Paper start make sure goaltypes is never empty -+ public Goal() { -+ if (this.goalTypes.size() == 0) { -+ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); -+ } -+ } -+ // Paper end -+ - public abstract boolean canUse(); - - public boolean canContinueToUse() { -@@ -34,6 +42,10 @@ public abstract class Goal { - // Paper start - remove streams from pathfindergoalselector - this.goalTypes.clear(); - this.goalTypes.addAllUnchecked(controls); -+ // make sure its never empty -+ if (this.goalTypes.size() == 0) { -+ this.goalTypes.addUnchecked(Flag.UNKNOWN_BEHAVIOR); -+ } - // Paper end - remove streams from pathfindergoalselector - } - -@@ -56,7 +68,19 @@ public abstract class Goal { - return Mth.positiveCeilDiv(serverTicks, 2); - } - -+ // Paper start - mob goal api -+ private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal = null; -+ public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { -+ if(this.vanillaGoal == null) { -+ this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); -+ } -+ //noinspection unchecked -+ return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; -+ } -+ // Paper end - mob goal api -+ - public static enum Flag { -+ UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR - MOVE, - LOOK, - JUMP, -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4870a16251def58c8259d6f63b3ef6b91687665d..6e4d06e0eadf3b97a3fa2b8b8499ce30335a4fff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2691,5 +2691,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..b2d510459bcf90a3611f3d91dae4ccc3d29b4079 ---- /dev/null -+++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -@@ -0,0 +1,103 @@ -+package com.destroystokyo.paper.entity.ai; -+ -+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(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses(); -+ } -+ -+ List> vanillaNames = classes.stream() -+ .filter(VanillaMobGoalTest::hasNoEnclosingClass) -+ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) -+ .filter(clazz -> !net.minecraft.world.entity.ai.goal.WrappedGoal.class.equals(clazz)) // TODO - properly fix -+ .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.Mob").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/0415-Add-villager-reputation-API.patch b/patches/server/0415-Add-villager-reputation-API.patch deleted file mode 100644 index 4c38780334..0000000000 --- a/patches/server/0415-Add-villager-reputation-API.patch +++ /dev/null @@ -1,127 +0,0 @@ -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/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -index 7a7c92f1a026116958ad24312df358a703834369..0de65462956fa734b6405614e047161696e596fb 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -@@ -27,7 +27,7 @@ import net.minecraft.util.VisibleForDebug; - - public class GossipContainer { - public static final int DISCARD_THRESHOLD = 2; -- private final Map gossips = Maps.newHashMap(); -+ private final Map gossips = Maps.newHashMap(); public Map getReputations() { return this.gossips; } // Paper - add getter for reputations - - @VisibleForDebug - public Map> getGossipEntries() { -@@ -226,6 +226,28 @@ public class GossipContainer { - public void remove(GossipType gossipType) { - this.entries.removeInt(gossipType); - } -+ -+ // 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()] = entries.getOrDefault(GossipType.MAJOR_NEGATIVE, 0); -+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MAJOR_POSITIVE, 0); -+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_NEGATIVE, 0); -+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE.ordinal()] = entries.getOrDefault(GossipType.MINOR_POSITIVE, 0); -+ reputation[com.destroystokyo.paper.entity.villager.ReputationType.TRADING.ordinal()] = entries.getOrDefault(GossipType.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.entries.put(GossipType.MAJOR_NEGATIVE, val); -+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MAJOR_POSITIVE)) != 0) this.entries.put(GossipType.MAJOR_POSITIVE, val); -+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_NEGATIVE)) != 0) this.entries.put(GossipType.MINOR_NEGATIVE, val); -+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.MINOR_POSITIVE)) != 0) this.entries.put(GossipType.MINOR_POSITIVE, val); -+ if ((val = rep.getReputation(com.destroystokyo.paper.entity.villager.ReputationType.TRADING)) != 0) this.entries.put(GossipType.TRADING, val); -+ } -+ // Paper end - } - - static class GossipEntry { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 1a8a49bd269ed52879866ff3853e131d04aa8bba..f0b910df1ee471b4d72d97c6197ab14f2854976e 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.ZombieVillager; - import org.bukkit.event.entity.CreatureSpawnEvent; - -+// 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, net.minecraft.world.entity.npc.Villager entity) { -@@ -146,4 +153,45 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - public static VillagerProfession bukkitToNmsProfession(Profession bukkit) { - return Registry.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.GossipContainer.EntityGossips rep = getHandle().getGossips().getReputations().get(uniqueId); -+ if (rep == null) { -+ return new Reputation(Maps.newHashMap()); -+ } -+ -+ return rep.getPaperReputation(); -+ } -+ -+ @Override -+ public Map getReputations() { -+ return getHandle().getGossips().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.GossipContainer.EntityGossips nmsReputation = -+ getHandle().getGossips().getReputations().computeIfAbsent( -+ uniqueId, -+ key -> new net.minecraft.world.entity.ai.gossip.GossipContainer.EntityGossips() -+ ); -+ 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().getGossips().getReputations().clear(); -+ } -+ // Paper end - } diff --git a/patches/server/0415-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server/0415-Option-for-maximum-exp-value-when-merging-orbs.patch new file mode 100644 index 0000000000..4239e066f4 --- /dev/null +++ b/patches/server/0415-Option-for-maximum-exp-value-when-merging-orbs.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 10 Nov 2017 23:03:12 -0500 +Subject: [PATCH] Option for maximum exp value when merging orbs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 22da112f45ddb20d113550eae67ac08eb2fcb727..f80bdb38cf377684382bb817fedbeb8d5aae4704 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -640,16 +640,30 @@ public class CraftEventFactory { + net.minecraft.world.entity.ExperienceOrb xp = (net.minecraft.world.entity.ExperienceOrb) 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().entities.behavior.experienceMergeMaxValue; ++ final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; ++ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary ++ + List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof net.minecraft.world.entity.ExperienceOrb) { + net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; +- if (!loopItem.isRemoved()) { ++ // Paper start ++ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ long newTotal = (long)xp.value + (long)loopItem.value; ++ if ((int) newTotal < 0) continue; // Overflow ++ if (maxValue > 0 && newTotal > (long)maxValue) { ++ loopItem.value = (int) (newTotal - maxValue); ++ xp.value = maxValue; ++ } else { + xp.value += loopItem.value; + loopItem.discard(); ++ } // Paper end + } + } + } ++ } // Paper end - End iteration skip check - All tweaking ends here + } + // Spigot end + } else if (!(entity instanceof ServerPlayer)) { diff --git a/patches/server/0416-ExperienceOrbMergeEvent.patch b/patches/server/0416-ExperienceOrbMergeEvent.patch new file mode 100644 index 0000000000..1ff06b6779 --- /dev/null +++ b/patches/server/0416-ExperienceOrbMergeEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 19 Dec 2017 22:57:26 -0500 +Subject: [PATCH] ExperienceOrbMergeEvent + +Has to be reimplemented at one point maybe +Fired when the server is about to merge 2 experience orbs +Plugins can cancel this if they want to ensure experience orbs do not lose important +metadata such as spawn reason, or conditionally move data from source to target. + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index f80bdb38cf377684382bb817fedbeb8d5aae4704..9b94df9940040f51fdcc1af5c7da96117af9017e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -650,7 +650,7 @@ public class CraftEventFactory { + if (e instanceof net.minecraft.world.entity.ExperienceOrb) { + net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; + // Paper start +- if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { ++ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent + long newTotal = (long)xp.value + (long)loopItem.value; + if ((int) newTotal < 0) continue; // Overflow + if (maxValue > 0 && newTotal > (long)maxValue) { diff --git a/patches/server/0416-Option-for-maximum-exp-value-when-merging-orbs.patch b/patches/server/0416-Option-for-maximum-exp-value-when-merging-orbs.patch deleted file mode 100644 index 4239e066f4..0000000000 --- a/patches/server/0416-Option-for-maximum-exp-value-when-merging-orbs.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 10 Nov 2017 23:03:12 -0500 -Subject: [PATCH] Option for maximum exp value when merging orbs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 22da112f45ddb20d113550eae67ac08eb2fcb727..f80bdb38cf377684382bb817fedbeb8d5aae4704 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -640,16 +640,30 @@ public class CraftEventFactory { - net.minecraft.world.entity.ExperienceOrb xp = (net.minecraft.world.entity.ExperienceOrb) 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().entities.behavior.experienceMergeMaxValue; -+ final boolean mergeUnconditionally = world.paperConfig().entities.behavior.experienceMergeMaxValue <= 0; -+ if (mergeUnconditionally || xp.value < maxValue) { // Paper - Skip iteration if unnecessary -+ - List entities = world.getEntities(entity, entity.getBoundingBox().inflate(radius, radius, radius)); - for (Entity e : entities) { - if (e instanceof net.minecraft.world.entity.ExperienceOrb) { - net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; -- if (!loopItem.isRemoved()) { -+ // Paper start -+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { -+ long newTotal = (long)xp.value + (long)loopItem.value; -+ if ((int) newTotal < 0) continue; // Overflow -+ if (maxValue > 0 && newTotal > (long)maxValue) { -+ loopItem.value = (int) (newTotal - maxValue); -+ xp.value = maxValue; -+ } else { - xp.value += loopItem.value; - loopItem.discard(); -+ } // Paper end - } - } - } -+ } // Paper end - End iteration skip check - All tweaking ends here - } - // Spigot end - } else if (!(entity instanceof ServerPlayer)) { diff --git a/patches/server/0417-ExperienceOrbMergeEvent.patch b/patches/server/0417-ExperienceOrbMergeEvent.patch deleted file mode 100644 index 1ff06b6779..0000000000 --- a/patches/server/0417-ExperienceOrbMergeEvent.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 19 Dec 2017 22:57:26 -0500 -Subject: [PATCH] ExperienceOrbMergeEvent - -Has to be reimplemented at one point maybe -Fired when the server is about to merge 2 experience orbs -Plugins can cancel this if they want to ensure experience orbs do not lose important -metadata such as spawn reason, or conditionally move data from source to target. - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index f80bdb38cf377684382bb817fedbeb8d5aae4704..9b94df9940040f51fdcc1af5c7da96117af9017e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -650,7 +650,7 @@ public class CraftEventFactory { - if (e instanceof net.minecraft.world.entity.ExperienceOrb) { - net.minecraft.world.entity.ExperienceOrb loopItem = (net.minecraft.world.entity.ExperienceOrb) e; - // Paper start -- if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue)) { -+ if (!loopItem.isRemoved() && !(maxValue > 0 && loopItem.value >= maxValue) && new com.destroystokyo.paper.event.entity.ExperienceOrbMergeEvent((org.bukkit.entity.ExperienceOrb) entity.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) loopItem.getBukkitEntity()).callEvent()) { // Paper - ExperienceOrbMergeEvent - long newTotal = (long)xp.value + (long)loopItem.value; - if ((int) newTotal < 0) continue; // Overflow - if (maxValue > 0 && newTotal > (long)maxValue) { diff --git a/patches/server/0417-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0417-Fix-PotionEffect-ignores-icon-flag.patch new file mode 100644 index 0000000000..1091a96abf --- /dev/null +++ b/patches/server/0417-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 ee6783220de6c3142810744a68fda51367589bd1..55881c189e96bccd6538dfb6b4ea897b72d3936d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -414,7 +414,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { +- this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); ++ this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(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/0418-Fix-PotionEffect-ignores-icon-flag.patch b/patches/server/0418-Fix-PotionEffect-ignores-icon-flag.patch deleted file mode 100644 index e542865e62..0000000000 --- a/patches/server/0418-Fix-PotionEffect-ignores-icon-flag.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: kickash32 -Date: Fri, 8 May 2020 00:49:18 -0400 -Subject: [PATCH] Fix PotionEffect ignores icon flag - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index be1540b0a5f95f8a85f91d5fe398cd2cf8832ec4..639d376bf382409410e26385134d36fd6e3b5f0c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -412,7 +412,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { -- this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN); -+ this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(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/0418-Optimize-brigadier-child-sorting-performance.patch b/patches/server/0418-Optimize-brigadier-child-sorting-performance.patch new file mode 100644 index 0000000000..3651f34b15 --- /dev/null +++ b/patches/server/0418-Optimize-brigadier-child-sorting-performance.patch @@ -0,0 +1,28 @@ +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 3384501f83d445f45aa8233e98c7597daa67b8ef..20a7cdf87f307878d66922aaac0c60cff218e46c 100644 +--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java ++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java +@@ -26,7 +26,7 @@ import java.util.function.Predicate; + import net.minecraft.commands.CommandSourceStack; + + public abstract class CommandNode implements Comparable> { +- private final Map> children = new LinkedHashMap<>(); ++ private Map> children = com.google.common.collect.Maps.newTreeMap(); // Paper - Switch to tree map for automatic sorting + private final Map> literals = new LinkedHashMap<>(); + private final Map> arguments = new LinkedHashMap<>(); + public Predicate requirement; +@@ -107,6 +107,8 @@ public abstract class CommandNode implements Comparable> { + this.arguments.put(node.getName(), (ArgumentCommandNode) node); + } + } ++ ++ // Paper - Remove manual sorting, it is no longer needed + } + + public void findAmbiguities(final AmbiguityConsumer consumer) { diff --git a/patches/server/0419-Optimize-brigadier-child-sorting-performance.patch b/patches/server/0419-Optimize-brigadier-child-sorting-performance.patch deleted file mode 100644 index 3651f34b15..0000000000 --- a/patches/server/0419-Optimize-brigadier-child-sorting-performance.patch +++ /dev/null @@ -1,28 +0,0 @@ -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 3384501f83d445f45aa8233e98c7597daa67b8ef..20a7cdf87f307878d66922aaac0c60cff218e46c 100644 ---- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java -+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java -@@ -26,7 +26,7 @@ import java.util.function.Predicate; - import net.minecraft.commands.CommandSourceStack; - - public abstract class CommandNode implements Comparable> { -- private final Map> children = new LinkedHashMap<>(); -+ private Map> children = com.google.common.collect.Maps.newTreeMap(); // Paper - Switch to tree map for automatic sorting - private final Map> literals = new LinkedHashMap<>(); - private final Map> arguments = new LinkedHashMap<>(); - public Predicate requirement; -@@ -107,6 +107,8 @@ public abstract class CommandNode implements Comparable> { - this.arguments.put(node.getName(), (ArgumentCommandNode) node); - } - } -+ -+ // Paper - Remove manual sorting, it is no longer needed - } - - public void findAmbiguities(final AmbiguityConsumer consumer) { diff --git a/patches/server/0419-Potential-bed-API.patch b/patches/server/0419-Potential-bed-API.patch new file mode 100644 index 0000000000..633d9baf8a --- /dev/null +++ b/patches/server/0419-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 f5f30e1408892b4e728053bc5005e551396583a5..15f6e1c04fefa1334301534646b8ed2535d16fa9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -12,6 +12,7 @@ import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; + import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.MenuProvider; + import net.minecraft.world.entity.Entity; +@@ -129,6 +130,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().sleepCounter; + } + ++ // Paper start - Potential bed api ++ @Override ++ public Location getPotentialBedLocation() { ++ ServerPlayer handle = (ServerPlayer) getHandle(); ++ BlockPos bed = handle.getRespawnPosition(); ++ if (bed == null) { ++ return null; ++ } ++ ++ ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension()); ++ 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/0420-Potential-bed-API.patch b/patches/server/0420-Potential-bed-API.patch deleted file mode 100644 index d63e2bb9ee..0000000000 --- a/patches/server/0420-Potential-bed-API.patch +++ /dev/null @@ -1,44 +0,0 @@ -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 3866c466fcc40f17f88063acb939f9091708a92f..3a14cc3d0d692c8bbc90de1b1c5731158b1323e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -12,6 +12,7 @@ import net.minecraft.nbt.CompoundTag; - import net.minecraft.network.chat.Component; - import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; - import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.MenuProvider; - import net.minecraft.world.entity.Entity; -@@ -126,6 +127,22 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return this.getHandle().sleepCounter; - } - -+ // Paper start - Potential bed api -+ @Override -+ public Location getPotentialBedLocation() { -+ ServerPlayer handle = (ServerPlayer) getHandle(); -+ BlockPos bed = handle.getRespawnPosition(); -+ if (bed == null) { -+ return null; -+ } -+ -+ ServerLevel worldServer = handle.server.getLevel(handle.getRespawnDimension()); -+ 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/0420-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0420-Wait-for-Async-Tasks-during-shutdown.patch new file mode 100644 index 0000000000..6d308392ef --- /dev/null +++ b/patches/server/0420-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 ff94b07246b5e17be53f4e7294557c6744c62248..9c2589c7e0517f771b9ca06760273a0aecefb27d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -911,6 +911,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException e) {} ++ pollCount++; ++ } ++ ++ List overdueWorkers = getScheduler().getActiveWorkers(); ++ for (BukkitWorker worker : overdueWorkers) { ++ Plugin plugin = worker.getOwner(); ++ 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() { + ReloadCommand.reload(console); diff --git a/patches/server/0421-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server/0421-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch new file mode 100644 index 0000000000..94dbb79f58 --- /dev/null +++ b/patches/server/0421-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch @@ -0,0 +1,19 @@ +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/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java +index 6babc2ccd99b739a03145e9ac6f676a46f06c773..4bb9730b6a42702e91467f980b9f045585039db3 100644 +--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java ++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java +@@ -312,6 +312,7 @@ public abstract class Raider extends PatrollingMonster { + + @Override + public boolean canUse() { ++ if (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + Raid raid = this.mob.getCurrentRaid(); + + if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) { diff --git a/patches/server/0421-Wait-for-Async-Tasks-during-shutdown.patch b/patches/server/0421-Wait-for-Async-Tasks-during-shutdown.patch deleted file mode 100644 index 6d308392ef..0000000000 --- a/patches/server/0421-Wait-for-Async-Tasks-during-shutdown.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:16:17 -0400 -Subject: [PATCH] Wait for Async Tasks during shutdown - -Server.reload() had this logic to give time for tasks to shutdown, -however shutdown did not... - -Adds a 5 second grace period for any async tasks to finish and warns -if any are still running after that delay just as reload does. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index ff94b07246b5e17be53f4e7294557c6744c62248..9c2589c7e0517f771b9ca06760273a0aecefb27d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -911,6 +911,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { -+ try { -+ Thread.sleep(100); -+ } catch (InterruptedException e) {} -+ pollCount++; -+ } -+ -+ List overdueWorkers = getScheduler().getActiveWorkers(); -+ for (BukkitWorker worker : overdueWorkers) { -+ Plugin plugin = worker.getOwner(); -+ 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() { - ReloadCommand.reload(console); diff --git a/patches/server/0422-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch b/patches/server/0422-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch deleted file mode 100644 index 94dbb79f58..0000000000 --- a/patches/server/0422-Ensure-EntityRaider-respects-game-and-entity-rules-f.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 6babc2ccd99b739a03145e9ac6f676a46f06c773..4bb9730b6a42702e91467f980b9f045585039db3 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -312,6 +312,7 @@ public abstract class Raider extends PatrollingMonster { - - @Override - public boolean canUse() { -+ if (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items - Raid raid = this.mob.getCurrentRaid(); - - if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) { diff --git a/patches/server/0422-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/0422-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch new file mode 100644 index 0000000000..de3bd7b059 --- /dev/null +++ b/patches/server/0422-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch @@ -0,0 +1,169 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 13 May 2020 23:01:26 -0400 +Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed + +This fixes exploits that let players destroy bedrock by Pistons, explosions +and Mushrooom/Tree generation. + +These blocks are designed to not be broken except by creative players/commands. +So protect them from a multitude of methods of destroying them. + +A config is provided if you rather let players use these exploits, and let +them destroy the worlds End Portals and get on top of the nether easy. + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 38bb502e9f1272020a23a3ef8ebb0cb1a5a251ef..b18b0e1b5e059f08fd3117eaa0fb28a10fac6562 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -172,6 +172,7 @@ public class Explosion { + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockposition = new BlockPos(d4, d5, d6); + BlockState iblockdata = this.level.getBlockState(blockposition); ++ if (!iblockdata.isDestroyable()) continue; // Paper + FluidState fluid = iblockdata.getFluidState(); // Paper + + if (!this.level.isInWorldBounds(blockposition)) { +@@ -350,7 +351,7 @@ public class Explosion { + BlockState iblockdata = this.level.getBlockState(blockposition); + Block block = iblockdata.getBlock(); + +- if (!iblockdata.isAir()) { ++ if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper + BlockPos blockposition1 = blockposition.immutable(); + + this.level.getProfiler().push("explosion_blocks"); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 4c93c2ee69c2728d798a750981275f4fc6840525..160a3cb1d70f765d277169bb4928238b6a575f26 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -429,6 +429,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { ++ // Paper start ++ BlockState type = getBlockState(pos); ++ if (!type.isDestroyable()) return false; ++ // Paper end + CraftBlockState blockstate = this.capturedBlockStates.get(pos); + if (blockstate == null) { + blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 77125720fcbeb7bfc180effb27cfb78c74832ce5..cb11cef117fc896ddcb40993ddb852a2e717c2ad 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -92,6 +92,19 @@ public class Block extends BlockBehaviour implements ItemLike { + protected final StateDefinition stateDefinition; + private BlockState defaultBlockState; + // Paper start ++ public final boolean isDestroyable() { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || ++ this != Blocks.BEDROCK && ++ this != Blocks.END_PORTAL_FRAME && ++ this != Blocks.END_PORTAL && ++ this != Blocks.END_GATEWAY && ++ this != Blocks.COMMAND_BLOCK && ++ this != Blocks.REPEATING_COMMAND_BLOCK && ++ this != Blocks.CHAIN_COMMAND_BLOCK && ++ this != Blocks.BARRIER && ++ this != Blocks.STRUCTURE_BLOCK && ++ this != Blocks.JIGSAW; ++ } + public co.aikar.timings.Timing timing; + public co.aikar.timings.Timing getTiming() { + if (timing == null) { +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 76720517cd2d82065eb8430cf854b536295341db..29755807fdb6c30e31c0ec2bbf33bed9afd5d478 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -199,6 +199,12 @@ public class PistonBaseBlock extends DirectionalBlock { + @Override + public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { + Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); ++ // Paper start - prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) ++ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { ++ return false; ++ } ++ // Paper end - prevent retracting when we're facing the wrong way + + if (!world.isClientSide) { + boolean flag = this.getNeighborSignal(world, pos, enumdirection); +@@ -231,7 +237,7 @@ public class PistonBaseBlock extends DirectionalBlock { + BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); + + world.setBlock(pos, iblockdata1, 20); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change + world.blockUpdated(pos, iblockdata1.getBlock()); + iblockdata1.updateNeighbourShapes(world, pos, 2); + if (this.isSticky) { +@@ -260,7 +266,14 @@ public class PistonBaseBlock extends DirectionalBlock { + } + } + } else { +- world.removeBlock(pos.relative(enumdirection), false); ++ // Paper start - fix headless pistons breaking blocks ++ BlockPos headPos = pos.relative(enumdirection); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. ++ world.removeBlock(headPos, false); ++ } else { ++ ((ServerLevel)world).getChunkSource().blockChanged(headPos); // ... fix client desync ++ } ++ // Paper end - fix headless pistons breaking blocks + } + + world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 3ec96c7f2ad0d6ba8ad32a4aabb0292b5c2ff5b4..ad38a7ced7f3dc05fb3d133e9da39f0a5eb0915b 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -223,7 +223,7 @@ public abstract class BlockBehaviour { + /** @deprecated */ + @Deprecated + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { +- return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); ++ return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper + } + + /** @deprecated */ +@@ -727,6 +727,12 @@ public abstract class BlockBehaviour { + return ((Block) this.owner).builtInRegistryHolder(); + } + ++ // Paper start ++ public final boolean isDestroyable() { ++ return getBlock().isDestroyable(); ++ } ++ // Paper end ++ + public Material getMaterial() { + return this.material; + } +@@ -824,7 +830,7 @@ public abstract class BlockBehaviour { + } + + public PushReaction getPistonPushReaction() { +- return this.getBlock().getPistonPushReaction(this.asState()); ++ return !isDestroyable() ? PushReaction.BLOCK : this.getBlock().getPistonPushReaction(this.asState()); // Paper + } + + public boolean isSolidRender(BlockGetter world, BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index 7aa6181237eaec1df2ed2fdcd3b1137dfe89ce69..1311d69bb2fa7b3617936e6ad6eb5236fedc260d 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -210,6 +210,13 @@ public class PortalForcer { + for (int j = -1; j < 3; ++j) { + for (int k = -1; k < 4; ++k) { + temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); ++ // Paper start - prevent destroying unbreakable blocks ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { ++ if (!this.level.getBlockState(temp).isDestroyable()) { ++ return false; ++ } ++ } ++ // Paper end - prevent destroying unbreakable blocks + if (k < 0 && !this.level.getBlockState(temp).getMaterial().isSolid()) { + return false; + } diff --git a/patches/server/0423-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch b/patches/server/0423-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch deleted file mode 100644 index f775181ba8..0000000000 --- a/patches/server/0423-Protect-Bedrock-and-End-Portal-Frames-from-being-des.patch +++ /dev/null @@ -1,169 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 13 May 2020 23:01:26 -0400 -Subject: [PATCH] Protect Bedrock and End Portal/Frames from being destroyed - -This fixes exploits that let players destroy bedrock by Pistons, explosions -and Mushrooom/Tree generation. - -These blocks are designed to not be broken except by creative players/commands. -So protect them from a multitude of methods of destroying them. - -A config is provided if you rather let players use these exploits, and let -them destroy the worlds End Portals and get on top of the nether easy. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 38bb502e9f1272020a23a3ef8ebb0cb1a5a251ef..b18b0e1b5e059f08fd3117eaa0fb28a10fac6562 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -172,6 +172,7 @@ public class Explosion { - for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { - BlockPos blockposition = new BlockPos(d4, d5, d6); - BlockState iblockdata = this.level.getBlockState(blockposition); -+ if (!iblockdata.isDestroyable()) continue; // Paper - FluidState fluid = iblockdata.getFluidState(); // Paper - - if (!this.level.isInWorldBounds(blockposition)) { -@@ -350,7 +351,7 @@ public class Explosion { - BlockState iblockdata = this.level.getBlockState(blockposition); - Block block = iblockdata.getBlock(); - -- if (!iblockdata.isAir()) { -+ if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper - BlockPos blockposition1 = blockposition.immutable(); - - this.level.getProfiler().push("explosion_blocks"); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c488e069a19d4bf082c94032571fcc77c0bada50..d5e80a0d953e7792669f21011bc685adaec78464 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -429,6 +429,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { - // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { -+ // Paper start -+ BlockState type = getBlockState(pos); -+ if (!type.isDestroyable()) return false; -+ // Paper end - CraftBlockState blockstate = this.capturedBlockStates.get(pos); - if (blockstate == null) { - blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 77125720fcbeb7bfc180effb27cfb78c74832ce5..cb11cef117fc896ddcb40993ddb852a2e717c2ad 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -92,6 +92,19 @@ public class Block extends BlockBehaviour implements ItemLike { - protected final StateDefinition stateDefinition; - private BlockState defaultBlockState; - // Paper start -+ public final boolean isDestroyable() { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -+ this != Blocks.BEDROCK && -+ this != Blocks.END_PORTAL_FRAME && -+ this != Blocks.END_PORTAL && -+ this != Blocks.END_GATEWAY && -+ this != Blocks.COMMAND_BLOCK && -+ this != Blocks.REPEATING_COMMAND_BLOCK && -+ this != Blocks.CHAIN_COMMAND_BLOCK && -+ this != Blocks.BARRIER && -+ this != Blocks.STRUCTURE_BLOCK && -+ this != Blocks.JIGSAW; -+ } - public co.aikar.timings.Timing timing; - public co.aikar.timings.Timing getTiming() { - if (timing == null) { -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 76720517cd2d82065eb8430cf854b536295341db..29755807fdb6c30e31c0ec2bbf33bed9afd5d478 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -199,6 +199,12 @@ public class PistonBaseBlock extends DirectionalBlock { - @Override - public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { - Direction enumdirection = (Direction) state.getValue(PistonBaseBlock.FACING); -+ // Paper start - prevent retracting when we're facing the wrong way (we were replaced before retraction could occur) -+ Direction directionQueuedAs = Direction.from3DDataValue(data & 7); // Paper - copied from below -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits && enumdirection != directionQueuedAs) { -+ return false; -+ } -+ // Paper end - prevent retracting when we're facing the wrong way - - if (!world.isClientSide) { - boolean flag = this.getNeighborSignal(world, pos, enumdirection); -@@ -231,7 +237,7 @@ public class PistonBaseBlock extends DirectionalBlock { - BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); - - world.setBlock(pos, iblockdata1, 20); -- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); -+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change - world.blockUpdated(pos, iblockdata1.getBlock()); - iblockdata1.updateNeighbourShapes(world, pos, 2); - if (this.isSticky) { -@@ -260,7 +266,14 @@ public class PistonBaseBlock extends DirectionalBlock { - } - } - } else { -- world.removeBlock(pos.relative(enumdirection), false); -+ // Paper start - fix headless pistons breaking blocks -+ BlockPos headPos = pos.relative(enumdirection); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || world.getBlockState(headPos) == Blocks.PISTON_HEAD.defaultBlockState().setValue(FACING, enumdirection)) { // double check to make sure we're not a headless piston. -+ world.removeBlock(headPos, false); -+ } else { -+ ((ServerLevel)world).getChunkSource().blockChanged(headPos); // ... fix client desync -+ } -+ // Paper end - fix headless pistons breaking blocks - } - - world.playSound((Player) null, pos, SoundEvents.PISTON_CONTRACT, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.15F + 0.6F); -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 3ec96c7f2ad0d6ba8ad32a4aabb0292b5c2ff5b4..ad38a7ced7f3dc05fb3d133e9da39f0a5eb0915b 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -223,7 +223,7 @@ public abstract class BlockBehaviour { - /** @deprecated */ - @Deprecated - public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { -- return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())); -+ return this.material.isReplaceable() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - } - - /** @deprecated */ -@@ -727,6 +727,12 @@ public abstract class BlockBehaviour { - return ((Block) this.owner).builtInRegistryHolder(); - } - -+ // Paper start -+ public final boolean isDestroyable() { -+ return getBlock().isDestroyable(); -+ } -+ // Paper end -+ - public Material getMaterial() { - return this.material; - } -@@ -824,7 +830,7 @@ public abstract class BlockBehaviour { - } - - public PushReaction getPistonPushReaction() { -- return this.getBlock().getPistonPushReaction(this.asState()); -+ return !isDestroyable() ? PushReaction.BLOCK : this.getBlock().getPistonPushReaction(this.asState()); // Paper - } - - public boolean isSolidRender(BlockGetter world, BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 7aa6181237eaec1df2ed2fdcd3b1137dfe89ce69..1311d69bb2fa7b3617936e6ad6eb5236fedc260d 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -210,6 +210,13 @@ public class PortalForcer { - for (int j = -1; j < 3; ++j) { - for (int k = -1; k < 4; ++k) { - temp.setWithOffset(pos, portalDirection.getStepX() * j + enumdirection1.getStepX() * distanceOrthogonalToPortal, k, portalDirection.getStepZ() * j + enumdirection1.getStepZ() * distanceOrthogonalToPortal); -+ // Paper start - prevent destroying unbreakable blocks -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits) { -+ if (!this.level.getBlockState(temp).isDestroyable()) { -+ return false; -+ } -+ } -+ // Paper end - prevent destroying unbreakable blocks - if (k < 0 && !this.level.getBlockState(temp).getMaterial().isSolid()) { - return false; - } diff --git a/patches/server/0423-Reduce-MutableInt-allocations-from-light-engine.patch b/patches/server/0423-Reduce-MutableInt-allocations-from-light-engine.patch new file mode 100644 index 0000000000..3adb42bf18 --- /dev/null +++ b/patches/server/0423-Reduce-MutableInt-allocations-from-light-engine.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +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/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +index 729c4b1763a24bac3c0764bea505555a32e54f57..37d7165dfd17da03428f8dbbbf95aa8005be289c 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java +@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt; + public final class BlockLightEngine extends LayerLightEngine { + private static final Direction[] DIRECTIONS = Direction.values(); + private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ private final MutableInt mutableInt = new MutableInt(); // Paper + + public BlockLightEngine(LightChunkGetter chunkProvider) { + super(chunkProvider, LightLayer.BLOCK, new BlockLightSectionStorage(chunkProvider)); +@@ -44,7 +45,7 @@ public final class BlockLightEngine extends LayerLightEngine= 15) { + return 15; +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +index 56b8f6ac53e7598da4dea2180825242222f86731..ca710a20e8b97341616463f4058b61cf4999af28 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java +@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt; + public final class SkyLightEngine extends LayerLightEngine { + private static final Direction[] DIRECTIONS = Direction.values(); + private static final Direction[] HORIZONTALS = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST}; ++ private final MutableInt mutableInt = new MutableInt(); // Paper + + public SkyLightEngine(LightChunkGetter chunkProvider) { + super(chunkProvider, LightLayer.SKY, new SkyLightSectionStorage(chunkProvider)); +@@ -26,7 +27,7 @@ public final class SkyLightEngine extends LayerLightEngine= 15) { + return level; + } else { +- MutableInt mutableInt = new MutableInt(); ++ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded + BlockState blockState = this.getStateAndOpacity(targetId, mutableInt); + if (mutableInt.getValue() >= 15) { + return 15; diff --git a/patches/server/0424-Reduce-MutableInt-allocations-from-light-engine.patch b/patches/server/0424-Reduce-MutableInt-allocations-from-light-engine.patch deleted file mode 100644 index 3adb42bf18..0000000000 --- a/patches/server/0424-Reduce-MutableInt-allocations-from-light-engine.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -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/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java -index 729c4b1763a24bac3c0764bea505555a32e54f57..37d7165dfd17da03428f8dbbbf95aa8005be289c 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java -+++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java -@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt; - public final class BlockLightEngine extends LayerLightEngine { - private static final Direction[] DIRECTIONS = Direction.values(); - private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ private final MutableInt mutableInt = new MutableInt(); // Paper - - public BlockLightEngine(LightChunkGetter chunkProvider) { - super(chunkProvider, LightLayer.BLOCK, new BlockLightSectionStorage(chunkProvider)); -@@ -44,7 +45,7 @@ public final class BlockLightEngine extends LayerLightEngine= 15) { - return 15; -diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java -index 56b8f6ac53e7598da4dea2180825242222f86731..ca710a20e8b97341616463f4058b61cf4999af28 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java -+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java -@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt; - public final class SkyLightEngine extends LayerLightEngine { - private static final Direction[] DIRECTIONS = Direction.values(); - private static final Direction[] HORIZONTALS = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST}; -+ private final MutableInt mutableInt = new MutableInt(); // Paper - - public SkyLightEngine(LightChunkGetter chunkProvider) { - super(chunkProvider, LightLayer.SKY, new SkyLightSectionStorage(chunkProvider)); -@@ -26,7 +27,7 @@ public final class SkyLightEngine extends LayerLightEngine= 15) { - return level; - } else { -- MutableInt mutableInt = new MutableInt(); -+ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded - BlockState blockState = this.getStateAndOpacity(targetId, mutableInt); - if (mutableInt.getValue() >= 15) { - return 15; diff --git a/patches/server/0424-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server/0424-Reduce-allocation-of-Vec3D-by-entity-tracker.patch new file mode 100644 index 0000000000..2b5c27897e --- /dev/null +++ b/patches/server/0424-Reduce-allocation-of-Vec3D-by-entity-tracker.patch @@ -0,0 +1,58 @@ +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/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +index 3167f5c6be39757e3cc42cbb17ab0cf13a2fe470..3768a71491ef7836b9739bdaec7a077c523dbacd 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -5,7 +5,7 @@ import net.minecraft.world.phys.Vec3; + + public class VecDeltaCodec { + private static final double TRUNCATION_STEPS = 4096.0D; +- private Vec3 base = Vec3.ZERO; ++ public Vec3 base = Vec3.ZERO; // Paper + + private static long encode(double value) { + return Mth.lfloor(value * 4096.0D); +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index ea224e68b680c5e9ab38998f7709a4dbe3471b86..82eceddc15dcdf592dc5bbe6f1249f537be0a91e 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -2040,9 +2040,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public void updatePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { +- Vec3 vec3d = player.position().subtract(this.entity.position()); ++ // Paper start - remove allocation of Vec3D here ++ // Vec3 vec3d = player.position().subtract(this.entity.position()); ++ double vec3d_dx = player.getX() - this.entity.getX(); ++ double vec3d_dz = player.getZ() - this.entity.getZ(); ++ // Paper end - remove allocation of Vec3D here + double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); +- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; ++ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper + double d2 = d0 * d0; + boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 7881176a900daa3306c691454f688c1f79b73475..ddc5b4849939a96b76611cfa1cd34c06c7acc0f8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -147,7 +147,13 @@ public class ServerEntity { + i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F); + j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F); + Vec3 vec3d = this.entity.trackingPosition(); +- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; ++ // Paper start - reduce allocation of Vec3D here ++ Vec3 base = this.positionCodec.base; ++ double vec3d_dx = vec3d.x - base.x; ++ double vec3d_dy = vec3d.y - base.y; ++ double vec3d_dz = vec3d.z - base.z; ++ 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.tickCount % 60 == 0; + boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; diff --git a/patches/server/0425-Ensure-safe-gateway-teleport.patch b/patches/server/0425-Ensure-safe-gateway-teleport.patch new file mode 100644 index 0000000000..6d352f74a8 --- /dev/null +++ b/patches/server/0425-Ensure-safe-gateway-teleport.patch @@ -0,0 +1,26 @@ +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/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +index befd35b0edf4a76b119f711f8536369a02abc1ba..56d68b87287f0bb2c79ce0bed02fa333b85c4287 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +@@ -105,7 +105,14 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { + List list = world.getEntitiesOfClass(Entity.class, new AABB(pos), TheEndGatewayBlockEntity::canEntityTeleport); + + if (!list.isEmpty()) { +- TheEndGatewayBlockEntity.teleportEntity(world, pos, state, (Entity) list.get(world.random.nextInt(list.size())), blockEntity); ++ // Paper start ++ for (Entity entity : list) { ++ if (entity.canChangeDimensions()) { ++ TheEndGatewayBlockEntity.teleportEntity(world, pos, state, entity, blockEntity); ++ break; ++ } ++ } ++ // Paper end + } + + if (blockEntity.age % 2400L == 0L) { diff --git a/patches/server/0425-Reduce-allocation-of-Vec3D-by-entity-tracker.patch b/patches/server/0425-Reduce-allocation-of-Vec3D-by-entity-tracker.patch deleted file mode 100644 index 2b5c27897e..0000000000 --- a/patches/server/0425-Reduce-allocation-of-Vec3D-by-entity-tracker.patch +++ /dev/null @@ -1,58 +0,0 @@ -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/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index 3167f5c6be39757e3cc42cbb17ab0cf13a2fe470..3768a71491ef7836b9739bdaec7a077c523dbacd 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -5,7 +5,7 @@ import net.minecraft.world.phys.Vec3; - - public class VecDeltaCodec { - private static final double TRUNCATION_STEPS = 4096.0D; -- private Vec3 base = Vec3.ZERO; -+ public Vec3 base = Vec3.ZERO; // Paper - - private static long encode(double value) { - return Mth.lfloor(value * 4096.0D); -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index ea224e68b680c5e9ab38998f7709a4dbe3471b86..82eceddc15dcdf592dc5bbe6f1249f537be0a91e 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -2040,9 +2040,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot - if (player != this.entity) { -- Vec3 vec3d = player.position().subtract(this.entity.position()); -+ // Paper start - remove allocation of Vec3D here -+ // Vec3 vec3d = player.position().subtract(this.entity.position()); -+ double vec3d_dx = player.getX() - this.entity.getX(); -+ double vec3d_dz = player.getZ() - this.entity.getZ(); -+ // Paper end - remove allocation of Vec3D here - double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); -- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; -+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; - boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 7881176a900daa3306c691454f688c1f79b73475..ddc5b4849939a96b76611cfa1cd34c06c7acc0f8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -147,7 +147,13 @@ public class ServerEntity { - i = Mth.floor(this.entity.getYRot() * 256.0F / 360.0F); - j = Mth.floor(this.entity.getXRot() * 256.0F / 360.0F); - Vec3 vec3d = this.entity.trackingPosition(); -- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; -+ // Paper start - reduce allocation of Vec3D here -+ Vec3 base = this.positionCodec.base; -+ double vec3d_dx = vec3d.x - base.x; -+ double vec3d_dy = vec3d.y - base.y; -+ double vec3d_dz = vec3d.z - base.z; -+ 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.tickCount % 60 == 0; - boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; diff --git a/patches/server/0426-Add-option-for-console-having-all-permissions.patch b/patches/server/0426-Add-option-for-console-having-all-permissions.patch new file mode 100644 index 0000000000..d1e3317d94 --- /dev/null +++ b/patches/server/0426-Add-option-for-console-having-all-permissions.patch @@ -0,0 +1,47 @@ +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/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +index 324e6d1a4fadd3e557e4ba05f04e6a5891cc54df..4e56018b64d11f76c8da43fd8f85c6de72204e36 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +@@ -93,5 +93,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(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); + } ++ ++ @Override ++ public boolean hasPermission(String name) { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || 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 a6612cc0ea87aeb8e87521ff7b5fe58c7b06b9ef..dfc15cfd897316f64a063b8ae93a0882ab5b3993 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java +@@ -45,4 +45,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 io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(name); ++ } ++ ++ @Override ++ public boolean hasPermission(org.bukkit.permissions.Permission perm) { ++ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(perm); ++ } ++ // Paper end + } diff --git a/patches/server/0426-Ensure-safe-gateway-teleport.patch b/patches/server/0426-Ensure-safe-gateway-teleport.patch deleted file mode 100644 index 6d352f74a8..0000000000 --- a/patches/server/0426-Ensure-safe-gateway-teleport.patch +++ /dev/null @@ -1,26 +0,0 @@ -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/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -index befd35b0edf4a76b119f711f8536369a02abc1ba..56d68b87287f0bb2c79ce0bed02fa333b85c4287 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -@@ -105,7 +105,14 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { - List list = world.getEntitiesOfClass(Entity.class, new AABB(pos), TheEndGatewayBlockEntity::canEntityTeleport); - - if (!list.isEmpty()) { -- TheEndGatewayBlockEntity.teleportEntity(world, pos, state, (Entity) list.get(world.random.nextInt(list.size())), blockEntity); -+ // Paper start -+ for (Entity entity : list) { -+ if (entity.canChangeDimensions()) { -+ TheEndGatewayBlockEntity.teleportEntity(world, pos, state, entity, blockEntity); -+ break; -+ } -+ } -+ // Paper end - } - - if (blockEntity.age % 2400L == 0L) { diff --git a/patches/server/0427-Add-option-for-console-having-all-permissions.patch b/patches/server/0427-Add-option-for-console-having-all-permissions.patch deleted file mode 100644 index d1e3317d94..0000000000 --- a/patches/server/0427-Add-option-for-console-having-all-permissions.patch +++ /dev/null @@ -1,47 +0,0 @@ -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/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -index 324e6d1a4fadd3e557e4ba05f04e6a5891cc54df..4e56018b64d11f76c8da43fd8f85c6de72204e36 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -@@ -93,5 +93,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(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); - } -+ -+ @Override -+ public boolean hasPermission(String name) { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(name); -+ } -+ -+ @Override -+ public boolean hasPermission(org.bukkit.permissions.Permission perm) { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || 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 a6612cc0ea87aeb8e87521ff7b5fe58c7b06b9ef..dfc15cfd897316f64a063b8ae93a0882ab5b3993 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftRemoteConsoleCommandSender.java -@@ -45,4 +45,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 io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(name); -+ } -+ -+ @Override -+ public boolean hasPermission(org.bukkit.permissions.Permission perm) { -+ return io.papermc.paper.configuration.GlobalConfiguration.get().console.hasAllPermissions || super.hasPermission(perm); -+ } -+ // Paper end - } diff --git a/patches/server/0427-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/server/0427-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch new file mode 100644 index 0000000000..643380032b --- /dev/null +++ b/patches/server/0427-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch @@ -0,0 +1,352 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize anyPlayerCloseEnoughForSpawning 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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index a52932d665ca45a5e066d7cef0ec0313d1c3f69f..0f75a109c06eb3113be74cf49ec560f5e2ea9cfc 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -79,14 +79,27 @@ public class ChunkHolder { + + // Paper start + public void onChunkAdd() { +- ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + + public void onChunkRemove() { +- ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ this.playersInMobSpawnRange = null; ++ this.playersInChunkTickRange = null; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + // Paper end + ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ // cached here to avoid a map lookup ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning ++ + public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 82eceddc15dcdf592dc5bbe6f1249f537be0a91e..43ad735f57ab513311d700b42d7d0f2f1e43d0e7 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -196,11 +196,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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<>(); ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ // 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 ChunkMap#anyPlayerCloseEnoughForSpawning + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); +@@ -210,6 +222,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + void removePlayerFromDistanceMaps(ServerPlayer player) { + ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.remove(player); +@@ -221,6 +237,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); +@@ -323,6 +340,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + // Paper end + this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + } + + protected ChunkGenerator generator() { +@@ -1524,43 +1573,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return this.anyPlayerCloseEnoughForSpawning(pos, false); + } + +- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; +- +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- double blockRange = 16384.0D; // Paper +- // Spigot end +- long i = chunkcoordintpair.toLong(); ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); ++ } + +- if (!this.distanceManager.hasPlayersNearby(i)) { ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos 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; ++ if (playersInRange == null) { + return false; +- } else { +- Iterator iterator = this.playerMap.getPlayers(i).iterator(); +- +- ServerPlayer entityplayer; ++ } ++ Object[] backingSet = playersInRange.getBackingSet(); + +- do { +- if (!iterator.hasNext()) { +- return false; ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer player)) { ++ continue; + } +- +- entityplayer = (ServerPlayer) iterator.next(); +- // Paper start - add PlayerNaturallySpawnCreaturesEvent +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) { ++ return true; // in range + } +- // Paper end +- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot +- +- return true; ++ } ++ } else { ++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.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 ServerPlayer player)) { ++ continue; ++ } ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) { ++ return true; // in range ++ } ++ } + } ++ // no players in range ++ return false; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + + public List getPlayersCloseForSpawning(ChunkPos pos) { +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index f456ba4bf699e1f6bd15726a037a0047b6ca7380..b2df5e18ce5260a9781052db7afb0b9786fb887c 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -49,7 +49,7 @@ public abstract class DistanceManager { + final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); +- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); ++ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final TickingTracker tickingTicketsTracker = new TickingTracker(); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); + // Paper start use a queue, but still keep unique requirement +@@ -141,7 +141,7 @@ public abstract class DistanceManager { + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + + public boolean runAllUpdates(ChunkMap chunkStorage) { +- this.naturalSpawnChunkCounter.runAllUpdates(); ++ //this.f.a(); // Paper - no longer used + this.tickingTicketsTracker.runAllUpdates(); + org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.playerTicketManager.runAllUpdates(); +@@ -429,7 +429,7 @@ public abstract class DistanceManager { + ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { + return new ObjectOpenHashSet(); + })).add(player); +- this.naturalSpawnChunkCounter.update(i, 0, true); ++ //this.f.update(i, 0, true); // Paper - no longer used + this.playerTicketManager.update(i, 0, true); + this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); + } +@@ -443,7 +443,7 @@ public abstract class DistanceManager { + if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); +- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); ++ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); + this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); + } +@@ -487,13 +487,17 @@ public abstract class DistanceManager { + // Paper end + + public int getNaturalSpawnChunkCount() { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.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 hasPlayersNearby(long chunkPos) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null; ++ // Paper end - use distance map to implement + } + + public String getDebugStatus() { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 497827822a64eeff2a4901f0e7c62f0f2c359b48..a59782d6f3640262c377a676e2b2ef5ec82563db 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -729,6 +729,37 @@ public class ServerChunkCache extends ChunkSource { + if (flag) { + this.chunkMap.tick(); + } else { ++ // Paper start - optimize isOutisdeRange ++ ChunkMap playerChunkMap = this.chunkMap; ++ for (ServerPlayer player : this.level.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int viewDistance = this.chunkMap.getEffectiveViewDistance(); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; ++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; ++ ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); ++ event.callEvent(); ++ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.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.getX()); ++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.getZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning ++ player.playerNaturallySpawnedEvent = event; ++ } ++ // Paper end - optimize isOutisdeRange + LevelData worlddata = this.level.getLevelData(); + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + +@@ -772,15 +803,7 @@ public class ServerChunkCache extends ChunkSource { + boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + + Collections.shuffle(list); +- // Paper start - call player naturally spawn event +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (ServerPlayer entityPlayer : this.level.players()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - moved natural spawn event up + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { +@@ -788,9 +811,9 @@ public class ServerChunkCache extends ChunkSource { + LevelChunk chunk1 = chunkproviderserver_a.chunk; + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { ++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning + chunk1.incrementInhabitedTime(j); +- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot ++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 971f72c1dd713077c128279a78ed37f15aedeff6..29a03760f092a004a47e75120841d80e696b6c3d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -263,6 +263,7 @@ public class ServerPlayer extends Player { + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + ++ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) { diff --git a/patches/server/0428-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/server/0428-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch deleted file mode 100644 index 643380032b..0000000000 --- a/patches/server/0428-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch +++ /dev/null @@ -1,352 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 5 May 2020 20:40:53 -0700 -Subject: [PATCH] Optimize anyPlayerCloseEnoughForSpawning 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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index a52932d665ca45a5e066d7cef0ec0313d1c3f69f..0f75a109c06eb3113be74cf49ec560f5e2ea9cfc 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -79,14 +79,27 @@ public class ChunkHolder { - - // Paper start - public void onChunkAdd() { -- -+ // Paper start - optimise anyPlayerCloseEnoughForSpawning -+ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); -+ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); -+ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); -+ // Paper end - optimise anyPlayerCloseEnoughForSpawning - } - - public void onChunkRemove() { -- -+ // Paper start - optimise anyPlayerCloseEnoughForSpawning -+ this.playersInMobSpawnRange = null; -+ this.playersInChunkTickRange = null; -+ // Paper end - optimise anyPlayerCloseEnoughForSpawning - } - // Paper end - -+ // Paper start - optimise anyPlayerCloseEnoughForSpawning -+ // cached here to avoid a map lookup -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; -+ // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ - public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { - this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); - this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 82eceddc15dcdf592dc5bbe6f1249f537be0a91e..43ad735f57ab513311d700b42d7d0f2f1e43d0e7 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -196,11 +196,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - 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<>(); -+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ // 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 ChunkMap#anyPlayerCloseEnoughForSpawning - - void addPlayerToDistanceMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -@@ -210,6 +222,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - void removePlayerFromDistanceMaps(ServerPlayer player) { - -+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerMobSpawnMap.remove(player); -+ this.playerChunkTickRangeMap.remove(player); -+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.remove(player); -@@ -221,6 +237,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -@@ -323,6 +340,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.regionManagers.add(this.dataRegionManager); - // Paper end - this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper -+ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); -+ if (playerChunk != null) { -+ playerChunk.playersInChunkTickRange = newState; -+ } -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); -+ if (playerChunk != null) { -+ playerChunk.playersInChunkTickRange = newState; -+ } -+ }); -+ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); -+ if (playerChunk != null) { -+ playerChunk.playersInMobSpawnRange = newState; -+ } -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); -+ if (playerChunk != null) { -+ playerChunk.playersInMobSpawnRange = newState; -+ } -+ }); -+ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - } - - protected ChunkGenerator generator() { -@@ -1524,43 +1573,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return this.anyPlayerCloseEnoughForSpawning(pos, false); - } - -- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { -- int chunkRange = level.spigotConfig.mobSpawnRange; -- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -- chunkRange = (chunkRange > 8) ? 8 : chunkRange; -- -- final int finalChunkRange = chunkRange; // Paper for lambda below -- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event -- double blockRange = 16384.0D; // Paper -- // Spigot end -- long i = chunkcoordintpair.toLong(); -+ // Paper start - optimise anyPlayerCloseEnoughForSpawning -+ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { -+ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); -+ } - -- if (!this.distanceManager.hasPlayersNearby(i)) { -+ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos 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; -+ if (playersInRange == null) { - return false; -- } else { -- Iterator iterator = this.playerMap.getPlayers(i).iterator(); -- -- ServerPlayer entityplayer; -+ } -+ Object[] backingSet = playersInRange.getBackingSet(); - -- do { -- if (!iterator.hasNext()) { -- return false; -+ if (reducedRange) { -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object raw = backingSet[i]; -+ if (!(raw instanceof ServerPlayer player)) { -+ continue; - } -- -- entityplayer = (ServerPlayer) iterator.next(); -- // Paper start - add PlayerNaturallySpawnCreaturesEvent -- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; -- blockRange = 16384.0D; -- if (reducedRange) { -- event = entityplayer.playerNaturallySpawnedEvent; -- if (event == null || event.isCancelled()) return false; -- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); -+ // don't check spectator and whatnot, already handled by mob spawn map update -+ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) { -+ return true; // in range - } -- // Paper end -- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot -- -- return true; -+ } -+ } else { -+ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.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 ServerPlayer player)) { -+ continue; -+ } -+ // don't check spectator and whatnot, already handled by mob spawn map update -+ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) { -+ return true; // in range -+ } -+ } - } -+ // no players in range -+ return false; -+ // Paper end - optimise anyPlayerCloseEnoughForSpawning - } - - public List getPlayersCloseForSpawning(ChunkPos pos) { -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index f456ba4bf699e1f6bd15726a037a0047b6ca7380..b2df5e18ce5260a9781052db7afb0b9786fb887c 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -49,7 +49,7 @@ public abstract class DistanceManager { - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); - public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); - private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); -- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); -+ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used - private final TickingTracker tickingTicketsTracker = new TickingTracker(); - private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); - // Paper start use a queue, but still keep unique requirement -@@ -141,7 +141,7 @@ public abstract class DistanceManager { - protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); - - public boolean runAllUpdates(ChunkMap chunkStorage) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -+ //this.f.a(); // Paper - no longer used - this.tickingTicketsTracker.runAllUpdates(); - org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper - this.playerTicketManager.runAllUpdates(); -@@ -429,7 +429,7 @@ public abstract class DistanceManager { - ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { - return new ObjectOpenHashSet(); - })).add(player); -- this.naturalSpawnChunkCounter.update(i, 0, true); -+ //this.f.update(i, 0, true); // Paper - no longer used - this.playerTicketManager.update(i, 0, true); - this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); - } -@@ -443,7 +443,7 @@ public abstract class DistanceManager { - if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); -- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); -+ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used - this.playerTicketManager.update(i, Integer.MAX_VALUE, false); - this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); - } -@@ -487,13 +487,17 @@ public abstract class DistanceManager { - // Paper end - - public int getNaturalSpawnChunkCount() { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.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 hasPlayersNearby(long chunkPos) { -- this.naturalSpawnChunkCounter.runAllUpdates(); -- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); -+ // Paper start - use distance map to implement -+ // note: this is the is spawn chunk method -+ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null; -+ // Paper end - use distance map to implement - } - - public String getDebugStatus() { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 497827822a64eeff2a4901f0e7c62f0f2c359b48..a59782d6f3640262c377a676e2b2ef5ec82563db 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -729,6 +729,37 @@ public class ServerChunkCache extends ChunkSource { - if (flag) { - this.chunkMap.tick(); - } else { -+ // Paper start - optimize isOutisdeRange -+ ChunkMap playerChunkMap = this.chunkMap; -+ for (ServerPlayer player : this.level.players) { -+ if (!player.affectsSpawning || player.isSpectator()) { -+ playerChunkMap.playerMobSpawnMap.remove(player); -+ continue; -+ } -+ -+ int viewDistance = this.chunkMap.getEffectiveViewDistance(); -+ -+ // copied and modified from isOutisdeRange -+ int chunkRange = level.spigotConfig.mobSpawnRange; -+ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; -+ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; -+ -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); -+ event.callEvent(); -+ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.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.getX()); -+ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.getZ()); -+ -+ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); -+ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning -+ player.playerNaturallySpawnedEvent = event; -+ } -+ // Paper end - optimize isOutisdeRange - LevelData worlddata = this.level.getLevelData(); - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - -@@ -772,15 +803,7 @@ public class ServerChunkCache extends ChunkSource { - boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - - Collections.shuffle(list); -- // Paper start - call player naturally spawn event -- int chunkRange = level.spigotConfig.mobSpawnRange; -- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; -- chunkRange = Math.min(chunkRange, 8); -- for (ServerPlayer entityPlayer : this.level.players()) { -- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); -- entityPlayer.playerNaturallySpawnedEvent.callEvent(); -- }; -- // Paper end -+ // Paper - moved natural spawn event up - Iterator iterator1 = list.iterator(); - - while (iterator1.hasNext()) { -@@ -788,9 +811,9 @@ public class ServerChunkCache extends ChunkSource { - LevelChunk chunk1 = chunkproviderserver_a.chunk; - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { -+ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning - chunk1.incrementInhabitedTime(j); -- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot -+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 971f72c1dd713077c128279a78ed37f15aedeff6..29a03760f092a004a47e75120841d80e696b6c3d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -263,6 +263,7 @@ public class ServerPlayer extends Player { - // CraftBukkit end - public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - -+ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) { diff --git a/patches/server/0428-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/0428-Use-distance-map-to-optimise-entity-tracker.patch new file mode 100644 index 0000000000..28b5efb452 --- /dev/null +++ b/patches/server/0428-Use-distance-map-to-optimise-entity-tracker.patch @@ -0,0 +1,395 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:18:05 -0700 +Subject: [PATCH] Use distance map to optimise entity tracker + +Use the distance map to find candidate players for tracking. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 43ad735f57ab513311d700b42d7d0f2f1e43d0e7..b0e0f85e04438affb8d8e0f75055ea83d0c03bcd 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -66,6 +66,7 @@ import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; + import net.minecraft.network.protocol.game.DebugPackets; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.ChunkProgressListener; + import net.minecraft.server.network.ServerPlayerConnection; + import net.minecraft.util.CsvOutput; +@@ -207,10 +208,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 ChunkMap#anyPlayerCloseEnoughForSpawning ++ // Paper start - use distance map to optimise tracker ++ public static boolean isLegacyTrackingEntity(Entity entity) { ++ return entity.isLegacyTrackingEntity; ++ } ++ ++ // inlined EnumMap, TrackingRange.TrackingRangeType ++ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; ++ final int[] entityTrackerTrackRanges; ++ public final int getEntityTrackerRange(final int ordinal) { ++ return this.entityTrackerTrackRanges[ordinal]; ++ } ++ ++ private int convertSpigotRangeToVanilla(final int vanilla) { ++ return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); ++ } ++ // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); ++ // 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 + // Note: players need to be explicitly added to distance maps before they can be updated + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning +@@ -222,6 +248,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + void removePlayerFromDistanceMaps(ServerPlayer 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 + // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); +@@ -237,6 +268,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // 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 + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { +@@ -340,6 +379,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + // Paper end + this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : 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.level.spigotConfig; ++ ++ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { ++ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; ++ int configuredSpigotValue; ++ switch (trackingRangeType) { ++ case PLAYER: ++ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; ++ break; ++ case ANIMAL: ++ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; ++ break; ++ case MONSTER: ++ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; ++ break; ++ case MISC: ++ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; ++ break; ++ case OTHER: ++ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; ++ break; ++ case ENDERDRAGON: ++ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; ++ break; ++ default: ++ throw new IllegalStateException("Missing case for enum " + trackingRangeType); ++ } ++ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); ++ ++ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); ++ this.entityTrackerTrackRanges[ordinal] = trackRange; ++ ++ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); ++ } ++ // Paper end - use distance map to optimise entity tracker + // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, +@@ -1696,17 +1774,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void move(ServerPlayer player) { +- ObjectIterator objectiterator = this.entityMap.values().iterator(); +- +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- +- if (playerchunkmap_entitytracker.entity == player) { +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); +- } else { +- playerchunkmap_entitytracker.updatePlayer(player); +- } +- } ++ // Paper - delay this logic for the entity tracker tick, no need to duplicate it + + int i = SectionPos.blockToSectionCoord(player.getBlockX()); + int j = SectionPos.blockToSectionCoord(player.getBlockZ()); +@@ -1833,7 +1901,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); ++ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -1877,7 +1945,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.tracker = null; // Paper - We're no longer tracked + } + ++ // Paper start - optimised tracker ++ private final void processTrackQueue() { ++ this.level.timings.tracker1.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ // update tracker entry ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ } ++ } finally { ++ this.level.timings.tracker1.stopTiming(); ++ } ++ ++ ++ this.level.timings.tracker2.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.sendChanges(); ++ } ++ } finally { ++ this.level.timings.tracker2.stopTiming(); ++ } ++ } ++ // Paper end - optimised tracker ++ + protected void tick() { ++ // Paper start - optimized tracker ++ if (true) { ++ this.processTrackQueue(); ++ return; ++ } ++ // Paper end - optimized tracker + List list = Lists.newArrayList(); + List list1 = this.level.players(); + ObjectIterator objectiterator = this.entityMap.values().iterator(); +@@ -1953,23 +2051,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); +- ObjectIterator objectiterator = this.entityMap.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 == player) { ++ continue; ++ } ++ ChunkMap.TrackedEntity tracker = this.entityMap.get(entity.getId()); ++ if (tracker != null) { // dumb plugins... move on... ++ tracker.updatePlayer(player); ++ } + +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- Entity entity = playerchunkmap_entitytracker.entity; ++ // 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 != player && entity.chunkPosition().equals(chunk.getPos())) { +- playerchunkmap_entitytracker.updatePlayer(player); +- if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { +- list.add(entity); +- } ++ if (entity instanceof Mob && ((Mob)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; +@@ -2045,6 +2151,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastSectionPos = SectionPos.of((EntityAccess) entity); + } + ++ // Paper start - use distance map to optimise tracker ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; ++ ++ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; ++ this.lastTrackerCandidates = newTrackerCandidates; ++ ++ if (newTrackerCandidates != null) { ++ Object[] rawData = newTrackerCandidates.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ Object raw = rawData[i]; ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)raw; ++ this.updatePlayer(player); ++ } ++ } ++ ++ if (oldTrackerCandidates == newTrackerCandidates) { ++ // this is likely the case. ++ // means there has been no range changes, so we can just use the above for tracking. ++ return; ++ } ++ ++ // stuff could have been removed, so we need to check the trackedPlayers set ++ // for players that were removed ++ ++ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME ++ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { ++ this.updatePlayer(conn.getPlayer()); ++ } ++ } ++ } ++ // Paper end - use distance map to optimise tracker ++ + public boolean equals(Object object) { + return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 8bdcf56a5a1c29f78e10f66de5adf5a3786ac092..afe081b095bb53c7a1a1d2145e60b8b5426d2ce0 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -57,6 +57,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -424,6 +425,39 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + // Paper 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() { ++ // determine highest range of passengers ++ if (this.passengers.isEmpty()) { ++ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] ++ .getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ Iterable passengers = this.getIndirectPassengers(); ++ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; ++ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; ++ int range = chunkMap.getEntityTrackerRange(type.ordinal()); ++ ++ for (Entity passenger : passengers) { ++ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; ++ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); ++ if (passengerRange > range) { ++ type = passengerType; ++ range = passengerRange; ++ } ++ } ++ ++ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ // Paper end - optimise entity tracking ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = ImmutableList.of(); +diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java +index 55ce69b5fe097841d00ef5c241459dce9bb0d4db..e5bcbfe175a697e04886d04543e1278b7e83a184 100644 +--- a/src/main/java/org/spigotmc/TrackingRange.java ++++ b/src/main/java/org/spigotmc/TrackingRange.java +@@ -24,6 +24,7 @@ public class TrackingRange + { + return defaultRange; + } ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return defaultRange; // Paper - enderdragon is exempt + SpigotWorldConfig config = entity.level.spigotConfig; + if ( entity instanceof ServerPlayer ) + { +@@ -47,8 +48,48 @@ public class TrackingRange + return config.miscTrackingRange; + } else + { +- if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getEffectiveViewDistance(); // 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.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt ++ if ( entity instanceof ServerPlayer ) ++ { ++ return TrackingRangeType.PLAYER; ++ // Paper start - Simplify and set water mobs to animal tracking range ++ } ++ switch (entity.activationType) { ++ case RAIDER: ++ case MONSTER: ++ case FLYING_MONSTER: ++ return TrackingRangeType.MONSTER; ++ case WATER: ++ case VILLAGER: ++ case ANIMAL: ++ return TrackingRangeType.ANIMAL; ++ case MISC: ++ } ++ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end ++ { ++ return TrackingRangeType.MISC; ++ } else ++ { ++ return TrackingRangeType.OTHER; ++ } ++ } ++ ++ public static enum TrackingRangeType { ++ PLAYER, ++ ANIMAL, ++ MONSTER, ++ MISC, ++ OTHER, ++ ENDERDRAGON; ++ } ++ // Paper end - optimise entity tracking + } diff --git a/patches/server/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch b/patches/server/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch new file mode 100644 index 0000000000..2bcf0781f9 --- /dev/null +++ b/patches/server/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 16 Apr 2020 16:13:59 -0700 +Subject: [PATCH] Optimize ServerLevels chunk level checking 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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a778b4b5b2413c25c2f0f0efc72ba1d362d89acf..c9ecc7593c299b351308634db44596a76fd0c09b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2255,19 +2255,22 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { +- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); ++ // Paper start - optimize is ticking ready type functions ++ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); ++ // Paper end + } + + public boolean isPositionEntityTicking(BlockPos pos) { +- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); ++ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); // Paper + } + + public boolean isNaturalSpawningAllowed(BlockPos pos) { +- return this.entityManager.canPositionTick(pos); ++ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)); // Paper + } + + public boolean isNaturalSpawningAllowed(ChunkPos pos) { +- return this.entityManager.canPositionTick(pos); ++ return this.entityManager.canPositionTick(pos.toLong()); // Paper + } + + private final class EntityCallbacks implements LevelCallback { +diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +index 2d41f619577b41d6420159668bbab70fb6c762eb..ed0b136e99def41d4377f2004477826b3546a145 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkPos.java ++++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +@@ -60,7 +60,7 @@ public class ChunkPos { + } + + public static long asLong(BlockPos pos) { +- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); ++ return (((long)pos.getX() >> 4) & 4294967295L) | ((((long)pos.getZ() >> 4) & 4294967295L) << 32); // Paper - inline + } + + public static int getX(long pos) { +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index 16519a6414f6f6418de40b714555a52631980617..a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -395,6 +395,11 @@ public class PersistentEntitySectionManager implements A + public LevelEntityGetter getEntityGetter() { + return this.entityGetter; + } ++ // Paper start ++ public final boolean canPositionTick(long position) { ++ return this.chunkVisibility.get(position).isTicking(); ++ } ++ // Paper end + + public boolean canPositionTick(BlockPos pos) { + return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking(); diff --git a/patches/server/0429-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/0429-Use-distance-map-to-optimise-entity-tracker.patch deleted file mode 100644 index 6b7c2e2afd..0000000000 --- a/patches/server/0429-Use-distance-map-to-optimise-entity-tracker.patch +++ /dev/null @@ -1,395 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 5 May 2020 20:18:05 -0700 -Subject: [PATCH] Use distance map to optimise entity tracker - -Use the distance map to find candidate players for tracking. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 43ad735f57ab513311d700b42d7d0f2f1e43d0e7..b0e0f85e04438affb8d8e0f75055ea83d0c03bcd 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -66,6 +66,7 @@ import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; - import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; - import net.minecraft.network.protocol.game.DebugPackets; - import net.minecraft.server.MCUtil; -+import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.progress.ChunkProgressListener; - import net.minecraft.server.network.ServerPlayerConnection; - import net.minecraft.util.CsvOutput; -@@ -207,10 +208,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - 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 ChunkMap#anyPlayerCloseEnoughForSpawning -+ // Paper start - use distance map to optimise tracker -+ public static boolean isLegacyTrackingEntity(Entity entity) { -+ return entity.isLegacyTrackingEntity; -+ } -+ -+ // inlined EnumMap, TrackingRange.TrackingRangeType -+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; -+ final int[] entityTrackerTrackRanges; -+ public final int getEntityTrackerRange(final int ordinal) { -+ return this.entityTrackerTrackRanges[ordinal]; -+ } -+ -+ private int convertSpigotRangeToVanilla(final int vanilla) { -+ return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); -+ } -+ // Paper end - use distance map to optimise tracker - - void addPlayerToDistanceMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); -+ // 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 - // Note: players need to be explicitly added to distance maps before they can be updated - this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning -@@ -222,6 +248,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - void removePlayerFromDistanceMaps(ServerPlayer 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 - // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - this.playerMobSpawnMap.remove(player); - this.playerChunkTickRangeMap.remove(player); -@@ -237,6 +268,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // 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 - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { -@@ -340,6 +379,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.regionManagers.add(this.dataRegionManager); - // Paper end - this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : 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.level.spigotConfig; -+ -+ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { -+ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; -+ int configuredSpigotValue; -+ switch (trackingRangeType) { -+ case PLAYER: -+ configuredSpigotValue = spigotWorldConfig.playerTrackingRange; -+ break; -+ case ANIMAL: -+ configuredSpigotValue = spigotWorldConfig.animalTrackingRange; -+ break; -+ case MONSTER: -+ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; -+ break; -+ case MISC: -+ configuredSpigotValue = spigotWorldConfig.miscTrackingRange; -+ break; -+ case OTHER: -+ configuredSpigotValue = spigotWorldConfig.otherTrackingRange; -+ break; -+ case ENDERDRAGON: -+ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; -+ break; -+ default: -+ throw new IllegalStateException("Missing case for enum " + trackingRangeType); -+ } -+ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); -+ -+ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); -+ this.entityTrackerTrackRanges[ordinal] = trackRange; -+ -+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -+ } -+ // Paper end - use distance map to optimise entity tracker - // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, - (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -@@ -1696,17 +1774,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void move(ServerPlayer player) { -- ObjectIterator objectiterator = this.entityMap.values().iterator(); -- -- while (objectiterator.hasNext()) { -- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); -- -- if (playerchunkmap_entitytracker.entity == player) { -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -- } else { -- playerchunkmap_entitytracker.updatePlayer(player); -- } -- } -+ // Paper - delay this logic for the entity tracker tick, no need to duplicate it - - int i = SectionPos.blockToSectionCoord(player.getBlockX()); - int j = SectionPos.blockToSectionCoord(player.getBlockZ()); -@@ -1833,7 +1901,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker - this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); -- playerchunkmap_entitytracker.updatePlayers(this.level.players()); -+ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players - if (entity instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entity; - -@@ -1877,7 +1945,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - entity.tracker = null; // Paper - We're no longer tracked - } - -+ // Paper start - optimised tracker -+ private final void processTrackQueue() { -+ this.level.timings.tracker1.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ // update tracker entry -+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); -+ } -+ } finally { -+ this.level.timings.tracker1.stopTiming(); -+ } -+ -+ -+ this.level.timings.tracker2.startTiming(); -+ try { -+ for (TrackedEntity tracker : this.entityMap.values()) { -+ tracker.serverEntity.sendChanges(); -+ } -+ } finally { -+ this.level.timings.tracker2.stopTiming(); -+ } -+ } -+ // Paper end - optimised tracker -+ - protected void tick() { -+ // Paper start - optimized tracker -+ if (true) { -+ this.processTrackQueue(); -+ return; -+ } -+ // Paper end - optimized tracker - List list = Lists.newArrayList(); - List list1 = this.level.players(); - ObjectIterator objectiterator = this.entityMap.values().iterator(); -@@ -1953,23 +2051,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); - List list = Lists.newArrayList(); - List list1 = Lists.newArrayList(); -- ObjectIterator objectiterator = this.entityMap.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 == player) { -+ continue; -+ } -+ ChunkMap.TrackedEntity tracker = this.entityMap.get(entity.getId()); -+ if (tracker != null) { // dumb plugins... move on... -+ tracker.updatePlayer(player); -+ } - -- while (objectiterator.hasNext()) { -- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); -- Entity entity = playerchunkmap_entitytracker.entity; -+ // 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 != player && entity.chunkPosition().equals(chunk.getPos())) { -- playerchunkmap_entitytracker.updatePlayer(player); -- if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { -- list.add(entity); -- } -+ if (entity instanceof Mob && ((Mob)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; -@@ -2045,6 +2151,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.lastSectionPos = SectionPos.of((EntityAccess) entity); - } - -+ // Paper start - use distance map to optimise tracker -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; -+ -+ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; -+ this.lastTrackerCandidates = newTrackerCandidates; -+ -+ if (newTrackerCandidates != null) { -+ Object[] rawData = newTrackerCandidates.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ Object raw = rawData[i]; -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)raw; -+ this.updatePlayer(player); -+ } -+ } -+ -+ if (oldTrackerCandidates == newTrackerCandidates) { -+ // this is likely the case. -+ // means there has been no range changes, so we can just use the above for tracking. -+ return; -+ } -+ -+ // stuff could have been removed, so we need to check the trackedPlayers set -+ // for players that were removed -+ -+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME -+ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { -+ this.updatePlayer(conn.getPlayer()); -+ } -+ } -+ } -+ // Paper end - use distance map to optimise tracker -+ - public boolean equals(Object object) { - return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 74e13ae8b1e6a9365d46f5684ee58e42658d2341..0a0fb24cd12d77e0c29eb6e0a20eacfc04330bf3 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -57,6 +57,7 @@ import net.minecraft.network.syncher.EntityDataSerializers; - import net.minecraft.network.syncher.SynchedEntityData; - import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MCUtil; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -@@ -424,6 +425,39 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - // Paper 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() { -+ // determine highest range of passengers -+ if (this.passengers.isEmpty()) { -+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] -+ .getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ Iterable passengers = this.getIndirectPassengers(); -+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; -+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; -+ int range = chunkMap.getEntityTrackerRange(type.ordinal()); -+ -+ for (Entity passenger : passengers) { -+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; -+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); -+ if (passengerRange > range) { -+ type = passengerType; -+ range = passengerRange; -+ } -+ } -+ -+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ // Paper end - optimise entity tracking -+ - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); -diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java -index 55ce69b5fe097841d00ef5c241459dce9bb0d4db..e5bcbfe175a697e04886d04543e1278b7e83a184 100644 ---- a/src/main/java/org/spigotmc/TrackingRange.java -+++ b/src/main/java/org/spigotmc/TrackingRange.java -@@ -24,6 +24,7 @@ public class TrackingRange - { - return defaultRange; - } -+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return defaultRange; // Paper - enderdragon is exempt - SpigotWorldConfig config = entity.level.spigotConfig; - if ( entity instanceof ServerPlayer ) - { -@@ -47,8 +48,48 @@ public class TrackingRange - return config.miscTrackingRange; - } else - { -- if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getEffectiveViewDistance(); // 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.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt -+ if ( entity instanceof ServerPlayer ) -+ { -+ return TrackingRangeType.PLAYER; -+ // Paper start - Simplify and set water mobs to animal tracking range -+ } -+ switch (entity.activationType) { -+ case RAIDER: -+ case MONSTER: -+ case FLYING_MONSTER: -+ return TrackingRangeType.MONSTER; -+ case WATER: -+ case VILLAGER: -+ case ANIMAL: -+ return TrackingRangeType.ANIMAL; -+ case MISC: -+ } -+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) -+ // Paper end -+ { -+ return TrackingRangeType.MISC; -+ } else -+ { -+ return TrackingRangeType.OTHER; -+ } -+ } -+ -+ public static enum TrackingRangeType { -+ PLAYER, -+ ANIMAL, -+ MONSTER, -+ MISC, -+ OTHER, -+ ENDERDRAGON; -+ } -+ // Paper end - optimise entity tracking - } diff --git a/patches/server/0430-Fix-villager-trading-demand-MC-163962.patch b/patches/server/0430-Fix-villager-trading-demand-MC-163962.patch new file mode 100644 index 0000000000..fb5e330854 --- /dev/null +++ b/patches/server/0430-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/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index 987e65332058f364fcef0a29c97b206b14ad6457..8b46e494ecd0cce5ab0b2bf8e50cf50dc7e2a7e5 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -113,7 +113,7 @@ public class MerchantOffer { + } + + public void updateDemand() { +- 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 assemble() { diff --git a/patches/server/0430-Optimize-ServerLevels-chunk-level-checking-methods.patch b/patches/server/0430-Optimize-ServerLevels-chunk-level-checking-methods.patch deleted file mode 100644 index 2bcf0781f9..0000000000 --- a/patches/server/0430-Optimize-ServerLevels-chunk-level-checking-methods.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 16 Apr 2020 16:13:59 -0700 -Subject: [PATCH] Optimize ServerLevels chunk level checking 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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a778b4b5b2413c25c2f0f0efc72ba1d362d89acf..c9ecc7593c299b351308634db44596a76fd0c09b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2255,19 +2255,22 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { -- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); -+ // Paper start - optimize is ticking ready type functions -+ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); -+ // Paper end - } - - public boolean isPositionEntityTicking(BlockPos pos) { -- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); -+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); // Paper - } - - public boolean isNaturalSpawningAllowed(BlockPos pos) { -- return this.entityManager.canPositionTick(pos); -+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)); // Paper - } - - public boolean isNaturalSpawningAllowed(ChunkPos pos) { -- return this.entityManager.canPositionTick(pos); -+ return this.entityManager.canPositionTick(pos.toLong()); // Paper - } - - private final class EntityCallbacks implements LevelCallback { -diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java -index 2d41f619577b41d6420159668bbab70fb6c762eb..ed0b136e99def41d4377f2004477826b3546a145 100644 ---- a/src/main/java/net/minecraft/world/level/ChunkPos.java -+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java -@@ -60,7 +60,7 @@ public class ChunkPos { - } - - public static long asLong(BlockPos pos) { -- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); -+ return (((long)pos.getX() >> 4) & 4294967295L) | ((((long)pos.getZ() >> 4) & 4294967295L) << 32); // Paper - inline - } - - public static int getX(long pos) { -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 16519a6414f6f6418de40b714555a52631980617..a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -395,6 +395,11 @@ public class PersistentEntitySectionManager implements A - public LevelEntityGetter getEntityGetter() { - return this.entityGetter; - } -+ // Paper start -+ public final boolean canPositionTick(long position) { -+ return this.chunkVisibility.get(position).isTicking(); -+ } -+ // Paper end - - public boolean canPositionTick(BlockPos pos) { - return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking(); diff --git a/patches/server/0431-Fix-villager-trading-demand-MC-163962.patch b/patches/server/0431-Fix-villager-trading-demand-MC-163962.patch deleted file mode 100644 index fb5e330854..0000000000 --- a/patches/server/0431-Fix-villager-trading-demand-MC-163962.patch +++ /dev/null @@ -1,20 +0,0 @@ -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/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -index 987e65332058f364fcef0a29c97b206b14ad6457..8b46e494ecd0cce5ab0b2bf8e50cf50dc7e2a7e5 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -113,7 +113,7 @@ public class MerchantOffer { - } - - public void updateDemand() { -- 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 assemble() { diff --git a/patches/server/0431-Maps-shouldn-t-load-chunks.patch b/patches/server/0431-Maps-shouldn-t-load-chunks.patch new file mode 100644 index 0000000000..3ab19f357a --- /dev/null +++ b/patches/server/0431-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/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index 339dfd9fdffbc1c49b9112ba43ef6f05d85bdf86..44bfc5560a1b43925aabfd8137495e169da32fa2 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -131,9 +131,9 @@ public class MapItem extends ComplexItem { + int k2 = (j / i + k1 - 64) * i; + int l2 = (k / i + l1 - 64) * i; + Multiset multiset = LinkedHashMultiset.create(); +- LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2)); ++ LevelChunk chunk = world.getChunkIfLoaded(new BlockPos(k2, 0, l2)); // Paper - Maps shouldn't load chunks + +- if (!chunk.isEmpty()) { ++ if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks + ChunkPos chunkcoordintpair = chunk.getPos(); + int i3 = k2 & 15; + int j3 = l2 & 15; diff --git a/patches/server/0432-Maps-shouldn-t-load-chunks.patch b/patches/server/0432-Maps-shouldn-t-load-chunks.patch deleted file mode 100644 index 3ab19f357a..0000000000 --- a/patches/server/0432-Maps-shouldn-t-load-chunks.patch +++ /dev/null @@ -1,32 +0,0 @@ -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/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 339dfd9fdffbc1c49b9112ba43ef6f05d85bdf86..44bfc5560a1b43925aabfd8137495e169da32fa2 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -131,9 +131,9 @@ public class MapItem extends ComplexItem { - int k2 = (j / i + k1 - 64) * i; - int l2 = (k / i + l1 - 64) * i; - Multiset multiset = LinkedHashMultiset.create(); -- LevelChunk chunk = world.getChunkAt(new BlockPos(k2, 0, l2)); -+ LevelChunk chunk = world.getChunkIfLoaded(new BlockPos(k2, 0, l2)); // Paper - Maps shouldn't load chunks - -- if (!chunk.isEmpty()) { -+ if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks - ChunkPos chunkcoordintpair = chunk.getPos(); - int i3 = k2 & 15; - int j3 = l2 & 15; diff --git a/patches/server/0432-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server/0432-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch new file mode 100644 index 0000000000..9ed5e37d35 --- /dev/null +++ b/patches/server/0432-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch @@ -0,0 +1,27 @@ +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/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index 44bfc5560a1b43925aabfd8137495e169da32fa2..d407cf849a31a7a77fda07aa687ebb254f43d6ab 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -252,14 +252,13 @@ public class MapItem extends ComplexItem { + boolean[] aboolean = new boolean[16384]; + int l = j / i - 64; + int i1 = k / i - 64; +- BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + + int j1; + int k1; + + for (j1 = 0; j1 < 128; ++j1) { + for (k1 = 0; k1 < 128; ++k1) { +- Holder holder = world.getBiome(blockposition_mutableblockposition.set((l + k1) * i, 0, (i1 + j1) * i)); ++ Holder holder = world.getUncachedNoiseBiome((l + k1) * i, 0, (i1 + j1) * i); // Paper + + aboolean[j1 * 128 + k1] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); + } diff --git a/patches/server/0433-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server/0433-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch new file mode 100644 index 0000000000..1cafad18f3 --- /dev/null +++ b/patches/server/0433-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 ea7ebbc2674df727cf44856f172731ee083b8800..a423970cf7c927ea8a1bf842aaa236d3cf2d54c2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -199,7 +199,7 @@ public class CraftScheduler implements BukkitScheduler { + + @Override + public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { +- this.runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); ++ this.runTaskTimerAsynchronously(plugin, (Object) task, delay, period); + } + + @Override diff --git a/patches/server/0433-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch b/patches/server/0433-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch deleted file mode 100644 index 9ed5e37d35..0000000000 --- a/patches/server/0433-Use-seed-based-lookup-for-Treasure-Maps-Fixes-lag-fr.patch +++ /dev/null @@ -1,27 +0,0 @@ -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/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 44bfc5560a1b43925aabfd8137495e169da32fa2..d407cf849a31a7a77fda07aa687ebb254f43d6ab 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -252,14 +252,13 @@ public class MapItem extends ComplexItem { - boolean[] aboolean = new boolean[16384]; - int l = j / i - 64; - int i1 = k / i - 64; -- BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); - - int j1; - int k1; - - for (j1 = 0; j1 < 128; ++j1) { - for (k1 = 0; k1 < 128; ++k1) { -- Holder holder = world.getBiome(blockposition_mutableblockposition.set((l + k1) * i, 0, (i1 + j1) * i)); -+ Holder holder = world.getUncachedNoiseBiome((l + k1) * i, 0, (i1 + j1) * i); // Paper - - aboolean[j1 * 128 + k1] = holder.is(BiomeTags.WATER_ON_MAP_OUTLINES); - } diff --git a/patches/server/0434-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch b/patches/server/0434-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch deleted file mode 100644 index 1cafad18f3..0000000000 --- a/patches/server/0434-Fix-CraftScheduler-runTaskTimerAsynchronously-Plugin.patch +++ /dev/null @@ -1,21 +0,0 @@ -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 ea7ebbc2674df727cf44856f172731ee083b8800..a423970cf7c927ea8a1bf842aaa236d3cf2d54c2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -199,7 +199,7 @@ public class CraftScheduler implements BukkitScheduler { - - @Override - public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { -- this.runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING); -+ this.runTaskTimerAsynchronously(plugin, (Object) task, delay, period); - } - - @Override diff --git a/patches/server/0434-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server/0434-Fix-piston-physics-inconsistency-MC-188840.patch new file mode 100644 index 0000000000..dcc303244a --- /dev/null +++ b/patches/server/0434-Fix-piston-physics-inconsistency-MC-188840.patch @@ -0,0 +1,80 @@ +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/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 29755807fdb6c30e31c0ec2bbf33bed9afd5d478..8d73893100884c08aa552ff41c2a07a3e714df47 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -411,14 +411,26 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + for (k = list.size() - 1; k >= 0; --k) { +- blockposition3 = (BlockPos) list.get(k); +- iblockdata1 = world.getBlockState(blockposition3); ++ // Paper start - fix a variety of piston desync dupes ++ boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; ++ BlockPos oldPos = blockposition3 = (BlockPos) list.get(k); ++ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; ++ // Paper end - fix a variety of piston desync dupes + blockposition3 = blockposition3.relative(enumdirection1); + map.remove(blockposition3); + BlockState iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir); + + world.setBlock(blockposition3, iblockdata2, 68); +- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(k), dir, retract, false)); ++ // Paper start - fix a variety of piston desync dupes ++ if (!allowDesync) { ++ iblockdata1 = world.getBlockState(oldPos); ++ map.replace(oldPos, iblockdata1); ++ } ++ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? list1.get(k) : iblockdata1, dir, retract, false)); ++ if (!allowDesync) { ++ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), 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/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +index dda0b32a4989bbead35a2219a969a30ba0e975b0..7c59d44a3bafdc65f453d77ff3e25cffb742ad6c 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +@@ -285,7 +285,7 @@ public class PistonMovingBlockEntity extends BlockEntity { + if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) { + BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos); + if (blockState.isAir()) { +- world.setBlock(pos, blockEntity.movedState, 84); ++ world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.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.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3); + } else { + if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/patches/server/0435-Fix-piston-physics-inconsistency-MC-188840.patch b/patches/server/0435-Fix-piston-physics-inconsistency-MC-188840.patch deleted file mode 100644 index dcc303244a..0000000000 --- a/patches/server/0435-Fix-piston-physics-inconsistency-MC-188840.patch +++ /dev/null @@ -1,80 +0,0 @@ -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/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 29755807fdb6c30e31c0ec2bbf33bed9afd5d478..8d73893100884c08aa552ff41c2a07a3e714df47 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -411,14 +411,26 @@ public class PistonBaseBlock extends DirectionalBlock { - } - - for (k = list.size() - 1; k >= 0; --k) { -- blockposition3 = (BlockPos) list.get(k); -- iblockdata1 = world.getBlockState(blockposition3); -+ // Paper start - fix a variety of piston desync dupes -+ boolean allowDesync = io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication; -+ BlockPos oldPos = blockposition3 = (BlockPos) list.get(k); -+ iblockdata1 = allowDesync ? world.getBlockState(oldPos) : null; -+ // Paper end - fix a variety of piston desync dupes - blockposition3 = blockposition3.relative(enumdirection1); - map.remove(blockposition3); - BlockState iblockdata2 = (BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(PistonBaseBlock.FACING, dir); - - world.setBlock(blockposition3, iblockdata2, 68); -- world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, (BlockState) list1.get(k), dir, retract, false)); -+ // Paper start - fix a variety of piston desync dupes -+ if (!allowDesync) { -+ iblockdata1 = world.getBlockState(oldPos); -+ map.replace(oldPos, iblockdata1); -+ } -+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockposition3, iblockdata2, allowDesync ? list1.get(k) : iblockdata1, dir, retract, false)); -+ if (!allowDesync) { -+ world.setBlock(oldPos, Blocks.AIR.defaultBlockState(), 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/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -index dda0b32a4989bbead35a2219a969a30ba0e975b0..7c59d44a3bafdc65f453d77ff3e25cffb742ad6c 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -@@ -285,7 +285,7 @@ public class PistonMovingBlockEntity extends BlockEntity { - if (world.getBlockState(pos).is(Blocks.MOVING_PISTON)) { - BlockState blockState = Block.updateFromNeighbourShapes(blockEntity.movedState, world, pos); - if (blockState.isAir()) { -- world.setBlock(pos, blockEntity.movedState, 84); -+ world.setBlock(pos, blockEntity.movedState, io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.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.updateOrDestroy(blockEntity.movedState, blockState, world, pos, 3); - } else { - if (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED)) { diff --git a/patches/server/0435-Fix-sand-duping.patch b/patches/server/0435-Fix-sand-duping.patch new file mode 100644 index 0000000000..5780d77de8 --- /dev/null +++ b/patches/server/0435-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/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index 18d81e8e8f387a7fb531652cb78c61a9bd5ae600..f6405e862b15b71dbb96215e604610fe5ff59bfc 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -126,6 +126,11 @@ public class FallingBlockEntity extends Entity { + + @Override + public void tick() { ++ // Paper start - fix sand duping ++ if (this.isRemoved()) { ++ return; ++ } ++ // Paper end - fix sand duping + if (this.blockState.isAir()) { + this.discard(); + } else { +@@ -138,6 +143,12 @@ public class FallingBlockEntity extends Entity { + + this.move(MoverType.SELF, this.getDeltaMovement()); + ++ // Paper start - fix sand duping ++ if (this.isRemoved()) { ++ return; ++ } ++ // Paper end - fix sand duping ++ + // Paper start - Configurable EntityFallingBlock height nerf + if (this.level.paperConfig().fixes.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.fallingBlockHeightNerf) { + if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { diff --git a/patches/server/0436-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server/0436-Fix-missing-chunks-due-to-integer-overflow.patch new file mode 100644 index 0000000000..94b8d07c3a --- /dev/null +++ b/patches/server/0436-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 EndIslandDensityFunction 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. + +This issue is being tracked in Mojira ticket MC-159283 + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java +index 30b6c5839b0da1aea792b7e092f0fcf5e83835db..683474cd96d3a0cdfb3b22d0111c8d3231f01d92 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -501,7 +501,7 @@ public final class DensityFunctions { + int j = z / 2; + int k = x % 2; + int l = z % 2; +- float f = 100.0F - Mth.sqrt((float)(x * x + z * z)) * 8.0F; ++ float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow + f = Mth.clamp(f, -100.0F, 80.0F); + + for(int m = -12; m <= 12; ++m) { diff --git a/patches/server/0436-Fix-sand-duping.patch b/patches/server/0436-Fix-sand-duping.patch deleted file mode 100644 index 5780d77de8..0000000000 --- a/patches/server/0436-Fix-sand-duping.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 Jun 2020 13:33:19 -0700 -Subject: [PATCH] Fix sand duping - -If the falling block dies during teleportation (entity#move), then we need -to detect that by placing a check after the move. - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 18d81e8e8f387a7fb531652cb78c61a9bd5ae600..f6405e862b15b71dbb96215e604610fe5ff59bfc 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -126,6 +126,11 @@ public class FallingBlockEntity extends Entity { - - @Override - public void tick() { -+ // Paper start - fix sand duping -+ if (this.isRemoved()) { -+ return; -+ } -+ // Paper end - fix sand duping - if (this.blockState.isAir()) { - this.discard(); - } else { -@@ -138,6 +143,12 @@ public class FallingBlockEntity extends Entity { - - this.move(MoverType.SELF, this.getDeltaMovement()); - -+ // Paper start - fix sand duping -+ if (this.isRemoved()) { -+ return; -+ } -+ // Paper end - fix sand duping -+ - // Paper start - Configurable EntityFallingBlock height nerf - if (this.level.paperConfig().fixes.fallingBlockHeightNerf != 0 && this.getY() > this.level.paperConfig().fixes.fallingBlockHeightNerf) { - if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { diff --git a/patches/server/0437-Fix-missing-chunks-due-to-integer-overflow.patch b/patches/server/0437-Fix-missing-chunks-due-to-integer-overflow.patch deleted file mode 100644 index 94b8d07c3a..0000000000 --- a/patches/server/0437-Fix-missing-chunks-due-to-integer-overflow.patch +++ /dev/null @@ -1,29 +0,0 @@ -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 EndIslandDensityFunction 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. - -This issue is being tracked in Mojira ticket MC-159283 - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -index 30b6c5839b0da1aea792b7e092f0fcf5e83835db..683474cd96d3a0cdfb3b22d0111c8d3231f01d92 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -@@ -501,7 +501,7 @@ public final class DensityFunctions { - int j = z / 2; - int k = x % 2; - int l = z % 2; -- float f = 100.0F - Mth.sqrt((float)(x * x + z * z)) * 8.0F; -+ float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow - f = Mth.clamp(f, -100.0F, 80.0F); - - for(int m = -12; m <= 12; ++m) { diff --git a/patches/server/0437-Prevent-position-desync-in-playerconnection-causing-.patch b/patches/server/0437-Prevent-position-desync-in-playerconnection-causing-.patch new file mode 100644 index 0000000000..ba844e85b3 --- /dev/null +++ b/patches/server/0437-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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index bbed54d5a0e3c363614d694950688f8edc02841d..30a69fa8f8266909fe98bea7ea781789c74bf50b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1446,6 +1446,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move ++ // Paper start - prevent position desync ++ if (this.awaitingPositionFromClient != null) { ++ return; // ... thanks Mojang for letting move calls teleport across dimensions. ++ } ++ // Paper end - prevent position desync + double d12 = d8; + + d7 = d0 - this.player.getX(); diff --git a/patches/server/0438-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server/0438-Inventory-getHolder-method-without-block-snapshot.patch new file mode 100644 index 0000000000..9ed9fe0826 --- /dev/null +++ b/patches/server/0438-Inventory-getHolder-method-without-block-snapshot.patch @@ -0,0 +1,42 @@ +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 3796f8b122ad981b6faacd2afcaf3696314aad6b..b17dab9e5c06d8789553b104602d7da35d926dd1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -532,6 +532,13 @@ public class CraftInventory implements Inventory { + return this.inventory.getOwner(); + } + ++ // Paper start - getHolder without snapshot ++ @Override ++ public InventoryHolder getHolder(boolean useSnapshot) { ++ return inventory instanceof net.minecraft.world.level.block.entity.BlockEntity ? ((net.minecraft.world.level.block.entity.BlockEntity) inventory).getOwner(useSnapshot) : getHolder(); ++ } ++ // Paper end ++ + @Override + public int getMaxStackSize() { + return this.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 01d425d359f2d6d87b6c01b435a9cfcfe11caa20..4707a651dc80086efa852bcfba38a534e7f1f3d0 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 this.getLeftSide().getLocation().add(this.getRightSide().getLocation()).multiply(0.5); diff --git a/patches/server/0438-Prevent-position-desync-in-playerconnection-causing-.patch b/patches/server/0438-Prevent-position-desync-in-playerconnection-causing-.patch deleted file mode 100644 index 8abc2018d3..0000000000 --- a/patches/server/0438-Prevent-position-desync-in-playerconnection-causing-.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 Jun 2020 16:51:39 -0700 -Subject: [PATCH] Prevent position desync 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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 245c1ff9fe02a6f8fe1f320e2a751c7579425b1f..0885f265cadb1ebc1f6bdfcd2a39502b0617f185 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1446,6 +1446,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); - this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move -+ // Paper start - prevent position desync -+ if (this.awaitingPositionFromClient != null) { -+ return; // ... thanks Mojang for letting move calls teleport across dimensions. -+ } -+ // Paper end - prevent position desync - double d12 = d8; - - d7 = d0 - this.player.getX(); diff --git a/patches/server/0439-Improve-Arrow-API.patch b/patches/server/0439-Improve-Arrow-API.patch new file mode 100644 index 0000000000..47bb897a5a --- /dev/null +++ b/patches/server/0439-Improve-Arrow-API.patch @@ -0,0 +1,38 @@ +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] Improve Arrow API + +Add method to get the arrow's itemstack and a method +to set the arrow's "noclip" status + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +index 376885c8148da619a3b203145d315ebaf44994fb..15abd085eeb0a31a925c1a8d6de903c9d4625a29 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java +@@ -102,6 +102,23 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { + this.getHandle().pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.byOrdinal(status.ordinal()); + } + ++ // Paper start ++ @Override ++ public org.bukkit.craftbukkit.inventory.CraftItemStack getItemStack() { ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getPickupItem()); ++ } ++ ++ @Override ++ public void setNoPhysics(boolean noPhysics) { ++ this.getHandle().setNoPhysics(noPhysics); ++ } ++ ++ @Override ++ public boolean hasNoPhysics() { ++ return this.getHandle().isNoPhysics(); ++ } ++ // Paper end ++ + @Override + public void setTicksLived(int value) { + super.setTicksLived(value); diff --git a/patches/server/0439-Inventory-getHolder-method-without-block-snapshot.patch b/patches/server/0439-Inventory-getHolder-method-without-block-snapshot.patch deleted file mode 100644 index 9ed9fe0826..0000000000 --- a/patches/server/0439-Inventory-getHolder-method-without-block-snapshot.patch +++ /dev/null @@ -1,42 +0,0 @@ -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 3796f8b122ad981b6faacd2afcaf3696314aad6b..b17dab9e5c06d8789553b104602d7da35d926dd1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -532,6 +532,13 @@ public class CraftInventory implements Inventory { - return this.inventory.getOwner(); - } - -+ // Paper start - getHolder without snapshot -+ @Override -+ public InventoryHolder getHolder(boolean useSnapshot) { -+ return inventory instanceof net.minecraft.world.level.block.entity.BlockEntity ? ((net.minecraft.world.level.block.entity.BlockEntity) inventory).getOwner(useSnapshot) : getHolder(); -+ } -+ // Paper end -+ - @Override - public int getMaxStackSize() { - return this.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 01d425d359f2d6d87b6c01b435a9cfcfe11caa20..4707a651dc80086efa852bcfba38a534e7f1f3d0 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 this.getLeftSide().getLocation().add(this.getRightSide().getLocation()).multiply(0.5); diff --git a/patches/server/0440-Add-and-implement-PlayerRecipeBookClickEvent.patch b/patches/server/0440-Add-and-implement-PlayerRecipeBookClickEvent.patch new file mode 100644 index 0000000000..c067c44ca1 --- /dev/null +++ b/patches/server/0440-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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 30a69fa8f8266909fe98bea7ea781789c74bf50b..b7865696d9b939791b0315ab2a231e2dc5872de8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3228,9 +3228,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { +- this.server.getRecipeManager().byKey(packet.getRecipe()).ifPresent((irecipe) -> { +- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(packet.isShiftDown(), 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(packet.getRecipe()), packet.isShiftDown()); ++ if (event.callEvent() && this.player.containerMenu instanceof RecipeBookMenu recipeBookMenu) { // check if inventory changed during event handling ++ this.server.getRecipeManager().byKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getRecipe())).ifPresent((irecipe) -> { ++ recipeBookMenu.handlePlacement(event.isMakeAll(), irecipe, this.player); ++ }); ++ } ++ // Paper end + } + } + } diff --git a/patches/server/0440-Improve-Arrow-API.patch b/patches/server/0440-Improve-Arrow-API.patch deleted file mode 100644 index 47bb897a5a..0000000000 --- a/patches/server/0440-Improve-Arrow-API.patch +++ /dev/null @@ -1,38 +0,0 @@ -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] Improve Arrow API - -Add method to get the arrow's itemstack and a method -to set the arrow's "noclip" status - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -index 376885c8148da619a3b203145d315ebaf44994fb..15abd085eeb0a31a925c1a8d6de903c9d4625a29 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java -@@ -102,6 +102,23 @@ public class CraftArrow extends AbstractProjectile implements AbstractArrow { - this.getHandle().pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.byOrdinal(status.ordinal()); - } - -+ // Paper start -+ @Override -+ public org.bukkit.craftbukkit.inventory.CraftItemStack getItemStack() { -+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(getHandle().getPickupItem()); -+ } -+ -+ @Override -+ public void setNoPhysics(boolean noPhysics) { -+ this.getHandle().setNoPhysics(noPhysics); -+ } -+ -+ @Override -+ public boolean hasNoPhysics() { -+ return this.getHandle().isNoPhysics(); -+ } -+ // Paper end -+ - @Override - public void setTicksLived(int value) { - super.setTicksLived(value); diff --git a/patches/server/0441-Add-and-implement-PlayerRecipeBookClickEvent.patch b/patches/server/0441-Add-and-implement-PlayerRecipeBookClickEvent.patch deleted file mode 100644 index da6eb3b6c2..0000000000 --- a/patches/server/0441-Add-and-implement-PlayerRecipeBookClickEvent.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Fri, 5 Jun 2020 18:24:06 -0400 -Subject: [PATCH] Add and implement PlayerRecipeBookClickEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index fd2358003a400ec5229721c22160d9c80ad67858..b00c66e68cb457ddedcde2ed7b0be791ffd78718 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3224,9 +3224,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - if (!this.player.containerMenu.stillValid(this.player)) { - ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); - } else { -- this.server.getRecipeManager().byKey(packet.getRecipe()).ifPresent((irecipe) -> { -- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(packet.isShiftDown(), 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(packet.getRecipe()), packet.isShiftDown()); -+ if (event.callEvent() && this.player.containerMenu instanceof RecipeBookMenu recipeBookMenu) { // check if inventory changed during event handling -+ this.server.getRecipeManager().byKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getRecipe())).ifPresent((irecipe) -> { -+ recipeBookMenu.handlePlacement(event.isMakeAll(), irecipe, this.player); -+ }); -+ } -+ // Paper end - } - } - } diff --git a/patches/server/0441-Hide-sync-chunk-writes-behind-flag.patch b/patches/server/0441-Hide-sync-chunk-writes-behind-flag.patch new file mode 100644 index 0000000000..1d9e0f2b56 --- /dev/null +++ b/patches/server/0441-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 1e01277448a3cf2b2045b54b182166a66c822e20..a32cfa75a9bea896f558bab646d0868391b069a9 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -136,7 +136,7 @@ public class DedicatedServerProperties extends Settings { + return Mth.clamp(integer, (int) 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true); ++ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); + this.hideOnlinePlayers = this.get("hide-online-players", false); diff --git a/patches/server/0442-Add-permission-for-command-blocks.patch b/patches/server/0442-Add-permission-for-command-blocks.patch new file mode 100644 index 0000000000..a0bcd3db18 --- /dev/null +++ b/patches/server/0442-Add-permission-for-command-blocks.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 16 May 2020 10:05:30 +0200 +Subject: [PATCH] Add permission for command blocks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index af00442931f9f6cf878bd61137c2f29fc7c8d0b1..431ff490760f54be76847c7b370dbbb4b65de102 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -396,7 +396,7 @@ public class ServerPlayerGameMode { + BlockEntity tileentity = this.level.getBlockEntity(pos); + Block block = iblockdata.getBlock(); + +- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { ++ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + return false; + } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b7865696d9b939791b0315ab2a231e2dc5872de8..02b6cf65f6abedfd4933e4e64d254f190e061301 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -878,7 +878,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = null; +@@ -945,7 +945,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); +- } else if (!this.player.canUseGameMasterBlocks()) { ++ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { + BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level); +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index da504702bc9423774b35dff792d2dbe7fc270fe3..c0195f73cd2c8721e882c681eaead65471710081 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -198,7 +198,7 @@ public abstract class BaseCommandBlock implements CommandSource { + } + + public InteractionResult usedBy(Player player) { +- if (!player.canUseGameMasterBlocks()) { ++ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission + return InteractionResult.PASS; + } else { + if (player.getCommandSenderWorld().isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +index 061a56e3828767cd6576d5a9edde5f3498e609d0..2e7c03b00bc941b86df6a7f1b2b188c9f0aede22 100644 +--- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java +@@ -130,7 +130,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + BlockEntity tileentity = world.getBlockEntity(pos); + +- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { ++ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission + player.openCommandBlock((CommandBlockEntity) tileentity); + return InteractionResult.sidedSuccess(world.isClientSide); + } else { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +index 70d3949616c63038ad3e9bd1069db5ea2fb3f3b8..8e06bc11fb28baee3407bbfe9d7b3689d6f85ff2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java +@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); + DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility ++ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper + // Spigot end + parent.recalculatePermissibles(); + } diff --git a/patches/server/0442-Hide-sync-chunk-writes-behind-flag.patch b/patches/server/0442-Hide-sync-chunk-writes-behind-flag.patch deleted file mode 100644 index 1d9e0f2b56..0000000000 --- a/patches/server/0442-Hide-sync-chunk-writes-behind-flag.patch +++ /dev/null @@ -1,23 +0,0 @@ -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 1e01277448a3cf2b2045b54b182166a66c822e20..a32cfa75a9bea896f558bab646d0868391b069a9 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -136,7 +136,7 @@ public class DedicatedServerProperties extends Settings { - return Mth.clamp(integer, (int) 1, 29999984); - }, 29999984); -- this.syncChunkWrites = this.get("sync-chunk-writes", true); -+ this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag - this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); - this.enableStatus = this.get("enable-status", true); - this.hideOnlinePlayers = this.get("hide-online-players", false); diff --git a/patches/server/0443-Add-permission-for-command-blocks.patch b/patches/server/0443-Add-permission-for-command-blocks.patch deleted file mode 100644 index 4acfd8ed30..0000000000 --- a/patches/server/0443-Add-permission-for-command-blocks.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 16 May 2020 10:05:30 +0200 -Subject: [PATCH] Add permission for command blocks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index af00442931f9f6cf878bd61137c2f29fc7c8d0b1..431ff490760f54be76847c7b370dbbb4b65de102 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -396,7 +396,7 @@ public class ServerPlayerGameMode { - BlockEntity tileentity = this.level.getBlockEntity(pos); - Block block = iblockdata.getBlock(); - -- if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { -+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); - return false; - } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2da61f9cc0f930538348bc185063c3d7dfeeb3b2..02635061fe59156a1c94ad3bf05d2fc534a8cf29 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -878,7 +878,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = null; -@@ -945,7 +945,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (!this.server.isCommandBlockEnabled()) { - this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); -- } else if (!this.player.canUseGameMasterBlocks()) { -+ } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); - } else { - BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level); -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index da504702bc9423774b35dff792d2dbe7fc270fe3..c0195f73cd2c8721e882c681eaead65471710081 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -198,7 +198,7 @@ public abstract class BaseCommandBlock implements CommandSource { - } - - public InteractionResult usedBy(Player player) { -- if (!player.canUseGameMasterBlocks()) { -+ if (!player.canUseGameMasterBlocks() && (!player.isCreative() || !player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission - return InteractionResult.PASS; - } else { - if (player.getCommandSenderWorld().isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/CommandBlock.java b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -index 061a56e3828767cd6576d5a9edde5f3498e609d0..2e7c03b00bc941b86df6a7f1b2b188c9f0aede22 100644 ---- a/src/main/java/net/minecraft/world/level/block/CommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CommandBlock.java -@@ -130,7 +130,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - BlockEntity tileentity = world.getBlockEntity(pos); - -- if (tileentity instanceof CommandBlockEntity && player.canUseGameMasterBlocks()) { -+ if (tileentity instanceof CommandBlockEntity && (player.canUseGameMasterBlocks() || (player.isCreative() && player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - player.openCommandBlock((CommandBlockEntity) tileentity); - return InteractionResult.sidedSuccess(world.isClientSide); - } else { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -index 70d3949616c63038ad3e9bd1069db5ea2fb3f3b8..8e06bc11fb28baee3407bbfe9d7b3689d6f85ff2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java -@@ -16,6 +16,7 @@ public final class CraftDefaultPermissions { - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent); - DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility -+ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".commandblock", "Gives the user the ability to use command blocks.", org.bukkit.permissions.PermissionDefault.OP, parent); // Paper - // Spigot end - parent.recalculatePermissibles(); - } diff --git a/patches/server/0443-Ensure-Entity-AABB-s-are-never-invalid.patch b/patches/server/0443-Ensure-Entity-AABB-s-are-never-invalid.patch new file mode 100644 index 0000000000..8dc151efda --- /dev/null +++ b/patches/server/0443-Ensure-Entity-AABB-s-are-never-invalid.patch @@ -0,0 +1,46 @@ +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 afe081b095bb53c7a1a1d2145e60b8b5426d2ce0..3ebe5297aa2fde1cb347f738738f5d74f6a61b9a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -662,8 +662,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void setPos(double x, double y, double z) { +- this.setPosRaw(x, y, z); +- this.setBoundingBox(this.makeBoundingBox()); ++ this.setPosRaw(x, y, z, true); // Paper - force bounding box update ++ // this.setBoundingBox(this.makeBoundingBox()); // Paper - move into setPositionRaw + } + + protected AABB makeBoundingBox() { +@@ -3884,6 +3884,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public final void setPosRaw(double x, double y, double z) { ++ // Paper start ++ this.setPosRaw(x, y, z, false); ++ } ++ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ // Paper end + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); +@@ -3901,6 +3906,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.levelCallback.onMove(); + } + ++ // Paper start - never allow AABB to become desynced from position ++ // hanging has its own special logic ++ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { ++ this.setBoundingBox(this.makeBoundingBox()); ++ } ++ // Paper end + } + + public void checkDespawn() {} diff --git a/patches/server/0444-Ensure-Entity-AABB-s-are-never-invalid.patch b/patches/server/0444-Ensure-Entity-AABB-s-are-never-invalid.patch deleted file mode 100644 index 756a326a3a..0000000000 --- a/patches/server/0444-Ensure-Entity-AABB-s-are-never-invalid.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 10 May 2020 22:12:46 -0400 -Subject: [PATCH] Ensure Entity 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 0a0fb24cd12d77e0c29eb6e0a20eacfc04330bf3..7e36e53d44b5efbd6498caecb717bec1dcbec96d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -662,8 +662,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public void setPos(double x, double y, double z) { -- this.setPosRaw(x, y, z); -- this.setBoundingBox(this.makeBoundingBox()); -+ this.setPosRaw(x, y, z, true); // Paper - force bounding box update -+ // this.setBoundingBox(this.makeBoundingBox()); // Paper - move into setPositionRaw - } - - protected AABB makeBoundingBox() { -@@ -3870,6 +3870,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public final void setPosRaw(double x, double y, double z) { -+ // Paper start -+ this.setPosRaw(x, y, z, false); -+ } -+ public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { -+ // Paper end - if (this.position.x != x || this.position.y != y || this.position.z != z) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); -@@ -3887,6 +3892,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.levelCallback.onMove(); - } - -+ // Paper start - never allow AABB to become desynced from position -+ // hanging has its own special logic -+ if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (forceBoundingBoxUpdate || this.position.x != x || this.position.y != y || this.position.z != z)) { -+ this.setBoundingBox(this.makeBoundingBox()); -+ } -+ // Paper end - } - - public void checkDespawn() {} diff --git a/patches/server/0444-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0444-Fix-Per-World-Difficulty-Remembering-Difficulty.patch new file mode 100644 index 0000000000..f734c3b466 --- /dev/null +++ b/patches/server/0444-Fix-Per-World-Difficulty-Remembering-Difficulty.patch @@ -0,0 +1,118 @@ +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 9c2589c7e0517f771b9ca06760273a0aecefb27d..7d2fee97f4d08eae245475c4b60c1a7ba46c840d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -791,7 +791,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -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 9c2589c7e0517f771b9ca06760273a0aecefb27d..7d2fee97f4d08eae245475c4b60c1a7ba46c840d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -791,7 +791,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 25f6d1c15cdfb4abdf1edd2ad9bbdc0e37b546f3..d2536f4ffae721f4df714b5345fa3329c3b8e3f5 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -1,6 +1,7 @@ + package io.papermc.paper.command; + + import io.papermc.paper.command.subcommands.ChunkDebugCommand; ++import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; +@@ -46,6 +47,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); ++ commands.put(Set.of("dumpitem"), new DumpItemCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..771503ff637fea10d4d8be0f37f3f146c41791d9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java +@@ -0,0 +1,59 @@ ++package io.papermc.paper.command.subcommands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.Objects; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.minecraft.core.Registry; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GRAY; ++import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; ++import static net.kyori.adventure.text.format.TextDecoration.ITALIC; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpItemCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.doDumpItem(sender); ++ return true; ++ } ++ ++ private void doDumpItem(final CommandSender sender) { ++ if (!(sender instanceof Player)) { ++ sender.sendMessage("Only players can use this command"); ++ return; ++ } ++ final ItemStack itemStack = CraftItemStack.asNMSCopy(((CraftPlayer) sender).getItemInHand()); ++ final @Nullable CompoundTag tag = itemStack.getTag(); ++ final @Nullable Component nbtComponent = tag == null ? null : PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); ++ final String itemId = Objects.requireNonNull(((CraftWorld) ((CraftPlayer) sender).getWorld()).getHandle().registryAccess() ++ .registryOrThrow(Registry.ITEM_REGISTRY).getKey(itemStack.getItem())).toString(); ++ final Component message = text() ++ .append(text(itemId, YELLOW)) ++ .apply(b -> { ++ if (nbtComponent != null) { ++ b.append(nbtComponent); ++ } ++ }) ++ .build(); ++ Bukkit.getConsoleSender().sendMessage(message); ++ sender.sendMessage(message); ++ sender.sendMessage(text().content(" Click to copy item to clipboard") ++ .color(GRAY) ++ .decorate(ITALIC) ++ .clickEvent(ClickEvent.copyToClipboard(tag == null ? itemId : (itemId + tag)))); ++ } ++} diff --git a/patches/server/0446-Don-t-allow-null-UUID-s-for-chat.patch b/patches/server/0446-Don-t-allow-null-UUID-s-for-chat.patch new file mode 100644 index 0000000000..9c92814826 --- /dev/null +++ b/patches/server/0446-Don-t-allow-null-UUID-s-for-chat.patch @@ -0,0 +1,23 @@ +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/chat/ChatSender.java b/src/main/java/net/minecraft/network/chat/ChatSender.java +index eb33c74d51f9b096ac39adf167fa09afdaa5e56b..d3d5487e9a4e95271a88c094058ec9d37a10d370 100644 +--- a/src/main/java/net/minecraft/network/chat/ChatSender.java ++++ b/src/main/java/net/minecraft/network/chat/ChatSender.java +@@ -8,6 +8,12 @@ import net.minecraft.world.entity.player.ProfilePublicKey; + public record ChatSender(UUID profileId, @Nullable ProfilePublicKey profilePublicKey) { + public static final ChatSender SYSTEM = new ChatSender(Util.NIL_UUID, (ProfilePublicKey)null); + ++ // Paper start ++ public ChatSender { ++ com.google.common.base.Preconditions.checkNotNull(profileId, "uuid cannot be null"); ++ } ++ // Paper end ++ + public boolean isSystem() { + return SYSTEM.equals(this); + } diff --git a/patches/server/0446-Paper-dumpitem-command.patch b/patches/server/0446-Paper-dumpitem-command.patch deleted file mode 100644 index 930a212a00..0000000000 --- a/patches/server/0446-Paper-dumpitem-command.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -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/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index 25f6d1c15cdfb4abdf1edd2ad9bbdc0e37b546f3..d2536f4ffae721f4df714b5345fa3329c3b8e3f5 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -1,6 +1,7 @@ - package io.papermc.paper.command; - - import io.papermc.paper.command.subcommands.ChunkDebugCommand; -+import io.papermc.paper.command.subcommands.DumpItemCommand; - import io.papermc.paper.command.subcommands.EntityCommand; - import io.papermc.paper.command.subcommands.FixLightCommand; - import io.papermc.paper.command.subcommands.HeapDumpCommand; -@@ -46,6 +47,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); - commands.put(Set.of("fixlight"), new FixLightCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); -+ commands.put(Set.of("dumpitem"), new DumpItemCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..771503ff637fea10d4d8be0f37f3f146c41791d9 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/DumpItemCommand.java -@@ -0,0 +1,59 @@ -+package io.papermc.paper.command.subcommands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.Objects; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.minecraft.core.Registry; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.item.ItemStack; -+import org.bukkit.Bukkit; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GRAY; -+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW; -+import static net.kyori.adventure.text.format.TextDecoration.ITALIC; -+ -+@DefaultQualifier(NonNull.class) -+public final class DumpItemCommand implements PaperSubcommand { -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ this.doDumpItem(sender); -+ return true; -+ } -+ -+ private void doDumpItem(final CommandSender sender) { -+ if (!(sender instanceof Player)) { -+ sender.sendMessage("Only players can use this command"); -+ return; -+ } -+ final ItemStack itemStack = CraftItemStack.asNMSCopy(((CraftPlayer) sender).getItemInHand()); -+ final @Nullable CompoundTag tag = itemStack.getTag(); -+ final @Nullable Component nbtComponent = tag == null ? null : PaperAdventure.asAdventure(net.minecraft.nbt.NbtUtils.toPrettyComponent(tag)); -+ final String itemId = Objects.requireNonNull(((CraftWorld) ((CraftPlayer) sender).getWorld()).getHandle().registryAccess() -+ .registryOrThrow(Registry.ITEM_REGISTRY).getKey(itemStack.getItem())).toString(); -+ final Component message = text() -+ .append(text(itemId, YELLOW)) -+ .apply(b -> { -+ if (nbtComponent != null) { -+ b.append(nbtComponent); -+ } -+ }) -+ .build(); -+ Bukkit.getConsoleSender().sendMessage(message); -+ sender.sendMessage(message); -+ sender.sendMessage(text().content(" Click to copy item to clipboard") -+ .color(GRAY) -+ .decorate(ITALIC) -+ .clickEvent(ClickEvent.copyToClipboard(tag == null ? itemId : (itemId + tag)))); -+ } -+} diff --git a/patches/server/0447-Don-t-allow-null-UUID-s-for-chat.patch b/patches/server/0447-Don-t-allow-null-UUID-s-for-chat.patch deleted file mode 100644 index 9c92814826..0000000000 --- a/patches/server/0447-Don-t-allow-null-UUID-s-for-chat.patch +++ /dev/null @@ -1,23 +0,0 @@ -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/chat/ChatSender.java b/src/main/java/net/minecraft/network/chat/ChatSender.java -index eb33c74d51f9b096ac39adf167fa09afdaa5e56b..d3d5487e9a4e95271a88c094058ec9d37a10d370 100644 ---- a/src/main/java/net/minecraft/network/chat/ChatSender.java -+++ b/src/main/java/net/minecraft/network/chat/ChatSender.java -@@ -8,6 +8,12 @@ import net.minecraft.world.entity.player.ProfilePublicKey; - public record ChatSender(UUID profileId, @Nullable ProfilePublicKey profilePublicKey) { - public static final ChatSender SYSTEM = new ChatSender(Util.NIL_UUID, (ProfilePublicKey)null); - -+ // Paper start -+ public ChatSender { -+ com.google.common.base.Preconditions.checkNotNull(profileId, "uuid cannot be null"); -+ } -+ // Paper end -+ - public boolean isSystem() { - return SYSTEM.equals(this); - } diff --git a/patches/server/0447-Improve-Legacy-Component-serialization-size.patch b/patches/server/0447-Improve-Legacy-Component-serialization-size.patch new file mode 100644 index 0000000000..866302739f --- /dev/null +++ b/patches/server/0447-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 4fede2161792ba3e7cdf0cc5a1f533188becc6f7..0f70be614f8f5350ad558d0ae645cdf0027e1e76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +@@ -47,6 +47,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 Style EMPTY = Style.EMPTY.withItalic(false); // Paper - OBFHELPER + private static final Style RESET = Style.EMPTY.withBold(false).withItalic(false).withUnderlined(false).withStrikethrough(false).withObfuscated(false); + + private final List list = new ArrayList(); +@@ -68,6 +69,7 @@ public final class CraftChatMessage { + Matcher matcher = (keepNewlines ? StringMessage.INCREMENTAL_PATTERN_KEEP_NEWLINES : StringMessage.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) { +@@ -113,7 +115,26 @@ public final class CraftChatMessage { + throw new AssertionError("Unexpected message format"); + } + } else { // Color resets formatting +- this.modifier = StringMessage.RESET.withColor(format); ++ // Paper start - improve legacy formatting ++ Style previous = modifier; ++ modifier = (!hasReset ? RESET : EMPTY).withColor(format); ++ hasReset = true; ++ if (previous.isBold()) { ++ modifier = modifier.withBold(false); ++ } ++ if (previous.isItalic()) { ++ modifier = modifier.withItalic(false); ++ } ++ if (previous.isObfuscated()) { ++ modifier = modifier.withObfuscated(false); ++ } ++ if (previous.isStrikethrough()) { ++ modifier = modifier.withStrikethrough(false); ++ } ++ if (previous.isUnderlined()) { ++ modifier = modifier.withUnderlined(false); ++ } ++ // Paper end + } + needsAdd = true; + break; diff --git a/patches/server/0448-Improve-Legacy-Component-serialization-size.patch b/patches/server/0448-Improve-Legacy-Component-serialization-size.patch deleted file mode 100644 index 866302739f..0000000000 --- a/patches/server/0448-Improve-Legacy-Component-serialization-size.patch +++ /dev/null @@ -1,56 +0,0 @@ -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 4fede2161792ba3e7cdf0cc5a1f533188becc6f7..0f70be614f8f5350ad558d0ae645cdf0027e1e76 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java -@@ -47,6 +47,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 Style EMPTY = Style.EMPTY.withItalic(false); // Paper - OBFHELPER - private static final Style RESET = Style.EMPTY.withBold(false).withItalic(false).withUnderlined(false).withStrikethrough(false).withObfuscated(false); - - private final List list = new ArrayList(); -@@ -68,6 +69,7 @@ public final class CraftChatMessage { - Matcher matcher = (keepNewlines ? StringMessage.INCREMENTAL_PATTERN_KEEP_NEWLINES : StringMessage.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) { -@@ -113,7 +115,26 @@ public final class CraftChatMessage { - throw new AssertionError("Unexpected message format"); - } - } else { // Color resets formatting -- this.modifier = StringMessage.RESET.withColor(format); -+ // Paper start - improve legacy formatting -+ Style previous = modifier; -+ modifier = (!hasReset ? RESET : EMPTY).withColor(format); -+ hasReset = true; -+ if (previous.isBold()) { -+ modifier = modifier.withBold(false); -+ } -+ if (previous.isItalic()) { -+ modifier = modifier.withItalic(false); -+ } -+ if (previous.isObfuscated()) { -+ modifier = modifier.withObfuscated(false); -+ } -+ if (previous.isStrikethrough()) { -+ modifier = modifier.withStrikethrough(false); -+ } -+ if (previous.isUnderlined()) { -+ modifier = modifier.withUnderlined(false); -+ } -+ // Paper end - } - needsAdd = true; - break; diff --git a/patches/server/0448-Optimize-Bit-Operations-by-inlining.patch b/patches/server/0448-Optimize-Bit-Operations-by-inlining.patch new file mode 100644 index 0000000000..c1cce4278a --- /dev/null +++ b/patches/server/0448-Optimize-Bit-Operations-by-inlining.patch @@ -0,0 +1,213 @@ +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/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index f8361dcf9d0378497ec5a34ea53b4e0019700851..e68cd32a7db88f29d3224b0908119232ab3cf71a 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -30,15 +30,16 @@ public class BlockPos extends Vec3i { + }).stable(); + private static final Logger LOGGER = LogUtils.getLogger(); + public static final BlockPos ZERO = new BlockPos(0, 0, 0); +- private static final int PACKED_X_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000)); +- private static final int PACKED_Z_LENGTH = PACKED_X_LENGTH; +- public static final int PACKED_Y_LENGTH = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH; +- private static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L; +- private static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L; +- private static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L; +- private static final int Y_OFFSET = 0; +- private static final int Z_OFFSET = PACKED_Y_LENGTH; +- private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH; ++ // Paper start - static constants ++ private static final int PACKED_X_LENGTH = 26; ++ private static final int PACKED_Z_LENGTH = 26; ++ public static final int PACKED_Y_LENGTH = 12; ++ private static final long PACKED_X_MASK = 67108863; ++ private static final long PACKED_Y_MASK = 4095; ++ private static final long PACKED_Z_MASK = 67108863; ++ private static final int Z_OFFSET = 12; ++ private static final int X_OFFSET = 38; ++ // Paper end + + public BlockPos(int x, int y, int z) { + super(x, y, z); +@@ -60,28 +61,29 @@ public class BlockPos extends Vec3i { + this(pos.getX(), pos.getY(), pos.getZ()); + } + ++ public static long getAdjacent(int baseX, int baseY, int baseZ, Direction enumdirection) { return asLong(baseX + enumdirection.getStepX(), baseY + enumdirection.getStepY(), baseZ + enumdirection.getStepZ()); } // Paper + public static long offset(long value, Direction direction) { + return offset(value, direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + + public static long offset(long value, int x, int y, int z) { +- return asLong(getX(value) + x, getY(value) + y, getZ(value) + z); ++ return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z); // Paper - simplify/inline + } + + public static int getX(long packedPos) { +- return (int)(packedPos << 64 - X_OFFSET - PACKED_X_LENGTH >> 64 - PACKED_X_LENGTH); ++ return (int) (packedPos >> 38); // Paper - simplify/inline + } + + public static int getY(long packedPos) { +- return (int)(packedPos << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH); ++ return (int) ((packedPos << 52) >> 52); // Paper - simplify/inline + } + + public static int getZ(long packedPos) { +- return (int)(packedPos << 64 - Z_OFFSET - PACKED_Z_LENGTH >> 64 - PACKED_Z_LENGTH); ++ return (int) ((packedPos << 26) >> 38); // Paper - simplify/inline + } + + public static BlockPos of(long packedPos) { +- return new BlockPos(getX(packedPos), getY(packedPos), getZ(packedPos)); ++ return new BlockPos((int) (packedPos >> 38), (int) ((packedPos << 52) >> 52), (int) ((packedPos << 26) >> 38)); // Paper - simplify/inline + } + + public long asLong() { +@@ -89,10 +91,7 @@ public class BlockPos extends Vec3i { + } + + public static long asLong(int x, int y, int z) { +- long l = 0L; +- l |= ((long)x & PACKED_X_MASK) << X_OFFSET; +- l |= ((long)y & PACKED_Y_MASK) << 0; +- return l | ((long)z & PACKED_Z_MASK) << Z_OFFSET; ++ return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); // Paper - inline constants and simplify + } + + public static long getFlatIndex(long y) { +diff --git a/src/main/java/net/minecraft/core/SectionPos.java b/src/main/java/net/minecraft/core/SectionPos.java +index a481bd6328fe9e66f6911bf32ed11947c504c93c..a9d0d72aad7b1b708617a082c5efcd881a0f00d3 100644 +--- a/src/main/java/net/minecraft/core/SectionPos.java ++++ b/src/main/java/net/minecraft/core/SectionPos.java +@@ -38,7 +38,7 @@ public class SectionPos extends Vec3i { + } + + public static SectionPos of(BlockPos pos) { +- return new SectionPos(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ())); ++ return new SectionPos(pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4); // Paper + } + + public static SectionPos of(ChunkPos chunkPos, int y) { +@@ -54,7 +54,7 @@ public class SectionPos extends Vec3i { + } + + public static SectionPos of(long packed) { +- return new SectionPos(x(packed), y(packed), z(packed)); ++ return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); // Paper + } + + public static SectionPos bottomOf(ChunkAccess chunk) { +@@ -65,8 +65,16 @@ public class SectionPos extends Vec3i { + return offset(packed, direction.getStepX(), direction.getStepY(), direction.getStepZ()); + } + ++ // Paper start ++ public static long getAdjacentFromBlockPos(int x, int y, int z, Direction enumdirection) { ++ return (((long) ((x >> 4) + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getStepY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getStepZ()) & 4194303L) << 20); ++ } ++ public static long getAdjacentFromSectionPos(int x, int y, int z, Direction enumdirection) { ++ return (((long) (x + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getStepY()) & 1048575L)) | (((long) (z + enumdirection.getStepZ()) & 4194303L) << 20); ++ } ++ // Paper end + public static long offset(long packed, int x, int y, int z) { +- return asLong(x(packed) + x, y(packed) + y, z(packed) + z); ++ return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static int posToSectionCoord(double coord) { +@@ -86,10 +94,7 @@ public class SectionPos extends Vec3i { + } + + public static short sectionRelativePos(BlockPos pos) { +- int i = sectionRelative(pos.getX()); +- int j = sectionRelative(pos.getY()); +- int k = sectionRelative(pos.getZ()); +- return (short)(i << 8 | k << 4 | j << 0); ++ return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15); // Paper - simplify/inline + } + + public static int sectionRelativeX(short packedLocalPos) { +@@ -152,16 +157,16 @@ public class SectionPos extends Vec3i { + return this.getZ(); + } + +- public int minBlockX() { +- return sectionToBlockCoord(this.x()); ++ public final int minBlockX() { // Paper - make final ++ return this.getX() << 4; // Paper - inline + } + +- public int minBlockY() { +- return sectionToBlockCoord(this.y()); ++ public final int minBlockY() { // Paper - make final ++ return this.getY() << 4; // Paper - inline + } + +- public int minBlockZ() { +- return sectionToBlockCoord(this.z()); ++ public int minBlockZ() { // Paper - make final ++ return this.getZ() << 4; // Paper - inline + } + + public int maxBlockX() { +@@ -177,7 +182,8 @@ public class SectionPos extends Vec3i { + } + + public static long blockToSection(long blockPos) { +- return asLong(blockToSectionCoord(BlockPos.getX(blockPos)), blockToSectionCoord(BlockPos.getY(blockPos)), blockToSectionCoord(BlockPos.getZ(blockPos))); ++ // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); ++ return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count + } + + public static long getZeroNode(long pos) { +@@ -201,15 +207,18 @@ public class SectionPos extends Vec3i { + return asLong(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ())); + } + ++ // 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 x, int y, int z) { +- long l = 0L; +- l |= ((long)x & 4194303L) << 42; +- l |= ((long)y & 1048575L) << 0; +- return l | ((long)z & 4194303L) << 20; ++ return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + public long asLong() { +- return asLong(this.x(), this.y(), this.z()); ++ return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count + } + + @Override +@@ -222,16 +231,11 @@ public class SectionPos extends Vec3i { + } + + public static Stream cube(SectionPos center, int radius) { +- int i = center.x(); +- int j = center.y(); +- int k = center.z(); +- return betweenClosedStream(i - radius, j - radius, k - radius, i + radius, j + radius, k + radius); ++ return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius); // Paper - simplify/inline + } + + public static Stream aroundChunk(ChunkPos center, int radius, int minY, int maxY) { +- int i = center.x; +- int j = center.z; +- return betweenClosedStream(i - radius, minY, j - radius, i + radius, maxY - 1, j + radius); ++ return betweenClosedStream(center.x - radius, 0, center.z - radius, center.x + radius, 15, center.z + radius); // Paper - simplify/inline + } + + public static Stream betweenClosedStream(final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ) { diff --git a/patches/server/0449-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0449-Add-Plugin-Tickets-to-API-Chunk-Methods.patch new file mode 100644 index 0000000000..506018715e --- /dev/null +++ b/patches/server/0449-Add-Plugin-Tickets-to-API-Chunk-Methods.patch @@ -0,0 +1,121 @@ +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 99a9bf12f2e3e6ac5457da53f6f12118efce5d82..58f361ad664ba45c496ab0817c7ab5e1a017b807 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -360,7 +360,7 @@ public final class CraftServer implements Server { + this.overrideSpawnLimits(); + console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = this.configuration.getString("settings.minimum-api"); + this.loadIcon(); + +@@ -931,7 +931,7 @@ public final class CraftServer implements Server { + this.console.setMotd(config.motd); + this.overrideSpawnLimits(); + this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); +- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); ++ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second + this.minimumAPI = this.configuration.getString("settings.minimum-api"); + this.printSaveWarning = false; + console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index a8ab324bfbaaf946af5998402588244465dd7286..710270775cd47e8df1146ad0636648568d45ac75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -282,8 +282,21 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public Chunk getChunkAt(int x, int z) { +- return this.world.getChunkSource().getChunk(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.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); ++ if (chunk == null) { ++ addTicket(x, z); ++ chunk = this.world.getChunkSource().getChunk(x, z, true); ++ } ++ return chunk.bukkitChunk; ++ // Paper end ++ } ++ ++ // Paper start ++ private void addTicket(int x, int z) { ++ net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper + } ++ // Paper end + + @Override + public Chunk getChunkAt(Block block) { +@@ -350,7 +363,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean unloadChunkRequest(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + if (this.isChunkLoaded(x, z)) { +- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); ++ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper + } + + return true; +@@ -433,9 +446,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + // Paper start - Optimize this method + ChunkPos chunkPos = new ChunkPos(x, z); ++ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); // Paper ++ if (immediate != null) return true; // Paper + + if (!generate) { +- ChunkAccess immediate = world.getChunkSource().getChunkAtImmediately(x, z); ++ ++ //IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); // Paper + if (immediate == null) { + immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); + } +@@ -443,7 +459,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { + return false; // not full status + } +- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunk(x, z); // make sure we're at ticket level 32 or lower + return true; + } +@@ -469,7 +485,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // we do this so we do not re-read the chunk data on disk + } + +- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper + world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); + return true; + // Paper end +@@ -2150,6 +2166,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { + net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; ++ if (chunk != null) addTicket(x, z); // Paper + ret.complete(chunk == null ? null : chunk.getBukkitChunk()); + }); + }); diff --git a/patches/server/0449-Optimize-Bit-Operations-by-inlining.patch b/patches/server/0449-Optimize-Bit-Operations-by-inlining.patch deleted file mode 100644 index c1cce4278a..0000000000 --- a/patches/server/0449-Optimize-Bit-Operations-by-inlining.patch +++ /dev/null @@ -1,213 +0,0 @@ -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/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index f8361dcf9d0378497ec5a34ea53b4e0019700851..e68cd32a7db88f29d3224b0908119232ab3cf71a 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -30,15 +30,16 @@ public class BlockPos extends Vec3i { - }).stable(); - private static final Logger LOGGER = LogUtils.getLogger(); - public static final BlockPos ZERO = new BlockPos(0, 0, 0); -- private static final int PACKED_X_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000)); -- private static final int PACKED_Z_LENGTH = PACKED_X_LENGTH; -- public static final int PACKED_Y_LENGTH = 64 - PACKED_X_LENGTH - PACKED_Z_LENGTH; -- private static final long PACKED_X_MASK = (1L << PACKED_X_LENGTH) - 1L; -- private static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L; -- private static final long PACKED_Z_MASK = (1L << PACKED_Z_LENGTH) - 1L; -- private static final int Y_OFFSET = 0; -- private static final int Z_OFFSET = PACKED_Y_LENGTH; -- private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_Z_LENGTH; -+ // Paper start - static constants -+ private static final int PACKED_X_LENGTH = 26; -+ private static final int PACKED_Z_LENGTH = 26; -+ public static final int PACKED_Y_LENGTH = 12; -+ private static final long PACKED_X_MASK = 67108863; -+ private static final long PACKED_Y_MASK = 4095; -+ private static final long PACKED_Z_MASK = 67108863; -+ private static final int Z_OFFSET = 12; -+ private static final int X_OFFSET = 38; -+ // Paper end - - public BlockPos(int x, int y, int z) { - super(x, y, z); -@@ -60,28 +61,29 @@ public class BlockPos extends Vec3i { - this(pos.getX(), pos.getY(), pos.getZ()); - } - -+ public static long getAdjacent(int baseX, int baseY, int baseZ, Direction enumdirection) { return asLong(baseX + enumdirection.getStepX(), baseY + enumdirection.getStepY(), baseZ + enumdirection.getStepZ()); } // Paper - public static long offset(long value, Direction direction) { - return offset(value, direction.getStepX(), direction.getStepY(), direction.getStepZ()); - } - - public static long offset(long value, int x, int y, int z) { -- return asLong(getX(value) + x, getY(value) + y, getZ(value) + z); -+ return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z); // Paper - simplify/inline - } - - public static int getX(long packedPos) { -- return (int)(packedPos << 64 - X_OFFSET - PACKED_X_LENGTH >> 64 - PACKED_X_LENGTH); -+ return (int) (packedPos >> 38); // Paper - simplify/inline - } - - public static int getY(long packedPos) { -- return (int)(packedPos << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH); -+ return (int) ((packedPos << 52) >> 52); // Paper - simplify/inline - } - - public static int getZ(long packedPos) { -- return (int)(packedPos << 64 - Z_OFFSET - PACKED_Z_LENGTH >> 64 - PACKED_Z_LENGTH); -+ return (int) ((packedPos << 26) >> 38); // Paper - simplify/inline - } - - public static BlockPos of(long packedPos) { -- return new BlockPos(getX(packedPos), getY(packedPos), getZ(packedPos)); -+ return new BlockPos((int) (packedPos >> 38), (int) ((packedPos << 52) >> 52), (int) ((packedPos << 26) >> 38)); // Paper - simplify/inline - } - - public long asLong() { -@@ -89,10 +91,7 @@ public class BlockPos extends Vec3i { - } - - public static long asLong(int x, int y, int z) { -- long l = 0L; -- l |= ((long)x & PACKED_X_MASK) << X_OFFSET; -- l |= ((long)y & PACKED_Y_MASK) << 0; -- return l | ((long)z & PACKED_Z_MASK) << Z_OFFSET; -+ return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); // Paper - inline constants and simplify - } - - public static long getFlatIndex(long y) { -diff --git a/src/main/java/net/minecraft/core/SectionPos.java b/src/main/java/net/minecraft/core/SectionPos.java -index a481bd6328fe9e66f6911bf32ed11947c504c93c..a9d0d72aad7b1b708617a082c5efcd881a0f00d3 100644 ---- a/src/main/java/net/minecraft/core/SectionPos.java -+++ b/src/main/java/net/minecraft/core/SectionPos.java -@@ -38,7 +38,7 @@ public class SectionPos extends Vec3i { - } - - public static SectionPos of(BlockPos pos) { -- return new SectionPos(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ())); -+ return new SectionPos(pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4); // Paper - } - - public static SectionPos of(ChunkPos chunkPos, int y) { -@@ -54,7 +54,7 @@ public class SectionPos extends Vec3i { - } - - public static SectionPos of(long packed) { -- return new SectionPos(x(packed), y(packed), z(packed)); -+ return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42)); // Paper - } - - public static SectionPos bottomOf(ChunkAccess chunk) { -@@ -65,8 +65,16 @@ public class SectionPos extends Vec3i { - return offset(packed, direction.getStepX(), direction.getStepY(), direction.getStepZ()); - } - -+ // Paper start -+ public static long getAdjacentFromBlockPos(int x, int y, int z, Direction enumdirection) { -+ return (((long) ((x >> 4) + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y >> 4) + enumdirection.getStepY()) & 1048575L)) | (((long) ((z >> 4) + enumdirection.getStepZ()) & 4194303L) << 20); -+ } -+ public static long getAdjacentFromSectionPos(int x, int y, int z, Direction enumdirection) { -+ return (((long) (x + enumdirection.getStepX()) & 4194303L) << 42) | (((long) ((y) + enumdirection.getStepY()) & 1048575L)) | (((long) (z + enumdirection.getStepZ()) & 4194303L) << 20); -+ } -+ // Paper end - public static long offset(long packed, int x, int y, int z) { -- return asLong(x(packed) + x, y(packed) + y, z(packed) + z); -+ return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20); // Simplify to reduce instruction count - } - - public static int posToSectionCoord(double coord) { -@@ -86,10 +94,7 @@ public class SectionPos extends Vec3i { - } - - public static short sectionRelativePos(BlockPos pos) { -- int i = sectionRelative(pos.getX()); -- int j = sectionRelative(pos.getY()); -- int k = sectionRelative(pos.getZ()); -- return (short)(i << 8 | k << 4 | j << 0); -+ return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15); // Paper - simplify/inline - } - - public static int sectionRelativeX(short packedLocalPos) { -@@ -152,16 +157,16 @@ public class SectionPos extends Vec3i { - return this.getZ(); - } - -- public int minBlockX() { -- return sectionToBlockCoord(this.x()); -+ public final int minBlockX() { // Paper - make final -+ return this.getX() << 4; // Paper - inline - } - -- public int minBlockY() { -- return sectionToBlockCoord(this.y()); -+ public final int minBlockY() { // Paper - make final -+ return this.getY() << 4; // Paper - inline - } - -- public int minBlockZ() { -- return sectionToBlockCoord(this.z()); -+ public int minBlockZ() { // Paper - make final -+ return this.getZ() << 4; // Paper - inline - } - - public int maxBlockX() { -@@ -177,7 +182,8 @@ public class SectionPos extends Vec3i { - } - - public static long blockToSection(long blockPos) { -- return asLong(blockToSectionCoord(BlockPos.getX(blockPos)), blockToSectionCoord(BlockPos.getY(blockPos)), blockToSectionCoord(BlockPos.getZ(blockPos))); -+ // b(a(BlockPosition.b(i)), a(BlockPosition.c(i)), a(BlockPosition.d(i))); -+ return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20); // Simplify to reduce instruction count - } - - public static long getZeroNode(long pos) { -@@ -201,15 +207,18 @@ public class SectionPos extends Vec3i { - return asLong(blockToSectionCoord(pos.getX()), blockToSectionCoord(pos.getY()), blockToSectionCoord(pos.getZ())); - } - -+ // 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 x, int y, int z) { -- long l = 0L; -- l |= ((long)x & 4194303L) << 42; -- l |= ((long)y & 1048575L) << 0; -- return l | ((long)z & 4194303L) << 20; -+ return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20); // Paper - Simplify to reduce instruction count - } - - public long asLong() { -- return asLong(this.x(), this.y(), this.z()); -+ return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20); // Paper - Simplify to reduce instruction count - } - - @Override -@@ -222,16 +231,11 @@ public class SectionPos extends Vec3i { - } - - public static Stream cube(SectionPos center, int radius) { -- int i = center.x(); -- int j = center.y(); -- int k = center.z(); -- return betweenClosedStream(i - radius, j - radius, k - radius, i + radius, j + radius, k + radius); -+ return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius); // Paper - simplify/inline - } - - public static Stream aroundChunk(ChunkPos center, int radius, int minY, int maxY) { -- int i = center.x; -- int j = center.z; -- return betweenClosedStream(i - radius, minY, j - radius, i + radius, maxY - 1, j + radius); -+ return betweenClosedStream(center.x - radius, 0, center.z - radius, center.x + radius, 15, center.z + radius); // Paper - simplify/inline - } - - public static Stream betweenClosedStream(final int minX, final int minY, final int minZ, final int maxX, final int maxY, final int maxZ) { diff --git a/patches/server/0450-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0450-Add-Plugin-Tickets-to-API-Chunk-Methods.patch deleted file mode 100644 index 506018715e..0000000000 --- a/patches/server/0450-Add-Plugin-Tickets-to-API-Chunk-Methods.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 9 Jun 2020 03:33:03 -0400 -Subject: [PATCH] Add Plugin Tickets to API Chunk Methods - -Like previous versions, plugins loading chunks kept them loaded until -they garbage collected to avoid constant spamming of chunk loads - -This adds tickets to a few more places so that they can be unloaded. - -Additionally, this drops their ticket level to BORDER so they wont be ticking -so they will just sit inactive instead. - -Using .loadChunk to keep a chunk ticking was a horrible idea for upstream -when we have TWO methods that are able to do that already in the API. - -Also reduce their collection count down to a maximum of 1 second. Barely -anyone knows what chunk-gc is in bukkit.yml as its less relevant now, and -since this wasn't spigot behavior, this is safe to mostly ignore (unless someone -wants it to collect even faster, they can restore that setting back to 1 instead of 20+) - -Not adding it to .getType() though to keep behavior consistent with vanilla for performance reasons. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 99a9bf12f2e3e6ac5457da53f6f12118efce5d82..58f361ad664ba45c496ab0817c7ab5e1a017b807 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -360,7 +360,7 @@ public final class CraftServer implements Server { - this.overrideSpawnLimits(); - console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, this.configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = this.configuration.getString("settings.minimum-api"); - this.loadIcon(); - -@@ -931,7 +931,7 @@ public final class CraftServer implements Server { - this.console.setMotd(config.motd); - this.overrideSpawnLimits(); - this.warningState = WarningState.value(this.configuration.getString("settings.deprecated-verbose")); -- TicketType.PLUGIN.timeout = this.configuration.getInt("chunk-gc.period-in-ticks"); -+ TicketType.PLUGIN.timeout = Math.min(20, configuration.getInt("chunk-gc.period-in-ticks")); // Paper - cap plugin loads to 1 second - this.minimumAPI = this.configuration.getString("settings.minimum-api"); - this.printSaveWarning = false; - console.autosavePeriod = this.configuration.getInt("ticks-per.autosave"); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index a8ab324bfbaaf946af5998402588244465dd7286..710270775cd47e8df1146ad0636648568d45ac75 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -282,8 +282,21 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public Chunk getChunkAt(int x, int z) { -- return this.world.getChunkSource().getChunk(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.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -+ if (chunk == null) { -+ addTicket(x, z); -+ chunk = this.world.getChunkSource().getChunk(x, z, true); -+ } -+ return chunk.bukkitChunk; -+ // Paper end -+ } -+ -+ // Paper start -+ private void addTicket(int x, int z) { -+ net.minecraft.server.MCUtil.MAIN_EXECUTOR.execute(() -> world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE)); // Paper - } -+ // Paper end - - @Override - public Chunk getChunkAt(Block block) { -@@ -350,7 +363,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean unloadChunkRequest(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot - if (this.isChunkLoaded(x, z)) { -- this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE); -+ this.world.getChunkSource().removeRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper - } - - return true; -@@ -433,9 +446,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot - // Paper start - Optimize this method - ChunkPos chunkPos = new ChunkPos(x, z); -+ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); // Paper -+ if (immediate != null) return true; // Paper - - if (!generate) { -- ChunkAccess immediate = world.getChunkSource().getChunkAtImmediately(x, z); -+ -+ //IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); // Paper - if (immediate == null) { - immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z); - } -@@ -443,7 +459,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) { - return false; // not full status - } -- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); -+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper - world.getChunk(x, z); // make sure we're at ticket level 32 or lower - return true; - } -@@ -469,7 +485,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // we do this so we do not re-read the chunk data on disk - } - -- world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); -+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper - world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true); - return true; - // Paper end -@@ -2150,6 +2166,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; -+ if (chunk != null) addTicket(x, z); // Paper - ret.complete(chunk == null ? null : chunk.getBukkitChunk()); - }); - }); diff --git a/patches/server/0450-incremental-chunk-and-player-saving.patch b/patches/server/0450-incremental-chunk-and-player-saving.patch new file mode 100644 index 0000000000..8c38af4a69 --- /dev/null +++ b/patches/server/0450-incremental-chunk-and-player-saving.patch @@ -0,0 +1,375 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 9 Jun 2019 03:53:22 +0100 +Subject: [PATCH] incremental chunk and player saving + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 7d2fee97f4d08eae245475c4b60c1a7ba46c840d..48650bc1c09b18f1b57d9828dfe27f51c74c4a75 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -854,7 +854,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.tickCount % this.autosavePeriod == 0) { // CraftBukkit +- MinecraftServer.LOGGER.debug("Autosave started"); +- this.profiler.push("save"); +- this.saveEverything(true, false, false); +- this.profiler.pop(); +- MinecraftServer.LOGGER.debug("Autosave finished"); ++ // Paper start - incremental chunk and player saving ++ int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate; ++ if (playerSaveInterval < 0) { ++ playerSaveInterval = autosavePeriod; + } ++ this.profiler.push("save"); ++ final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0; ++ try { ++ this.isSaving = true; ++ if (playerSaveInterval > 0) { ++ this.playerList.saveAll(playerSaveInterval); ++ } ++ for (ServerLevel level : this.getAllLevels()) { ++ if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { ++ level.saveIncrementally(fullSave); ++ } ++ } ++ } finally { ++ this.isSaving = false; ++ } ++ this.profiler.pop(); ++ // Paper end + io.papermc.paper.util.CachedLists.reset(); // Paper + // Paper start - move executeAll() into full server tick timing + try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 0f75a109c06eb3113be74cf49ec560f5e2ea9cfc..ac42029596ae0c824bf33a4058ac1009740e29ea 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -99,6 +99,8 @@ public class ChunkHolder { + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; + // Paper end - optimise anyPlayerCloseEnoughForSpawning ++ long lastAutoSaveTime; // Paper - incremental autosave ++ long inactiveTimeStart; // Paper - incremental autosave + + public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); +@@ -527,7 +529,19 @@ public class ChunkHolder { + boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); + boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); + ++ boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper + this.wasAccessibleSinceLastSave |= flag3; ++ // Paper start - incremental autosave ++ if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value(); ++ } ++ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } ++ // Paper end + if (!flag2 && flag3) { + int expectCreateCount = ++this.fullChunkCreateCount; // Paper + this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); +@@ -689,8 +703,32 @@ public class ChunkHolder { + } + + public void refreshAccessibility() { ++ boolean prev = this.wasAccessibleSinceLastSave; // Paper + this.wasAccessibleSinceLastSave = ChunkHolder.getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); ++ // Paper start - incremental autosave ++ if (prev != this.wasAccessibleSinceLastSave) { ++ if (this.wasAccessibleSinceLastSave) { ++ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; ++ if (timeSinceAutoSave < 0) { ++ // safest bet is to assume autosave is needed here ++ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value(); ++ } ++ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; ++ this.chunkMap.autoSaveQueue.add(this); ++ } else { ++ this.inactiveTimeStart = this.chunkMap.level.getGameTime(); ++ this.chunkMap.autoSaveQueue.remove(this); ++ } ++ } ++ // Paper end ++ } ++ ++ // Paper start - incremental autosave ++ public boolean setHasBeenLoaded() { ++ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); ++ return this.wasAccessibleSinceLastSave; + } ++ // Paper end + + public void replaceProtoChunk(ImposterProtoChunk chunk) { + for (int i = 0; i < this.futures.length(); ++i) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index b0e0f85e04438affb8d8e0f75055ea83d0c03bcd..7493da0f1c3f8ab0ebc517347ef23fbe2747a306 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -103,6 +103,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; + import net.minecraft.world.phys.Vec3; ++import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper + import org.apache.commons.lang3.mutable.MutableBoolean; + import org.apache.commons.lang3.mutable.MutableObject; + import org.slf4j.Logger; +@@ -779,6 +780,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + ++ // 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.pos), MCUtil.getCoordinateKey(playerchunk2.pos)); ++ }); ++ ++ 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.level.paperConfig().chunks.maxAutoSaveChunksPerTick); ++ long currentTick = this.level.getGameTime(); ++ long maxSaveTime = currentTick - this.level.paperConfig().chunks.autoSaveInterval.value(); ++ ++ for (Iterator iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) { ++ ChunkHolder playerchunk = iterator.next(); ++ if (playerchunk.lastAutoSaveTime > maxSaveTime) { ++ break; ++ } ++ ++ iterator.remove(); ++ ++ ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null); ++ if (ichunkaccess instanceof LevelChunk) { ++ boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime; ++ ++ if (shouldSave && this.save(ichunkaccess) && this.level.entityManager.storeChunkSections(playerchunk.pos.toLong(), entity -> {})) { ++ ++savedThisTick; ++ ++ if (!playerchunk.setHasBeenLoaded()) { ++ // do not fall through to reschedule logic ++ playerchunk.inactiveTimeStart = currentTick; ++ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ continue; ++ } ++ } ++ } ++ ++ reschedule.add(playerchunk); ++ ++ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) { ++ break; ++ } ++ } ++ ++ for (int i = 0, len = reschedule.size(); i < len; ++i) { ++ ChunkHolder playerchunk = reschedule.get(i); ++ playerchunk.lastAutoSaveTime = this.level.getGameTime(); ++ this.autoSaveQueue.add(playerchunk); ++ } ++ } ++ // Paper end ++ + protected void saveAllChunks(boolean flush) { + // Paper start - do not overload I/O threads with too much work when saving + int[] saved = new int[1]; +@@ -874,13 +933,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + int l = 0; +- Iterator objectiterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper +- +- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { +- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { +- ++l; +- } +- } ++ // Paper - incremental chunk and player saving + + } + +@@ -916,6 +969,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + this.level.unload(chunk); + } ++ this.autoSaveQueue.remove(holder); // Paper + + this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); + this.lightEngine.tryScheduleUpdate(); +@@ -1367,6 +1421,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + asyncSaveData, chunk); + + chunk.setUnsaved(false); ++ chunk.setLastSaved(this.level.getGameTime()); // Paper - track last saved time + } + // Paper end + +@@ -1376,6 +1431,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (!chunk.isUnsaved()) { + return false; + } else { ++ chunk.setLastSaved(this.level.getGameTime()); // Paper - track save time + chunk.setUnsaved(false); + ChunkPos chunkcoordintpair = chunk.getPos(); + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index a59782d6f3640262c377a676e2b2ef5ec82563db..b5f46703e536f8138ff4e6769485c45b35941f9f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -670,6 +670,15 @@ public class ServerChunkCache extends ChunkSource { + } // Paper - Timings + } + ++ // Paper start - duplicate save, but call incremental ++ public void saveIncrementally() { ++ this.runDistanceManagerUpdates(); ++ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings ++ this.chunkMap.saveIncrementally(); ++ } // Paper - Timings ++ } ++ // Paper end ++ + @Override + public void close() throws IOException { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index c9ecc7593c299b351308634db44596a76fd0c09b..8b5eac2ad96c0ebb6eae04585998cade578ff74b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1087,6 +1087,37 @@ public class ServerLevel extends Level implements WorldGenLevel { + return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); + } + ++ // Paper start - derived from below ++ public void saveIncrementally(boolean doFull) { ++ ServerChunkCache chunkproviderserver = this.getChunkSource(); ++ ++ if (doFull) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); ++ } ++ ++ try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { ++ if (doFull) { ++ this.saveLevelData(); ++ } ++ ++ this.timings.worldSaveChunks.startTiming(); // Paper ++ if (!this.noSave()) chunkproviderserver.saveIncrementally(); ++ this.timings.worldSaveChunks.stopTiming(); // Paper ++ ++ // Copied from save() ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ if (doFull) { // Paper ++ ServerLevel worldserver1 = this; ++ ++ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save()); ++ this.convertable.saveDataTag(this.server.registryHolder, this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); ++ } ++ // CraftBukkit end ++ } ++ } ++ // Paper end ++ + public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { + ServerChunkCache chunkproviderserver = this.getChunkSource(); + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index be7d2275548936beade4aba02dc5b14fec95117a..6f2b52165c1935511790a429792d3754251537c8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -179,6 +179,7 @@ import org.bukkit.inventory.MainHand; + public class ServerPlayer extends Player { + + private static final Logger LOGGER = LogUtils.getLogger(); ++ public long lastSave = MinecraftServer.currentTick; // Paper + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; + private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; + public ServerGamePacketListenerImpl connection; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 32ab0cd6cb42b0ab8a14f790dfcf4b155c945d6d..1850ce4566e6c5d19140cbf2636b3573f16c4239 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -569,6 +569,7 @@ public abstract class PlayerList { + protected void save(ServerPlayer player) { + if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit + if (!player.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) ++ player.lastSave = MinecraftServer.currentTick; // Paper + this.playerIo.save(player); + ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit + +@@ -1171,10 +1172,22 @@ public abstract class PlayerList { + } + + public void saveAll() { ++ // Paper start - incremental player saving ++ this.saveAll(-1); ++ } ++ ++ public void saveAll(int interval) { + net.minecraft.server.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main + MinecraftTimings.savePlayers.startTiming(); // Paper ++ int numSaved = 0; ++ long now = MinecraftServer.currentTick; + for (int i = 0; i < this.players.size(); ++i) { +- this.save(this.players.get(i)); ++ ServerPlayer entityplayer = this.players.get(i); ++ if (interval == -1 || now - entityplayer.lastSave >= interval) { ++ this.save(entityplayer); ++ if (interval != -1 && ++numSaved <= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; } ++ } ++ // Paper end + } + MinecraftTimings.savePlayers.stopTiming(); // Paper + return null; }); // Paper - ensure main +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index dc164608bfb2fb18a1adf83fa10bac4028dcac0a..a97909e77b9b28aede8c8716831c3f9a90618f09 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -457,6 +457,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + public LevelHeightAccessor getHeightAccessorForGeneration() { + return this; + } ++ public void setLastSaved(long ticks) {} // Paper + + public static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids) { + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index c8444f4bfd127b7d8194aaa984505eff249ae094..2981ba61e347b8660082ff946521fc7f219d2c0d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -87,6 +87,12 @@ public class LevelChunk extends ChunkAccess { + private final Int2ObjectMap gameEventDispatcherSections; + private final LevelChunkTicks blockTicks; + private final LevelChunkTicks fluidTicks; ++ // Paper start - track last save time ++ public long lastSaveTime; ++ public void setLastSaved(long ticks) { ++ this.lastSaveTime = ticks; ++ } ++ // Paper end + + public LevelChunk(Level world, ChunkPos pos) { + this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); diff --git a/patches/server/0451-Stop-copy-on-write-operations-for-updating-light-dat.patch b/patches/server/0451-Stop-copy-on-write-operations-for-updating-light-dat.patch new file mode 100644 index 0000000000..74e5860e6b --- /dev/null +++ b/patches/server/0451-Stop-copy-on-write-operations-for-updating-light-dat.patch @@ -0,0 +1,297 @@ +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/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +index 573cdb0897978eef8f5fc906ed4928293f4b2ab9..314b46f0becd088d26956b45981217b128d539cb 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java +@@ -9,7 +9,7 @@ import net.minecraft.world.level.chunk.LightChunkGetter; + + public class BlockLightSectionStorage extends LayerLightSectionStorage { + protected BlockLightSectionStorage(LightChunkGetter chunkProvider) { +- super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new Long2ObjectOpenHashMap<>())); ++ super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), false)); // Paper - avoid copying light data + } + + @Override +@@ -20,13 +20,13 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage { +- public BlockDataLayerStorageMap(Long2ObjectOpenHashMap arrays) { +- super(arrays); ++ public BlockDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object long2objectopenhashmap, boolean isVisible) { // Paper - avoid copying light data ++ super(long2objectopenhashmap, isVisible); // Paper - avoid copying light data + } + + @Override + public BlockLightSectionStorage.BlockDataLayerStorageMap copy() { +- return new BlockLightSectionStorage.BlockDataLayerStorageMap(this.map.clone()); ++ return new BlockDataLayerStorageMap(this.data, true); // Paper - avoid copying light data + } + } + } +diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +index 67ff66e232592203cf8dad605ad01eabc4dded89..f357a3473682c2d37a20fb862522c67b9979402a 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java ++++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java +@@ -9,10 +9,23 @@ public abstract class DataLayerStorageMap> { + private final long[] lastSectionKeys = new long[2]; + private final DataLayer[] lastSections = new DataLayer[2]; + private boolean cacheEnabled; +- protected final Long2ObjectOpenHashMap map; ++ 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 + +- protected DataLayerStorageMap(Long2ObjectOpenHashMap arrays) { +- this.map = arrays; ++ // Paper start - avoid copying light data ++ protected DataLayerStorageMap(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.clearCache(); + this.cacheEnabled = true; + } +@@ -20,16 +33,17 @@ public abstract class DataLayerStorageMap> { + public abstract M copy(); + + public void copyDataLayer(long pos) { +- this.map.put(pos, this.map.get(pos).copy()); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data + this.clearCache(); + } + + public boolean hasLayer(long chunkPos) { +- return this.map.containsKey(chunkPos); ++ return lookup.apply(chunkPos) != null; // Paper - avoid copying light data + } + + @Nullable +- public DataLayer getLayer(long chunkPos) { ++ public final DataLayer getLayer(long chunkPos) { // Paper - final + if (this.cacheEnabled) { + for(int i = 0; i < 2; ++i) { + if (chunkPos == this.lastSectionKeys[i]) { +@@ -38,7 +52,7 @@ public abstract class DataLayerStorageMap> { + } + } + +- DataLayer dataLayer = this.map.get(chunkPos); ++ DataLayer dataLayer = lookup.apply(chunkPos); // Paper - avoid copying light data + if (dataLayer == null) { + return null; + } else { +@@ -58,11 +72,13 @@ public abstract class DataLayerStorageMap> { + + @Nullable + public DataLayer removeLayer(long chunkPos) { +- return this.map.remove(chunkPos); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ return (DataLayer) this.data.queueRemove(chunkPos); // Paper - avoid copying light data + } + + public void setLayer(long pos, DataLayer data) { +- this.map.put(pos, data); ++ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data ++ this.data.queueUpdate(pos, data); // Paper - avoid copying light data + } + + public void clearCache() { +diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +index c899674ee24167ee3abdf1588d7396df0ab4f0aa..85175b01b1623b3bc66c65805cec26eaead48265 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java +@@ -27,7 +27,7 @@ public abstract class LayerLightSectionStorage> + protected final LongSet dataSectionSet = new LongOpenHashSet(); + protected final LongSet toMarkNoData = new LongOpenHashSet(); + protected final LongSet toMarkData = new LongOpenHashSet(); +- protected volatile M visibleSectionData; ++ 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 updatingSectionData; + protected final LongSet changedSections = new LongOpenHashSet(); + protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); +@@ -42,8 +42,8 @@ public abstract class LayerLightSectionStorage> + this.layer = lightType; + this.chunkSource = chunkProvider; + this.updatingSectionData = lightData; +- this.visibleSectionData = lightData.copy(); +- this.visibleSectionData.disableCache(); ++ this.e_visible = lightData.copy(); // Paper - avoid copying light dat ++ this.e_visible.disableCache(); // Paper - avoid copying light dat + } + + protected boolean storingLightForSection(long sectionPos) { +@@ -52,7 +52,15 @@ public abstract class LayerLightSectionStorage> + + @Nullable + protected DataLayer getDataLayer(long sectionPos, boolean cached) { +- return this.getDataLayer((M)(cached ? this.updatingSectionData : this.visibleSectionData), sectionPos); ++ // Paper start - avoid copying light data ++ if (cached) { ++ return this.getDataLayer(this.updatingSectionData, sectionPos); ++ } else { ++ synchronized (this.visibleUpdateLock) { ++ return this.getDataLayer(this.e_visible, sectionPos); ++ } ++ } ++ // Paper end - avoid copying light data + } + + @Nullable +@@ -342,9 +350,11 @@ public abstract class LayerLightSectionStorage> + + protected void swapSectionMap() { + if (!this.changedSections.isEmpty()) { ++ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data + M dataLayerStorageMap = this.updatingSectionData.copy(); + dataLayerStorageMap.disableCache(); +- this.visibleSectionData = dataLayerStorageMap; ++ this.e_visible = dataLayerStorageMap; // Paper - avoid copying light data ++ } // Paper - avoid copying light data + this.changedSections.clear(); + } + +diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +index 59d2af9f883541518c203302257f03dbe957aa0b..e6c857c8b4e4e65e3cf6a75ce6d844ff61acb566 100644 +--- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java +@@ -21,7 +21,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage(), new Long2IntOpenHashMap(), Integer.MAX_VALUE)); ++ super(LightLayer.SKY, chunkProvider, new SkyLightSectionStorage.SkyDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Paper - avoid copying light data + } + + @Override +@@ -32,8 +32,9 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage i) { + (this.updatingSectionData).currentLowestY = i; +- (this.updatingSectionData).topSections.defaultReturnValue((this.updatingSectionData).currentLowestY); ++ (this.updatingSectionData).otherData.queueDefaultReturnValue((this.updatingSectionData).currentLowestY); // Paper - avoid copying light data + } + + long l = SectionPos.getZeroNode(sectionPos); +- int j = (this.updatingSectionData).topSections.get(l); ++ int j = (this.updatingSectionData).otherData.getUpdating(l); // Paper - avoid copying light data + if (j < i + 1) { +- (this.updatingSectionData).topSections.put(l, i + 1); ++ (this.updatingSectionData).otherData.queueUpdate(l, i + 1); // Paper - avoid copying light data + if (this.columnsWithSkySources.contains(l)) { + this.queueAddSource(sectionPos); + if (j > (this.updatingSectionData).currentLowestY) { +@@ -102,19 +104,19 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage= i; + } + +@@ -286,18 +288,21 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage { + int currentLowestY; +- final Long2IntOpenHashMap topSections; +- +- public SkyDataLayerStorageMap(Long2ObjectOpenHashMap arrays, Long2IntOpenHashMap columnToTopSection, int minSectionY) { +- super(arrays); +- this.topSections = columnToTopSection; +- columnToTopSection.defaultReturnValue(minSectionY); ++ private final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData; // Paper - avoid copying light data ++ ++ // Paper start - avoid copying light data ++ public SkyDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object arrays, com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int columnToTopSection, int minSectionY, boolean isVisible) { ++ super(arrays, isVisible); ++ this.otherData = columnToTopSection; ++ otherData.queueDefaultReturnValue(minSectionY); ++ // Paper end + this.currentLowestY = minSectionY; + } + + @Override + public SkyLightSectionStorage.SkyDataLayerStorageMap copy() { +- return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.map.clone(), this.topSections.clone(), this.currentLowestY); ++ this.otherData.performUpdatesLockMap(); // Paper - avoid copying light data ++ return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.data, this.otherData, this.currentLowestY, true); // Paper - avoid copying light data + } + } + } diff --git a/patches/server/0451-incremental-chunk-and-player-saving.patch b/patches/server/0451-incremental-chunk-and-player-saving.patch deleted file mode 100644 index 8c38af4a69..0000000000 --- a/patches/server/0451-incremental-chunk-and-player-saving.patch +++ /dev/null @@ -1,375 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 9 Jun 2019 03:53:22 +0100 -Subject: [PATCH] incremental chunk and player saving - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7d2fee97f4d08eae245475c4b60c1a7ba46c840d..48650bc1c09b18f1b57d9828dfe27f51c74c4a75 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -854,7 +854,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.tickCount % this.autosavePeriod == 0) { // CraftBukkit -- MinecraftServer.LOGGER.debug("Autosave started"); -- this.profiler.push("save"); -- this.saveEverything(true, false, false); -- this.profiler.pop(); -- MinecraftServer.LOGGER.debug("Autosave finished"); -+ // Paper start - incremental chunk and player saving -+ int playerSaveInterval = io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.rate; -+ if (playerSaveInterval < 0) { -+ playerSaveInterval = autosavePeriod; - } -+ this.profiler.push("save"); -+ final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0; -+ try { -+ this.isSaving = true; -+ if (playerSaveInterval > 0) { -+ this.playerList.saveAll(playerSaveInterval); -+ } -+ for (ServerLevel level : this.getAllLevels()) { -+ if (level.paperConfig().chunks.autoSaveInterval.value() > 0) { -+ level.saveIncrementally(fullSave); -+ } -+ } -+ } finally { -+ this.isSaving = false; -+ } -+ this.profiler.pop(); -+ // Paper end - io.papermc.paper.util.CachedLists.reset(); // Paper - // Paper start - move executeAll() into full server tick timing - try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0f75a109c06eb3113be74cf49ec560f5e2ea9cfc..ac42029596ae0c824bf33a4058ac1009740e29ea 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -99,6 +99,8 @@ public class ChunkHolder { - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; - // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ long lastAutoSaveTime; // Paper - incremental autosave -+ long inactiveTimeStart; // Paper - incremental autosave - - public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { - this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); -@@ -527,7 +529,19 @@ public class ChunkHolder { - boolean flag2 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); - boolean flag3 = playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER); - -+ boolean prevHasBeenLoaded = this.wasAccessibleSinceLastSave; // Paper - this.wasAccessibleSinceLastSave |= flag3; -+ // Paper start - incremental autosave -+ if (this.wasAccessibleSinceLastSave & !prevHasBeenLoaded) { -+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; -+ if (timeSinceAutoSave < 0) { -+ // safest bet is to assume autosave is needed here -+ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value(); -+ } -+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; -+ this.chunkMap.autoSaveQueue.add(this); -+ } -+ // Paper end - if (!flag2 && flag3) { - int expectCreateCount = ++this.fullChunkCreateCount; // Paper - this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); -@@ -689,8 +703,32 @@ public class ChunkHolder { - } - - public void refreshAccessibility() { -+ boolean prev = this.wasAccessibleSinceLastSave; // Paper - this.wasAccessibleSinceLastSave = ChunkHolder.getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); -+ // Paper start - incremental autosave -+ if (prev != this.wasAccessibleSinceLastSave) { -+ if (this.wasAccessibleSinceLastSave) { -+ long timeSinceAutoSave = this.inactiveTimeStart - this.lastAutoSaveTime; -+ if (timeSinceAutoSave < 0) { -+ // safest bet is to assume autosave is needed here -+ timeSinceAutoSave = this.chunkMap.level.paperConfig().chunks.autoSaveInterval.value(); -+ } -+ this.lastAutoSaveTime = this.chunkMap.level.getGameTime() - timeSinceAutoSave; -+ this.chunkMap.autoSaveQueue.add(this); -+ } else { -+ this.inactiveTimeStart = this.chunkMap.level.getGameTime(); -+ this.chunkMap.autoSaveQueue.remove(this); -+ } -+ } -+ // Paper end -+ } -+ -+ // Paper start - incremental autosave -+ public boolean setHasBeenLoaded() { -+ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER); -+ return this.wasAccessibleSinceLastSave; - } -+ // Paper end - - public void replaceProtoChunk(ImposterProtoChunk chunk) { - for (int i = 0; i < this.futures.length(); ++i) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index b0e0f85e04438affb8d8e0f75055ea83d0c03bcd..7493da0f1c3f8ab0ebc517347ef23fbe2747a306 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -103,6 +103,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp - import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelStorageSource; - import net.minecraft.world.phys.Vec3; -+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; // Paper - import org.apache.commons.lang3.mutable.MutableBoolean; - import org.apache.commons.lang3.mutable.MutableObject; - import org.slf4j.Logger; -@@ -779,6 +780,64 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - } - -+ // 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.pos), MCUtil.getCoordinateKey(playerchunk2.pos)); -+ }); -+ -+ 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.level.paperConfig().chunks.maxAutoSaveChunksPerTick); -+ long currentTick = this.level.getGameTime(); -+ long maxSaveTime = currentTick - this.level.paperConfig().chunks.autoSaveInterval.value(); -+ -+ for (Iterator iterator = this.autoSaveQueue.iterator(); iterator.hasNext();) { -+ ChunkHolder playerchunk = iterator.next(); -+ if (playerchunk.lastAutoSaveTime > maxSaveTime) { -+ break; -+ } -+ -+ iterator.remove(); -+ -+ ChunkAccess ichunkaccess = playerchunk.getChunkToSave().getNow(null); -+ if (ichunkaccess instanceof LevelChunk) { -+ boolean shouldSave = ((LevelChunk)ichunkaccess).lastSaveTime <= maxSaveTime; -+ -+ if (shouldSave && this.save(ichunkaccess) && this.level.entityManager.storeChunkSections(playerchunk.pos.toLong(), entity -> {})) { -+ ++savedThisTick; -+ -+ if (!playerchunk.setHasBeenLoaded()) { -+ // do not fall through to reschedule logic -+ playerchunk.inactiveTimeStart = currentTick; -+ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) { -+ break; -+ } -+ continue; -+ } -+ } -+ } -+ -+ reschedule.add(playerchunk); -+ -+ if (savedThisTick >= this.level.paperConfig().chunks.maxAutoSaveChunksPerTick) { -+ break; -+ } -+ } -+ -+ for (int i = 0, len = reschedule.size(); i < len; ++i) { -+ ChunkHolder playerchunk = reschedule.get(i); -+ playerchunk.lastAutoSaveTime = this.level.getGameTime(); -+ this.autoSaveQueue.add(playerchunk); -+ } -+ } -+ // Paper end -+ - protected void saveAllChunks(boolean flush) { - // Paper start - do not overload I/O threads with too much work when saving - int[] saved = new int[1]; -@@ -874,13 +933,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - int l = 0; -- Iterator objectiterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper -- -- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) { -- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) { -- ++l; -- } -- } -+ // Paper - incremental chunk and player saving - - } - -@@ -916,6 +969,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - this.level.unload(chunk); - } -+ this.autoSaveQueue.remove(holder); // Paper - - this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); - this.lightEngine.tryScheduleUpdate(); -@@ -1367,6 +1421,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - asyncSaveData, chunk); - - chunk.setUnsaved(false); -+ chunk.setLastSaved(this.level.getGameTime()); // Paper - track last saved time - } - // Paper end - -@@ -1376,6 +1431,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (!chunk.isUnsaved()) { - return false; - } else { -+ chunk.setLastSaved(this.level.getGameTime()); // Paper - track save time - chunk.setUnsaved(false); - ChunkPos chunkcoordintpair = chunk.getPos(); - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index a59782d6f3640262c377a676e2b2ef5ec82563db..b5f46703e536f8138ff4e6769485c45b35941f9f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -670,6 +670,15 @@ public class ServerChunkCache extends ChunkSource { - } // Paper - Timings - } - -+ // Paper start - duplicate save, but call incremental -+ public void saveIncrementally() { -+ this.runDistanceManagerUpdates(); -+ try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings -+ this.chunkMap.saveIncrementally(); -+ } // Paper - Timings -+ } -+ // Paper end -+ - @Override - public void close() throws IOException { - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index c9ecc7593c299b351308634db44596a76fd0c09b..8b5eac2ad96c0ebb6eae04585998cade578ff74b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1087,6 +1087,37 @@ public class ServerLevel extends Level implements WorldGenLevel { - return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); - } - -+ // Paper start - derived from below -+ public void saveIncrementally(boolean doFull) { -+ ServerChunkCache chunkproviderserver = this.getChunkSource(); -+ -+ if (doFull) { -+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); -+ } -+ -+ try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { -+ if (doFull) { -+ this.saveLevelData(); -+ } -+ -+ this.timings.worldSaveChunks.startTiming(); // Paper -+ if (!this.noSave()) chunkproviderserver.saveIncrementally(); -+ this.timings.worldSaveChunks.stopTiming(); // Paper -+ -+ // Copied from save() -+ // CraftBukkit start - moved from MinecraftServer.saveChunks -+ if (doFull) { // Paper -+ ServerLevel worldserver1 = this; -+ -+ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); -+ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save()); -+ this.convertable.saveDataTag(this.server.registryHolder, this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); -+ } -+ // CraftBukkit end -+ } -+ } -+ // Paper end -+ - public void save(@Nullable ProgressListener progressListener, boolean flush, boolean savingDisabled) { - ServerChunkCache chunkproviderserver = this.getChunkSource(); - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index be7d2275548936beade4aba02dc5b14fec95117a..6f2b52165c1935511790a429792d3754251537c8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -179,6 +179,7 @@ import org.bukkit.inventory.MainHand; - public class ServerPlayer extends Player { - - private static final Logger LOGGER = LogUtils.getLogger(); -+ public long lastSave = MinecraftServer.currentTick; // Paper - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; - public ServerGamePacketListenerImpl connection; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 32ab0cd6cb42b0ab8a14f790dfcf4b155c945d6d..1850ce4566e6c5d19140cbf2636b3573f16c4239 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -569,6 +569,7 @@ public abstract class PlayerList { - protected void save(ServerPlayer player) { - if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit - if (!player.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) -+ player.lastSave = MinecraftServer.currentTick; // Paper - this.playerIo.save(player); - ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit - -@@ -1171,10 +1172,22 @@ public abstract class PlayerList { - } - - public void saveAll() { -+ // Paper start - incremental player saving -+ this.saveAll(-1); -+ } -+ -+ public void saveAll(int interval) { - net.minecraft.server.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main - MinecraftTimings.savePlayers.startTiming(); // Paper -+ int numSaved = 0; -+ long now = MinecraftServer.currentTick; - for (int i = 0; i < this.players.size(); ++i) { -- this.save(this.players.get(i)); -+ ServerPlayer entityplayer = this.players.get(i); -+ if (interval == -1 || now - entityplayer.lastSave >= interval) { -+ this.save(entityplayer); -+ if (interval != -1 && ++numSaved <= io.papermc.paper.configuration.GlobalConfiguration.get().playerAutoSave.maxPerTick()) { break; } -+ } -+ // Paper end - } - MinecraftTimings.savePlayers.stopTiming(); // Paper - return null; }); // Paper - ensure main -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index dc164608bfb2fb18a1adf83fa10bac4028dcac0a..a97909e77b9b28aede8c8716831c3f9a90618f09 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -457,6 +457,7 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - public LevelHeightAccessor getHeightAccessorForGeneration() { - return this; - } -+ public void setLastSaved(long ticks) {} // Paper - - public static record TicksToSave(SerializableTickContainer blocks, SerializableTickContainer fluids) { - -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index c8444f4bfd127b7d8194aaa984505eff249ae094..2981ba61e347b8660082ff946521fc7f219d2c0d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -87,6 +87,12 @@ public class LevelChunk extends ChunkAccess { - private final Int2ObjectMap gameEventDispatcherSections; - private final LevelChunkTicks blockTicks; - private final LevelChunkTicks fluidTicks; -+ // Paper start - track last save time -+ public long lastSaveTime; -+ public void setLastSaved(long ticks) { -+ this.lastSaveTime = ticks; -+ } -+ // Paper end - - public LevelChunk(Level world, ChunkPos pos) { - this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); diff --git a/patches/server/0452-Stop-copy-on-write-operations-for-updating-light-dat.patch b/patches/server/0452-Stop-copy-on-write-operations-for-updating-light-dat.patch deleted file mode 100644 index 74e5860e6b..0000000000 --- a/patches/server/0452-Stop-copy-on-write-operations-for-updating-light-dat.patch +++ /dev/null @@ -1,297 +0,0 @@ -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/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java -index 573cdb0897978eef8f5fc906ed4928293f4b2ab9..314b46f0becd088d26956b45981217b128d539cb 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java -@@ -9,7 +9,7 @@ import net.minecraft.world.level.chunk.LightChunkGetter; - - public class BlockLightSectionStorage extends LayerLightSectionStorage { - protected BlockLightSectionStorage(LightChunkGetter chunkProvider) { -- super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new Long2ObjectOpenHashMap<>())); -+ super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), false)); // Paper - avoid copying light data - } - - @Override -@@ -20,13 +20,13 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage { -- public BlockDataLayerStorageMap(Long2ObjectOpenHashMap arrays) { -- super(arrays); -+ public BlockDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object long2objectopenhashmap, boolean isVisible) { // Paper - avoid copying light data -+ super(long2objectopenhashmap, isVisible); // Paper - avoid copying light data - } - - @Override - public BlockLightSectionStorage.BlockDataLayerStorageMap copy() { -- return new BlockLightSectionStorage.BlockDataLayerStorageMap(this.map.clone()); -+ return new BlockDataLayerStorageMap(this.data, true); // Paper - avoid copying light data - } - } - } -diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java -index 67ff66e232592203cf8dad605ad01eabc4dded89..f357a3473682c2d37a20fb862522c67b9979402a 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java -+++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java -@@ -9,10 +9,23 @@ public abstract class DataLayerStorageMap> { - private final long[] lastSectionKeys = new long[2]; - private final DataLayer[] lastSections = new DataLayer[2]; - private boolean cacheEnabled; -- protected final Long2ObjectOpenHashMap map; -+ 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 - -- protected DataLayerStorageMap(Long2ObjectOpenHashMap arrays) { -- this.map = arrays; -+ // Paper start - avoid copying light data -+ protected DataLayerStorageMap(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.clearCache(); - this.cacheEnabled = true; - } -@@ -20,16 +33,17 @@ public abstract class DataLayerStorageMap> { - public abstract M copy(); - - public void copyDataLayer(long pos) { -- this.map.put(pos, this.map.get(pos).copy()); -+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data -+ this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data - this.clearCache(); - } - - public boolean hasLayer(long chunkPos) { -- return this.map.containsKey(chunkPos); -+ return lookup.apply(chunkPos) != null; // Paper - avoid copying light data - } - - @Nullable -- public DataLayer getLayer(long chunkPos) { -+ public final DataLayer getLayer(long chunkPos) { // Paper - final - if (this.cacheEnabled) { - for(int i = 0; i < 2; ++i) { - if (chunkPos == this.lastSectionKeys[i]) { -@@ -38,7 +52,7 @@ public abstract class DataLayerStorageMap> { - } - } - -- DataLayer dataLayer = this.map.get(chunkPos); -+ DataLayer dataLayer = lookup.apply(chunkPos); // Paper - avoid copying light data - if (dataLayer == null) { - return null; - } else { -@@ -58,11 +72,13 @@ public abstract class DataLayerStorageMap> { - - @Nullable - public DataLayer removeLayer(long chunkPos) { -- return this.map.remove(chunkPos); -+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data -+ return (DataLayer) this.data.queueRemove(chunkPos); // Paper - avoid copying light data - } - - public void setLayer(long pos, DataLayer data) { -- this.map.put(pos, data); -+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data -+ this.data.queueUpdate(pos, data); // Paper - avoid copying light data - } - - public void clearCache() { -diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java -index c899674ee24167ee3abdf1588d7396df0ab4f0aa..85175b01b1623b3bc66c65805cec26eaead48265 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java -@@ -27,7 +27,7 @@ public abstract class LayerLightSectionStorage> - protected final LongSet dataSectionSet = new LongOpenHashSet(); - protected final LongSet toMarkNoData = new LongOpenHashSet(); - protected final LongSet toMarkData = new LongOpenHashSet(); -- protected volatile M visibleSectionData; -+ 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 updatingSectionData; - protected final LongSet changedSections = new LongOpenHashSet(); - protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet(); -@@ -42,8 +42,8 @@ public abstract class LayerLightSectionStorage> - this.layer = lightType; - this.chunkSource = chunkProvider; - this.updatingSectionData = lightData; -- this.visibleSectionData = lightData.copy(); -- this.visibleSectionData.disableCache(); -+ this.e_visible = lightData.copy(); // Paper - avoid copying light dat -+ this.e_visible.disableCache(); // Paper - avoid copying light dat - } - - protected boolean storingLightForSection(long sectionPos) { -@@ -52,7 +52,15 @@ public abstract class LayerLightSectionStorage> - - @Nullable - protected DataLayer getDataLayer(long sectionPos, boolean cached) { -- return this.getDataLayer((M)(cached ? this.updatingSectionData : this.visibleSectionData), sectionPos); -+ // Paper start - avoid copying light data -+ if (cached) { -+ return this.getDataLayer(this.updatingSectionData, sectionPos); -+ } else { -+ synchronized (this.visibleUpdateLock) { -+ return this.getDataLayer(this.e_visible, sectionPos); -+ } -+ } -+ // Paper end - avoid copying light data - } - - @Nullable -@@ -342,9 +350,11 @@ public abstract class LayerLightSectionStorage> - - protected void swapSectionMap() { - if (!this.changedSections.isEmpty()) { -+ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data - M dataLayerStorageMap = this.updatingSectionData.copy(); - dataLayerStorageMap.disableCache(); -- this.visibleSectionData = dataLayerStorageMap; -+ this.e_visible = dataLayerStorageMap; // Paper - avoid copying light data -+ } // Paper - avoid copying light data - this.changedSections.clear(); - } - -diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java -index 59d2af9f883541518c203302257f03dbe957aa0b..e6c857c8b4e4e65e3cf6a75ce6d844ff61acb566 100644 ---- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java -@@ -21,7 +21,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage(), new Long2IntOpenHashMap(), Integer.MAX_VALUE)); -+ super(LightLayer.SKY, chunkProvider, new SkyLightSectionStorage.SkyDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Paper - avoid copying light data - } - - @Override -@@ -32,8 +32,9 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage i) { - (this.updatingSectionData).currentLowestY = i; -- (this.updatingSectionData).topSections.defaultReturnValue((this.updatingSectionData).currentLowestY); -+ (this.updatingSectionData).otherData.queueDefaultReturnValue((this.updatingSectionData).currentLowestY); // Paper - avoid copying light data - } - - long l = SectionPos.getZeroNode(sectionPos); -- int j = (this.updatingSectionData).topSections.get(l); -+ int j = (this.updatingSectionData).otherData.getUpdating(l); // Paper - avoid copying light data - if (j < i + 1) { -- (this.updatingSectionData).topSections.put(l, i + 1); -+ (this.updatingSectionData).otherData.queueUpdate(l, i + 1); // Paper - avoid copying light data - if (this.columnsWithSkySources.contains(l)) { - this.queueAddSource(sectionPos); - if (j > (this.updatingSectionData).currentLowestY) { -@@ -102,19 +104,19 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage= i; - } - -@@ -286,18 +288,21 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage { - int currentLowestY; -- final Long2IntOpenHashMap topSections; -- -- public SkyDataLayerStorageMap(Long2ObjectOpenHashMap arrays, Long2IntOpenHashMap columnToTopSection, int minSectionY) { -- super(arrays); -- this.topSections = columnToTopSection; -- columnToTopSection.defaultReturnValue(minSectionY); -+ private final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData; // Paper - avoid copying light data -+ -+ // Paper start - avoid copying light data -+ public SkyDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object arrays, com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int columnToTopSection, int minSectionY, boolean isVisible) { -+ super(arrays, isVisible); -+ this.otherData = columnToTopSection; -+ otherData.queueDefaultReturnValue(minSectionY); -+ // Paper end - this.currentLowestY = minSectionY; - } - - @Override - public SkyLightSectionStorage.SkyDataLayerStorageMap copy() { -- return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.map.clone(), this.topSections.clone(), this.currentLowestY); -+ this.otherData.performUpdatesLockMap(); // Paper - avoid copying light data -+ return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.data, this.otherData, this.currentLowestY, true); // Paper - avoid copying light data - } - } - } diff --git a/patches/server/0452-Support-old-UUID-format-for-NBT.patch b/patches/server/0452-Support-old-UUID-format-for-NBT.patch new file mode 100644 index 0000000000..04f6394d7a --- /dev/null +++ b/patches/server/0452-Support-old-UUID-format-for-NBT.patch @@ -0,0 +1,63 @@ +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/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java +index 0778fdd4f47015787f7ffbfb39c31ec0e1c039bd..912fd5135e89348bdd3c0a8b6c07860ebc106df3 100644 +--- a/src/main/java/net/minecraft/nbt/CompoundTag.java ++++ b/src/main/java/net/minecraft/nbt/CompoundTag.java +@@ -181,6 +181,12 @@ public class CompoundTag implements Tag { + } + + public void putUUID(String key, UUID value) { ++ // Paper start - support old format ++ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ this.tags.remove(key + "Most"); ++ this.tags.remove(key + "Least"); ++ } ++ // Paper end + this.tags.put(key, NbtUtils.createUUID(value)); + } + +@@ -189,10 +195,20 @@ public class CompoundTag implements Tag { + * You must use {@link #hasUUID(String)} before or else it will throw an NPE. + */ + public UUID getUUID(String key) { ++ // Paper start - support old format ++ if (!contains(key, 11) && this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ return new UUID(this.getLong(key + "Most"), this.getLong(key + "Least")); ++ } ++ // Paper end + return NbtUtils.loadUUID(this.get(key)); + } + + public boolean hasUUID(String key) { ++ // Paper start - support old format ++ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { ++ return true; ++ } ++ // Paper end + Tag tag = this.get(key); + return tag != null && tag.getType() == IntArrayTag.TYPE && ((IntArrayTag)tag).getAsIntArray().length == 4; + } +diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index d6d8adf1e49cdb74dc5d8e2e60bcaca7c5e1c16d..b31741a9c3363e4288636ceff9b38c598a84aa43 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -75,6 +75,11 @@ public final class NbtUtils { + if (nbt.contains("Name", 8)) { + string = nbt.getString("Name"); + } ++ // Paper start - support string UUID's ++ if (nbt.contains("Id", 8)) { ++ uUID = UUID.fromString(nbt.getString("Id")); ++ } ++ // Paper end + + if (nbt.hasUUID("Id")) { + uUID = nbt.getUUID("Id"); diff --git a/patches/server/0453-Clean-up-duplicated-GameProfile-Properties.patch b/patches/server/0453-Clean-up-duplicated-GameProfile-Properties.patch new file mode 100644 index 0000000000..25554ddf57 --- /dev/null +++ b/patches/server/0453-Clean-up-duplicated-GameProfile-Properties.patch @@ -0,0 +1,47 @@ +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/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java +index b31741a9c3363e4288636ceff9b38c598a84aa43..46681f3fa63516aa750de11cf1dee17cb3734fcd 100644 +--- a/src/main/java/net/minecraft/nbt/NbtUtils.java ++++ b/src/main/java/net/minecraft/nbt/NbtUtils.java +@@ -93,7 +93,8 @@ public final class NbtUtils { + for(String string2 : compoundTag.getAllKeys()) { + ListTag listTag = compoundTag.getList(string2, 10); + +- for(int i = 0; i < listTag.size(); ++i) { ++ if (listTag.size() == 0) continue; // Paper - remove duplicate properties ++ for (int i = listTag.size() - 1; i < listTag.size(); ++i) { // Paper - remove duplicate properties + CompoundTag compoundTag2 = listTag.getCompound(i); + String string3 = compoundTag2.getString("Value"); + if (compoundTag2.contains("Signature", 8)) { +diff --git a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java +index 2fb1500e9d3202b6377bf4d8e50102a98f409148..72a0c7ad03b18c3156b4f3c7240f7551583f981c 100644 +--- a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java ++++ b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java +@@ -52,6 +52,18 @@ public class PlayerHeadItem extends StandingAndWallBlockItem { + }); + // CraftBukkit start + } else { ++ // Paper start - clean up old duplicated properties ++ CompoundTag properties = nbt.getCompound("SkullOwner").getCompound("Properties"); ++ for (String key : properties.getAllKeys()) { ++ net.minecraft.nbt.ListTag values = properties.getList(key, 10); ++ if (values.size() > 1) { ++ net.minecraft.nbt.Tag texture = values.get(values.size() - 1); ++ values = new net.minecraft.nbt.ListTag(); ++ values.add(texture); ++ properties.put(key, values); ++ } ++ } ++ // Paper end + net.minecraft.nbt.ListTag textures = nbt.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 CompoundTag && !((CompoundTag) textures.get(i)).contains("Signature", 8) && ((CompoundTag) textures.get(i)).getString("Value").trim().isEmpty()) { diff --git a/patches/server/0453-Support-old-UUID-format-for-NBT.patch b/patches/server/0453-Support-old-UUID-format-for-NBT.patch deleted file mode 100644 index 04f6394d7a..0000000000 --- a/patches/server/0453-Support-old-UUID-format-for-NBT.patch +++ /dev/null @@ -1,63 +0,0 @@ -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/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java -index 0778fdd4f47015787f7ffbfb39c31ec0e1c039bd..912fd5135e89348bdd3c0a8b6c07860ebc106df3 100644 ---- a/src/main/java/net/minecraft/nbt/CompoundTag.java -+++ b/src/main/java/net/minecraft/nbt/CompoundTag.java -@@ -181,6 +181,12 @@ public class CompoundTag implements Tag { - } - - public void putUUID(String key, UUID value) { -+ // Paper start - support old format -+ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { -+ this.tags.remove(key + "Most"); -+ this.tags.remove(key + "Least"); -+ } -+ // Paper end - this.tags.put(key, NbtUtils.createUUID(value)); - } - -@@ -189,10 +195,20 @@ public class CompoundTag implements Tag { - * You must use {@link #hasUUID(String)} before or else it will throw an NPE. - */ - public UUID getUUID(String key) { -+ // Paper start - support old format -+ if (!contains(key, 11) && this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { -+ return new UUID(this.getLong(key + "Most"), this.getLong(key + "Least")); -+ } -+ // Paper end - return NbtUtils.loadUUID(this.get(key)); - } - - public boolean hasUUID(String key) { -+ // Paper start - support old format -+ if (this.contains(key + "Most", 99) && this.contains(key + "Least", 99)) { -+ return true; -+ } -+ // Paper end - Tag tag = this.get(key); - return tag != null && tag.getType() == IntArrayTag.TYPE && ((IntArrayTag)tag).getAsIntArray().length == 4; - } -diff --git a/src/main/java/net/minecraft/nbt/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java -index d6d8adf1e49cdb74dc5d8e2e60bcaca7c5e1c16d..b31741a9c3363e4288636ceff9b38c598a84aa43 100644 ---- a/src/main/java/net/minecraft/nbt/NbtUtils.java -+++ b/src/main/java/net/minecraft/nbt/NbtUtils.java -@@ -75,6 +75,11 @@ public final class NbtUtils { - if (nbt.contains("Name", 8)) { - string = nbt.getString("Name"); - } -+ // Paper start - support string UUID's -+ if (nbt.contains("Id", 8)) { -+ uUID = UUID.fromString(nbt.getString("Id")); -+ } -+ // Paper end - - if (nbt.hasUUID("Id")) { - uUID = nbt.getUUID("Id"); diff --git a/patches/server/0454-Clean-up-duplicated-GameProfile-Properties.patch b/patches/server/0454-Clean-up-duplicated-GameProfile-Properties.patch deleted file mode 100644 index 25554ddf57..0000000000 --- a/patches/server/0454-Clean-up-duplicated-GameProfile-Properties.patch +++ /dev/null @@ -1,47 +0,0 @@ -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/NbtUtils.java b/src/main/java/net/minecraft/nbt/NbtUtils.java -index b31741a9c3363e4288636ceff9b38c598a84aa43..46681f3fa63516aa750de11cf1dee17cb3734fcd 100644 ---- a/src/main/java/net/minecraft/nbt/NbtUtils.java -+++ b/src/main/java/net/minecraft/nbt/NbtUtils.java -@@ -93,7 +93,8 @@ public final class NbtUtils { - for(String string2 : compoundTag.getAllKeys()) { - ListTag listTag = compoundTag.getList(string2, 10); - -- for(int i = 0; i < listTag.size(); ++i) { -+ if (listTag.size() == 0) continue; // Paper - remove duplicate properties -+ for (int i = listTag.size() - 1; i < listTag.size(); ++i) { // Paper - remove duplicate properties - CompoundTag compoundTag2 = listTag.getCompound(i); - String string3 = compoundTag2.getString("Value"); - if (compoundTag2.contains("Signature", 8)) { -diff --git a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java -index 2fb1500e9d3202b6377bf4d8e50102a98f409148..72a0c7ad03b18c3156b4f3c7240f7551583f981c 100644 ---- a/src/main/java/net/minecraft/world/item/PlayerHeadItem.java -+++ b/src/main/java/net/minecraft/world/item/PlayerHeadItem.java -@@ -52,6 +52,18 @@ public class PlayerHeadItem extends StandingAndWallBlockItem { - }); - // CraftBukkit start - } else { -+ // Paper start - clean up old duplicated properties -+ CompoundTag properties = nbt.getCompound("SkullOwner").getCompound("Properties"); -+ for (String key : properties.getAllKeys()) { -+ net.minecraft.nbt.ListTag values = properties.getList(key, 10); -+ if (values.size() > 1) { -+ net.minecraft.nbt.Tag texture = values.get(values.size() - 1); -+ values = new net.minecraft.nbt.ListTag(); -+ values.add(texture); -+ properties.put(key, values); -+ } -+ } -+ // Paper end - net.minecraft.nbt.ListTag textures = nbt.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 CompoundTag && !((CompoundTag) textures.get(i)).contains("Signature", 8) && ((CompoundTag) textures.get(i)).getString("Value").trim().isEmpty()) { diff --git a/patches/server/0454-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0454-Convert-legacy-attributes-in-Item-Meta.patch new file mode 100644 index 0000000000..b949a9e873 --- /dev/null +++ b/patches/server/0454-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 0520c45197629cbdc2777d9ae11eef572e793160..46c313d581b9af6aa0a48f97ae3cc800a88535f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -11,6 +11,20 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; + public class CraftAttributeMap implements Attributable { + + private final AttributeMap handle; ++ // Paper start - convert legacy attributes ++ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); ++ ++ public static String convertIfNeeded(String nms) { ++ if (nms == null) { ++ return null; ++ } ++ nms = legacyNMS.getOrDefault(nms, nms); ++ if (!nms.toLowerCase().equals(nms) || nms.indexOf(' ') != -1) { ++ return null; ++ } ++ return nms; ++ } ++ // Paper end + + public CraftAttributeMap(AttributeMap handle) { + this.handle = handle; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index 4304ee35a9bd912c2ae4058febf22f0eea25adbd..4a91da3561d16995e8cfe04ebbc104da009a2503 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -480,7 +480,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/0455-Convert-legacy-attributes-in-Item-Meta.patch b/patches/server/0455-Convert-legacy-attributes-in-Item-Meta.patch deleted file mode 100644 index b949a9e873..0000000000 --- a/patches/server/0455-Convert-legacy-attributes-in-Item-Meta.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jul 2020 04:50:22 -0400 -Subject: [PATCH] Convert legacy attributes in Item Meta - - -diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -index 0520c45197629cbdc2777d9ae11eef572e793160..46c313d581b9af6aa0a48f97ae3cc800a88535f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -11,6 +11,20 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey; - public class CraftAttributeMap implements Attributable { - - private final AttributeMap handle; -+ // Paper start - convert legacy attributes -+ private static final com.google.common.collect.ImmutableMap legacyNMS = com.google.common.collect.ImmutableMap.builder().put("generic.maxHealth", "generic.max_health").put("Max Health", "generic.max_health").put("zombie.spawnReinforcements", "zombie.spawn_reinforcements").put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements").put("horse.jumpStrength", "horse.jump_strength").put("Jump Strength", "horse.jump_strength").put("generic.followRange", "generic.follow_range").put("Follow Range", "generic.follow_range").put("generic.knockbackResistance", "generic.knockback_resistance").put("Knockback Resistance", "generic.knockback_resistance").put("generic.movementSpeed", "generic.movement_speed").put("Movement Speed", "generic.movement_speed").put("generic.flyingSpeed", "generic.flying_speed").put("Flying Speed", "generic.flying_speed").put("generic.attackDamage", "generic.attack_damage").put("generic.attackKnockback", "generic.attack_knockback").put("generic.attackSpeed", "generic.attack_speed").put("generic.armorToughness", "generic.armor_toughness").build(); -+ -+ public static String convertIfNeeded(String nms) { -+ if (nms == null) { -+ return null; -+ } -+ nms = legacyNMS.getOrDefault(nms, nms); -+ if (!nms.toLowerCase().equals(nms) || nms.indexOf(' ') != -1) { -+ return null; -+ } -+ return nms; -+ } -+ // Paper end - - public CraftAttributeMap(AttributeMap handle) { - this.handle = handle; -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index 4304ee35a9bd912c2ae4058febf22f0eea25adbd..4a91da3561d16995e8cfe04ebbc104da009a2503 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -480,7 +480,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/0455-Remove-some-streams-from-structures.patch b/patches/server/0455-Remove-some-streams-from-structures.patch new file mode 100644 index 0000000000..d535ed9e91 --- /dev/null +++ b/patches/server/0455-Remove-some-streams-from-structures.patch @@ -0,0 +1,48 @@ +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/levelgen/Beardifier.java b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java +index 24f58441ae7b43a62d74aa55e9808c1c65f466e4..99e329d374b60ebf77f93551f6bf83b9bca1e4b8 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java +@@ -36,9 +36,10 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + int j = pos.getMinBlockZ(); + ObjectList objectList = new ObjectArrayList<>(10); + ObjectList objectList2 = new ObjectArrayList<>(32); +- world.startsForStructure(pos, (structure) -> { ++ // Paper start - replace for each ++ for (net.minecraft.world.level.levelgen.structure.StructureStart start : world.startsForStructure(pos, (structure) -> { + return structure.terrainAdaptation() != TerrainAdjustment.NONE; +- }).forEach((start) -> { ++ })) { // Paper end + TerrainAdjustment terrainAdjustment = start.getStructure().terrainAdaptation(); + + for(StructurePiece structurePiece : start.getPieces()) { +@@ -51,9 +52,11 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + } + + for(JigsawJunction jigsawJunction : poolElementStructurePiece.getJunctions()) { +- int i = jigsawJunction.getSourceX(); +- int j = jigsawJunction.getSourceZ(); +- if (i > i - 12 && j > j - 12 && i < i + 15 + 12 && j < j + 15 + 12) { ++ // Paper start - decompile fix ++ int i2 = jigsawJunction.getSourceX(); ++ int j2 = jigsawJunction.getSourceZ(); ++ if (i2 > i - 12 && j2 > j - 12 && i2 < i + 15 + 12 && j2 < j + 15 + 12) { ++ // Paper end + objectList2.add(jigsawJunction); + } + } +@@ -63,7 +66,7 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { + } + } + +- }); ++ } // Paper + return new Beardifier(objectList.iterator(), objectList2.iterator()); + } + diff --git a/patches/server/0456-Remove-some-streams-from-structures.patch b/patches/server/0456-Remove-some-streams-from-structures.patch deleted file mode 100644 index d535ed9e91..0000000000 --- a/patches/server/0456-Remove-some-streams-from-structures.patch +++ /dev/null @@ -1,48 +0,0 @@ -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/levelgen/Beardifier.java b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java -index 24f58441ae7b43a62d74aa55e9808c1c65f466e4..99e329d374b60ebf77f93551f6bf83b9bca1e4b8 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/Beardifier.java -@@ -36,9 +36,10 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - int j = pos.getMinBlockZ(); - ObjectList objectList = new ObjectArrayList<>(10); - ObjectList objectList2 = new ObjectArrayList<>(32); -- world.startsForStructure(pos, (structure) -> { -+ // Paper start - replace for each -+ for (net.minecraft.world.level.levelgen.structure.StructureStart start : world.startsForStructure(pos, (structure) -> { - return structure.terrainAdaptation() != TerrainAdjustment.NONE; -- }).forEach((start) -> { -+ })) { // Paper end - TerrainAdjustment terrainAdjustment = start.getStructure().terrainAdaptation(); - - for(StructurePiece structurePiece : start.getPieces()) { -@@ -51,9 +52,11 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - } - - for(JigsawJunction jigsawJunction : poolElementStructurePiece.getJunctions()) { -- int i = jigsawJunction.getSourceX(); -- int j = jigsawJunction.getSourceZ(); -- if (i > i - 12 && j > j - 12 && i < i + 15 + 12 && j < j + 15 + 12) { -+ // Paper start - decompile fix -+ int i2 = jigsawJunction.getSourceX(); -+ int j2 = jigsawJunction.getSourceZ(); -+ if (i2 > i - 12 && j2 > j - 12 && i2 < i + 15 + 12 && j2 < j + 15 + 12) { -+ // Paper end - objectList2.add(jigsawJunction); - } - } -@@ -63,7 +66,7 @@ public class Beardifier implements DensityFunctions.BeardifierOrMarker { - } - } - -- }); -+ } // Paper - return new Beardifier(objectList.iterator(), objectList2.iterator()); - } - diff --git a/patches/server/0456-Remove-streams-from-classes-related-villager-gossip.patch b/patches/server/0456-Remove-streams-from-classes-related-villager-gossip.patch new file mode 100644 index 0000000000..b70ae30d26 --- /dev/null +++ b/patches/server/0456-Remove-streams-from-classes-related-villager-gossip.patch @@ -0,0 +1,71 @@ +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/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +index 0de65462956fa734b6405614e047161696e596fb..aa277479f5552503a202a057b1a3ede379f2bbbf 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java ++++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java +@@ -58,8 +58,21 @@ public class GossipContainer { + }); + } + ++ // Paper start - Remove streams from reputation ++ private List decompress() { ++ List list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ for (Map.Entry entry : getReputations().entrySet()) { ++ for (GossipContainer.GossipEntry cur : entry.getValue().decompress(entry.getKey())) { ++ if (cur.weightedValue() != 0) ++ list.add(cur); ++ } ++ } ++ return list; ++ } ++ // Paper end ++ + private Collection selectGossipsForTransfer(RandomSource random, int count) { +- List list = this.unpack().collect(Collectors.toList()); ++ List list = this.decompress(); // Paper - Remove streams from reputation + if (list.isEmpty()) { + return Collections.emptyList(); + } else { +@@ -153,7 +166,7 @@ public class GossipContainer { + } + + public Dynamic store(DynamicOps dynamicOps) { +- return new Dynamic<>(dynamicOps, dynamicOps.createList(this.unpack().map((gossipEntry) -> { ++ return new Dynamic<>(dynamicOps, dynamicOps.createList(this.decompress().stream().map((gossipEntry) -> { + return gossipEntry.store(dynamicOps); + }).map(Dynamic::getValue))); + } +@@ -179,11 +192,23 @@ public class GossipContainer { + final Object2IntMap entries = new Object2IntOpenHashMap<>(); + + public int weightedValue(Predicate gossipTypeFilter) { +- return this.entries.object2IntEntrySet().stream().filter((entry) -> { +- return gossipTypeFilter.test(entry.getKey()); +- }).mapToInt((entry) -> { +- return entry.getIntValue() * (entry.getKey()).weight; +- }).sum(); ++ // Paper start - Remove streams from reputation ++ int weight = 0; ++ for (Object2IntMap.Entry entry : entries.object2IntEntrySet()) { ++ if (gossipTypeFilter.test(entry.getKey())) { ++ weight += entry.getIntValue() * entry.getKey().weight; ++ } ++ } ++ return weight; ++ } ++ ++ public List decompress(UUID uuid) { ++ List list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ for (Object2IntMap.Entry entry : entries.object2IntEntrySet()) { ++ list.add(new GossipContainer.GossipEntry(uuid, entry.getKey(), entry.getIntValue())); ++ } ++ return list; ++ // Paper - end + } + + public Stream unpack(UUID target) { diff --git a/patches/server/0457-Remove-streams-from-classes-related-villager-gossip.patch b/patches/server/0457-Remove-streams-from-classes-related-villager-gossip.patch deleted file mode 100644 index b70ae30d26..0000000000 --- a/patches/server/0457-Remove-streams-from-classes-related-villager-gossip.patch +++ /dev/null @@ -1,71 +0,0 @@ -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/GossipContainer.java b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -index 0de65462956fa734b6405614e047161696e596fb..aa277479f5552503a202a057b1a3ede379f2bbbf 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -+++ b/src/main/java/net/minecraft/world/entity/ai/gossip/GossipContainer.java -@@ -58,8 +58,21 @@ public class GossipContainer { - }); - } - -+ // Paper start - Remove streams from reputation -+ private List decompress() { -+ List list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ for (Map.Entry entry : getReputations().entrySet()) { -+ for (GossipContainer.GossipEntry cur : entry.getValue().decompress(entry.getKey())) { -+ if (cur.weightedValue() != 0) -+ list.add(cur); -+ } -+ } -+ return list; -+ } -+ // Paper end -+ - private Collection selectGossipsForTransfer(RandomSource random, int count) { -- List list = this.unpack().collect(Collectors.toList()); -+ List list = this.decompress(); // Paper - Remove streams from reputation - if (list.isEmpty()) { - return Collections.emptyList(); - } else { -@@ -153,7 +166,7 @@ public class GossipContainer { - } - - public Dynamic store(DynamicOps dynamicOps) { -- return new Dynamic<>(dynamicOps, dynamicOps.createList(this.unpack().map((gossipEntry) -> { -+ return new Dynamic<>(dynamicOps, dynamicOps.createList(this.decompress().stream().map((gossipEntry) -> { - return gossipEntry.store(dynamicOps); - }).map(Dynamic::getValue))); - } -@@ -179,11 +192,23 @@ public class GossipContainer { - final Object2IntMap entries = new Object2IntOpenHashMap<>(); - - public int weightedValue(Predicate gossipTypeFilter) { -- return this.entries.object2IntEntrySet().stream().filter((entry) -> { -- return gossipTypeFilter.test(entry.getKey()); -- }).mapToInt((entry) -> { -- return entry.getIntValue() * (entry.getKey()).weight; -- }).sum(); -+ // Paper start - Remove streams from reputation -+ int weight = 0; -+ for (Object2IntMap.Entry entry : entries.object2IntEntrySet()) { -+ if (gossipTypeFilter.test(entry.getKey())) { -+ weight += entry.getIntValue() * entry.getKey().weight; -+ } -+ } -+ return weight; -+ } -+ -+ public List decompress(UUID uuid) { -+ List list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ for (Object2IntMap.Entry entry : entries.object2IntEntrySet()) { -+ list.add(new GossipContainer.GossipEntry(uuid, entry.getKey(), entry.getIntValue())); -+ } -+ return list; -+ // Paper - end - } - - public Stream unpack(UUID target) { diff --git a/patches/server/0457-Support-components-in-ItemMeta.patch b/patches/server/0457-Support-components-in-ItemMeta.patch new file mode 100644 index 0000000000..05ab3c1898 --- /dev/null +++ b/patches/server/0457-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 4a91da3561d16995e8cfe04ebbc104da009a2503..c475ddea1c995df1dfcaf4f491f341761a5f8802 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -874,11 +874,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 this.displayName != null; +@@ -1021,6 +1033,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()) { +@@ -1035,6 +1055,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 this.customModelData != null; +@@ -1502,6 +1537,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/0458-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch b/patches/server/0458-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch new file mode 100644 index 0000000000..68a10afd43 --- /dev/null +++ b/patches/server/0458-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/StopAttackingIfTargetInvalid.java b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +index 44d3c9da39389b72bfc5ee39c1abb6baf9dccdb1..565691aaed71de3efe15dd751fbbbe7849ef56b7 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java +@@ -56,15 +56,15 @@ public class StopAttackingIfTargetInvalid extends Behavior { + LivingEntity entityliving = this.getAttackTarget(entity); + + if (!entity.canAttack(entityliving)) { +- this.clearAttackTarget(entity); ++ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } else if (this.canGrowTiredOfTryingToReachTarget && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entity)) { +- this.clearAttackTarget(entity); ++ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET); // Paper + } else if (this.isCurrentTargetDeadOrRemoved(entity)) { +- this.clearAttackTarget(entity); ++ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED); // Paper + } else if (this.isCurrentTargetInDifferentLevel(entity)) { +- this.clearAttackTarget(entity); ++ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL); // Paper + } else if (this.stopAttackingWhen.test(this.getAttackTarget(entity))) { +- this.clearAttackTarget(entity); ++ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper + } + } + +@@ -88,17 +88,20 @@ public class StopAttackingIfTargetInvalid extends Behavior { + return optional.isPresent() && !((LivingEntity) optional.get()).isAlive(); + } + +- protected void clearAttackTarget(E entity) { ++ protected void clearAttackTarget(E entity, EntityTargetEvent.TargetReason reason) { + // CraftBukkit start +- LivingEntity old = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); +- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); ++ // Paper start - fix this event ++ // LivingEntity old = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); ++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, null, reason); + if (event.isCancelled()) { + return; + } +- if (event.getTarget() != null) { ++ // comment out, bad logic - bad ++ /*if (event.getTarget() != null) { + entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); + return; +- } ++ }*/ ++ // Paper end + // CraftBukkit end + this.onTargetErased.accept(entity, this.getAttackTarget(entity)); + entity.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET); diff --git a/patches/server/0458-Support-components-in-ItemMeta.patch b/patches/server/0458-Support-components-in-ItemMeta.patch deleted file mode 100644 index 05ab3c1898..0000000000 --- a/patches/server/0458-Support-components-in-ItemMeta.patch +++ /dev/null @@ -1,83 +0,0 @@ -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 4a91da3561d16995e8cfe04ebbc104da009a2503..c475ddea1c995df1dfcaf4f491f341761a5f8802 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -874,11 +874,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 this.displayName != null; -@@ -1021,6 +1033,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()) { -@@ -1035,6 +1055,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 this.customModelData != null; -@@ -1502,6 +1537,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/0459-Add-entity-liquid-API.patch b/patches/server/0459-Add-entity-liquid-API.patch new file mode 100644 index 0000000000..57a97d8edd --- /dev/null +++ b/patches/server/0459-Add-entity-liquid-API.patch @@ -0,0 +1,40 @@ +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/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 3bda8128c2956d817677e28ff87c9c5ed61c8bd2..c2282592a3e5c8e08acb30a8fe6f3a83dfe6d93d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1271,5 +1271,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().isInWaterOrBubble(); ++ } ++ ++ public boolean isInWaterOrRainOrBubbleColumn() { ++ return getHandle().isInWaterRainOrBubble(); ++ } ++ ++ public boolean isInLava() { ++ return getHandle().isInLava(); ++ } + // Paper end + } diff --git a/patches/server/0459-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch b/patches/server/0459-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch deleted file mode 100644 index 68a10afd43..0000000000 --- a/patches/server/0459-Improve-EntityTargetLivingEntityEvent-for-1.16-mobs.patch +++ /dev/null @@ -1,59 +0,0 @@ -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/StopAttackingIfTargetInvalid.java b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -index 44d3c9da39389b72bfc5ee39c1abb6baf9dccdb1..565691aaed71de3efe15dd751fbbbe7849ef56b7 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java -@@ -56,15 +56,15 @@ public class StopAttackingIfTargetInvalid extends Behavior { - LivingEntity entityliving = this.getAttackTarget(entity); - - if (!entity.canAttack(entityliving)) { -- this.clearAttackTarget(entity); -+ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper - } else if (this.canGrowTiredOfTryingToReachTarget && StopAttackingIfTargetInvalid.isTiredOfTryingToReachTarget(entity)) { -- this.clearAttackTarget(entity); -+ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET); // Paper - } else if (this.isCurrentTargetDeadOrRemoved(entity)) { -- this.clearAttackTarget(entity); -+ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_DIED); // Paper - } else if (this.isCurrentTargetInDifferentLevel(entity)) { -- this.clearAttackTarget(entity); -+ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_OTHER_LEVEL); // Paper - } else if (this.stopAttackingWhen.test(this.getAttackTarget(entity))) { -- this.clearAttackTarget(entity); -+ this.clearAttackTarget(entity, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_INVALID); // Paper - } - } - -@@ -88,17 +88,20 @@ public class StopAttackingIfTargetInvalid extends Behavior { - return optional.isPresent() && !((LivingEntity) optional.get()).isAlive(); - } - -- protected void clearAttackTarget(E entity) { -+ protected void clearAttackTarget(E entity, EntityTargetEvent.TargetReason reason) { - // CraftBukkit start -- LivingEntity old = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); -- EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET); -+ // Paper start - fix this event -+ // LivingEntity old = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null); -+ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, null, reason); - if (event.isCancelled()) { - return; - } -- if (event.getTarget() != null) { -+ // comment out, bad logic - bad -+ /*if (event.getTarget() != null) { - entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, ((CraftLivingEntity) event.getTarget()).getHandle()); - return; -- } -+ }*/ -+ // Paper end - // CraftBukkit end - this.onTargetErased.accept(entity, this.getAttackTarget(entity)); - entity.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET); diff --git a/patches/server/0460-Add-entity-liquid-API.patch b/patches/server/0460-Add-entity-liquid-API.patch deleted file mode 100644 index 0f1072e587..0000000000 --- a/patches/server/0460-Add-entity-liquid-API.patch +++ /dev/null @@ -1,40 +0,0 @@ -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/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index cd958bc3c00f53ebaf9b3ae39564d3abb6c819a1..863cd2c84dd207f984ddad977e9fd23860247c68 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1254,5 +1254,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().isInWaterOrBubble(); -+ } -+ -+ public boolean isInWaterOrRainOrBubbleColumn() { -+ return getHandle().isInWaterRainOrBubble(); -+ } -+ -+ public boolean isInLava() { -+ return getHandle().isInLava(); -+ } - // Paper end - } diff --git a/patches/server/0460-Update-itemstack-legacy-name-and-lore.patch b/patches/server/0460-Update-itemstack-legacy-name-and-lore.patch new file mode 100644 index 0000000000..b354241c07 --- /dev/null +++ b/patches/server/0460-Update-itemstack-legacy-name-and-lore.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Wed, 1 Jul 2020 11:57:40 -0500 +Subject: [PATCH] Update itemstack legacy name and lore + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 5e0852c4656813272a7ee6cb9c2331410c1b7739..cbcc90cffe38ea249cd0de4b0a90adc2a3ddeb0b 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -166,6 +166,44 @@ public final class ItemStack { + list.sort((java.util.Comparator) enchantSorter); // Paper + } catch (Exception ignored) {} + } ++ ++ private void processText() { ++ CompoundTag display = getTagElement("display"); ++ if (display != null) { ++ if (display.contains("Name", 8)) { ++ String json = display.getString("Name"); ++ if (json != null && json.contains("\u00A7")) { ++ try { ++ display.put("Name", convert(json)); ++ } catch (com.google.gson.JsonParseException jsonparseexception) { ++ display.remove("Name"); ++ } ++ } ++ } ++ if (display.contains("Lore", 9)) { ++ ListTag 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 (com.google.gson.JsonParseException e) { ++ list.set(index, net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(net.minecraft.network.chat.Component.literal("")))); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ private net.minecraft.nbt.StringTag convert(String json) { ++ Component component = Component.Serializer.fromJson(json); ++ if (component.getContents() instanceof net.minecraft.network.chat.contents.LiteralContents literalContents && literalContents.text().contains("\u00A7") && component.getSiblings().isEmpty()) { ++ // Only convert if the root component is a single comp with legacy in it, don't convert already normal components ++ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(literalContents.text())[0]; ++ } ++ return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); ++ } + // Paper end + + public ItemStack(ItemLike item) { +@@ -220,6 +258,7 @@ public final class ItemStack { + this.tag = nbttagcompound.getCompound("tag").copy(); + // CraftBukkit end + this.processEnchantOrder(this.tag); // Paper ++ this.processText(); // Paper + this.getItem().verifyTagAfterLoad(this.tag); + } + diff --git a/patches/server/0461-Spawn-player-in-correct-world-on-login.patch b/patches/server/0461-Spawn-player-in-correct-world-on-login.patch new file mode 100644 index 0000000000..a59a3e8562 --- /dev/null +++ b/patches/server/0461-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 1850ce4566e6c5d19140cbf2636b3573f16c4239..1cb6900489ad25ddd40a3b8c39f436c03a72b3dc 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -203,7 +203,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.contains("WorldUUIDMost") && nbttagcompound.contains("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().dimension(); ++ } else { ++ resourcekey = Level.OVERWORLD; ++ } ++ } else if (nbttagcompound != null) { ++ // Vanilla migration support ++ // Paper end + DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error + Logger logger = PlayerList.LOGGER; + diff --git a/patches/server/0461-Update-itemstack-legacy-name-and-lore.patch b/patches/server/0461-Update-itemstack-legacy-name-and-lore.patch deleted file mode 100644 index b354241c07..0000000000 --- a/patches/server/0461-Update-itemstack-legacy-name-and-lore.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Wed, 1 Jul 2020 11:57:40 -0500 -Subject: [PATCH] Update itemstack legacy name and lore - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 5e0852c4656813272a7ee6cb9c2331410c1b7739..cbcc90cffe38ea249cd0de4b0a90adc2a3ddeb0b 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -166,6 +166,44 @@ public final class ItemStack { - list.sort((java.util.Comparator) enchantSorter); // Paper - } catch (Exception ignored) {} - } -+ -+ private void processText() { -+ CompoundTag display = getTagElement("display"); -+ if (display != null) { -+ if (display.contains("Name", 8)) { -+ String json = display.getString("Name"); -+ if (json != null && json.contains("\u00A7")) { -+ try { -+ display.put("Name", convert(json)); -+ } catch (com.google.gson.JsonParseException jsonparseexception) { -+ display.remove("Name"); -+ } -+ } -+ } -+ if (display.contains("Lore", 9)) { -+ ListTag 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 (com.google.gson.JsonParseException e) { -+ list.set(index, net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(net.minecraft.network.chat.Component.literal("")))); -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ private net.minecraft.nbt.StringTag convert(String json) { -+ Component component = Component.Serializer.fromJson(json); -+ if (component.getContents() instanceof net.minecraft.network.chat.contents.LiteralContents literalContents && literalContents.text().contains("\u00A7") && component.getSiblings().isEmpty()) { -+ // Only convert if the root component is a single comp with legacy in it, don't convert already normal components -+ component = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(literalContents.text())[0]; -+ } -+ return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.util.CraftChatMessage.toJSON(component)); -+ } - // Paper end - - public ItemStack(ItemLike item) { -@@ -220,6 +258,7 @@ public final class ItemStack { - this.tag = nbttagcompound.getCompound("tag").copy(); - // CraftBukkit end - this.processEnchantOrder(this.tag); // Paper -+ this.processText(); // Paper - this.getItem().verifyTagAfterLoad(this.tag); - } - diff --git a/patches/server/0462-Add-PrepareResultEvent.patch b/patches/server/0462-Add-PrepareResultEvent.patch new file mode 100644 index 0000000000..415d9409c8 --- /dev/null +++ b/patches/server/0462-Add-PrepareResultEvent.patch @@ -0,0 +1,152 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Fri, 3 Jul 2020 11:58:56 -0500 +Subject: [PATCH] Add PrepareResultEvent + +Adds a new event for all crafting stations that generate a result slot item + +Anvil, Grindstone and Smithing now extend this event + +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index eb22059fe008c3d3fc0364a7f85f91b4cca8b328..506d758efbf16da9467f120321d2359a8832e477 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -316,6 +316,7 @@ public class AnvilMenu extends ItemCombinerMenu { + } + + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + public int getCost() { +diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +index fc2b400c58ddbd7b012707c61d9a37363d351251..4f5593d387545545e30475d3edaa92a4306ba96b 100644 +--- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java +@@ -150,6 +150,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { + this.setupResultSlot(itemstack, itemstack1, itemstack2); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { +diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index aa0ba9c7dcb0ee81c9081031c447eeab61d92a10..7a0c38c743ef02f5b9c052f88c2d6429a53b8286 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -159,6 +159,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.repairSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index f5e52220abc5c678c090b32d83eb9644fa91ce9d..35575434f3c90f1bd23df6584ee8a5a991f93f9f 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -78,6 +78,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + super.slotsChanged(inventory); + if (inventory == this.inputSlots) { + this.createResult(); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index d028ad0ab0e800b0fd93362d21942dff392f24af..942b4cc710bede4c942d269dcfc14ae105ab848d 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -248,7 +248,8 @@ public class LoomMenu extends AbstractContainerMenu { + this.resultSlot.set(ItemStack.EMPTY); + } + +- this.broadcastChanges(); ++ // this.broadcastChanges(); // Paper - done below ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper + } else { + this.resultSlot.set(ItemStack.EMPTY); + this.selectablePatterns = List.of(); +diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +index 92dd2ea23185bba311e184b2ac9744a423c102ea..71bb09e3d31f098503d0e1bdf073b60f07d76ed0 100644 +--- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +@@ -76,6 +76,7 @@ public class SmithingMenu extends ItemCombinerMenu { + // CraftBukkit end + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index cdebd0cdf6eb901464cf4c16089b10ea0147b54d..b47dc7671fab2117b989d647d7e8e36d12af5f76 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -176,6 +176,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + this.setupRecipeList(inventory, itemstack); + } + ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper + } + + private void setupRecipeList(Container input, ItemStack stack) { +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 9b94df9940040f51fdcc1af5c7da96117af9017e..c2eefe215f47e36cc2b8476750ae00ec88f826a6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1594,19 +1594,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(AbstractContainerMenu 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.broadcastChanges();; ++ } ++ // Paper end + + /** + * Mob spawner event. diff --git a/patches/server/0462-Spawn-player-in-correct-world-on-login.patch b/patches/server/0462-Spawn-player-in-correct-world-on-login.patch deleted file mode 100644 index a59a3e8562..0000000000 --- a/patches/server/0462-Spawn-player-in-correct-world-on-login.patch +++ /dev/null @@ -1,30 +0,0 @@ -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 1850ce4566e6c5d19140cbf2636b3573f16c4239..1cb6900489ad25ddd40a3b8c39f436c03a72b3dc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -203,7 +203,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.contains("WorldUUIDMost") && nbttagcompound.contains("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().dimension(); -+ } else { -+ resourcekey = Level.OVERWORLD; -+ } -+ } else if (nbttagcompound != null) { -+ // Vanilla migration support -+ // Paper end - DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error - Logger logger = PlayerList.LOGGER; - diff --git a/patches/server/0463-Add-PrepareResultEvent.patch b/patches/server/0463-Add-PrepareResultEvent.patch deleted file mode 100644 index 415d9409c8..0000000000 --- a/patches/server/0463-Add-PrepareResultEvent.patch +++ /dev/null @@ -1,152 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Fri, 3 Jul 2020 11:58:56 -0500 -Subject: [PATCH] Add PrepareResultEvent - -Adds a new event for all crafting stations that generate a result slot item - -Anvil, Grindstone and Smithing now extend this event - -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index eb22059fe008c3d3fc0364a7f85f91b4cca8b328..506d758efbf16da9467f120321d2359a8832e477 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -316,6 +316,7 @@ public class AnvilMenu extends ItemCombinerMenu { - } - - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - public int getCost() { -diff --git a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -index fc2b400c58ddbd7b012707c61d9a37363d351251..4f5593d387545545e30475d3edaa92a4306ba96b 100644 ---- a/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/CartographyTableMenu.java -@@ -150,6 +150,7 @@ public class CartographyTableMenu extends AbstractContainerMenu { - this.setupResultSlot(itemstack, itemstack1, itemstack2); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - private void setupResultSlot(ItemStack map, ItemStack item, ItemStack oldResult) { -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index aa0ba9c7dcb0ee81c9081031c447eeab61d92a10..7a0c38c743ef02f5b9c052f88c2d6429a53b8286 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -159,6 +159,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.repairSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index f5e52220abc5c678c090b32d83eb9644fa91ce9d..35575434f3c90f1bd23df6584ee8a5a991f93f9f 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -78,6 +78,7 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - super.slotsChanged(inventory); - if (inventory == this.inputSlots) { - this.createResult(); -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index d028ad0ab0e800b0fd93362d21942dff392f24af..942b4cc710bede4c942d269dcfc14ae105ab848d 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -248,7 +248,8 @@ public class LoomMenu extends AbstractContainerMenu { - this.resultSlot.set(ItemStack.EMPTY); - } - -- this.broadcastChanges(); -+ // this.broadcastChanges(); // Paper - done below -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - } else { - this.resultSlot.set(ItemStack.EMPTY); - this.selectablePatterns = List.of(); -diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -index 92dd2ea23185bba311e184b2ac9744a423c102ea..71bb09e3d31f098503d0e1bdf073b60f07d76ed0 100644 ---- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java -@@ -76,6 +76,7 @@ public class SmithingMenu extends ItemCombinerMenu { - // CraftBukkit end - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - @Override -diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index cdebd0cdf6eb901464cf4c16089b10ea0147b54d..b47dc7671fab2117b989d647d7e8e36d12af5f76 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -176,6 +176,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - this.setupRecipeList(inventory, itemstack); - } - -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper - } - - private void setupRecipeList(Container input, ItemStack stack) { -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9b94df9940040f51fdcc1af5c7da96117af9017e..c2eefe215f47e36cc2b8476750ae00ec88f826a6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1594,19 +1594,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(AbstractContainerMenu 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.broadcastChanges();; -+ } -+ // Paper end - - /** - * Mob spawner event. diff --git a/patches/server/0463-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server/0463-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch new file mode 100644 index 0000000000..0319fe7f46 --- /dev/null +++ b/patches/server/0463-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 5 Jul 2020 14:59:31 -0400 +Subject: [PATCH] Don't check chunk for portal on world gen entity add + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 37d51104a7d38c2d16ae38a9adcbe37597c94fe2..049d4422136a342556951dc0114435f6c2ede946 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3435,7 +3435,7 @@ public abstract class LivingEntity extends Entity { + Entity entity = this.getVehicle(); + + super.stopRiding(suppressCancellation); // Paper - suppress +- if (entity != null && entity != this.getVehicle() && !this.level.isClientSide) { ++ if (entity != null && entity != this.getVehicle() && !this.level.isClientSide && entity.valid) { // Paper - don't process on world gen + this.dismountVehicle(entity); + } + diff --git a/patches/server/0464-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch b/patches/server/0464-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch deleted file mode 100644 index 110614ac67..0000000000 --- a/patches/server/0464-Don-t-check-chunk-for-portal-on-world-gen-entity-add.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sun, 5 Jul 2020 14:59:31 -0400 -Subject: [PATCH] Don't check chunk for portal on world gen entity add - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 4a2a8566c9d68f21a98774fcecac0f4fa43d88c4..09513bc7a5b78580da415d486369b9403e99c773 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3413,7 +3413,7 @@ public abstract class LivingEntity extends Entity { - Entity entity = this.getVehicle(); - - super.stopRiding(suppressCancellation); // Paper - suppress -- if (entity != null && entity != this.getVehicle() && !this.level.isClientSide) { -+ if (entity != null && entity != this.getVehicle() && !this.level.isClientSide && entity.valid) { // Paper - don't process on world gen - this.dismountVehicle(entity); - } - diff --git a/patches/server/0464-Optimize-NetworkManager-Exception-Handling.patch b/patches/server/0464-Optimize-NetworkManager-Exception-Handling.patch new file mode 100644 index 0000000000..cf2905d1ed --- /dev/null +++ b/patches/server/0464-Optimize-NetworkManager-Exception-Handling.patch @@ -0,0 +1,67 @@ +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/ConnectionProtocol.java b/src/main/java/net/minecraft/network/ConnectionProtocol.java +index 672e296cec289abd3bf797d84e16983ca50907be..aec921477e035095d569eab3335175b93a65e672 100644 +--- a/src/main/java/net/minecraft/network/ConnectionProtocol.java ++++ b/src/main/java/net/minecraft/network/ConnectionProtocol.java +@@ -302,6 +302,7 @@ public enum ConnectionProtocol { + + @Nullable + public Packet createPacket(int id, FriendlyByteBuf buf) { ++ if (id < 0 || id >= this.idToDeserializer.size()) return null; // Paper + Function> function = this.idToDeserializer.get(id); + return function != null ? function.apply(buf) : null; + } +diff --git a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java +index 99b581052f937b0f2d6b5d73de699008c1d51774..ed54479b14dcfc736ac90749106557f0ff537550 100644 +--- a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java ++++ b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java +@@ -8,8 +8,20 @@ import io.netty.handler.codec.CorruptedFrameException; + import java.util.List; + + public class Varint21FrameDecoder extends ByteToMessageDecoder { ++ private final byte[] lenBuf = new byte[3]; // Paper ++ @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) { ++ // Paper start - if channel is not active just discard the packet ++ if (!channelHandlerContext.channel().isActive()) { ++ byteBuf.skipBytes(byteBuf.readableBytes()); ++ return; ++ } ++ // Paper end + byteBuf.markReaderIndex(); ++ // Paper start - reuse temporary length buffer ++ byte[] abyte = lenBuf; ++ java.util.Arrays.fill(abyte, (byte) 0); ++ // Paper end + byte[] bs = new byte[3]; + + for(int i = 0; i < bs.length; ++i) { +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index 049e64c355d5f064009b1107ad15d28c44f999dd..acfa1907bfc9c29d261cfccc00d65bad9ad1a002 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -30,11 +30,15 @@ public class PacketUtils { + try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings + packet.handle(listener); + } catch (Exception exception) { +- if (listener.shouldPropagateHandlingExceptions()) { +- throw exception; ++ net.minecraft.network.Connection networkmanager = listener.getConnection(); ++ if (networkmanager.getPlayer() != null) { ++ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception); ++ } else { ++ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception); + } +- +- PacketUtils.LOGGER.error("Failed to handle packet {}, suppressing error", packet, exception); ++ net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error"); ++ networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error))); ++ networkmanager.setReadOnly(); + } + } else { + PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); diff --git a/patches/server/0465-Optimize-NetworkManager-Exception-Handling.patch b/patches/server/0465-Optimize-NetworkManager-Exception-Handling.patch deleted file mode 100644 index cf2905d1ed..0000000000 --- a/patches/server/0465-Optimize-NetworkManager-Exception-Handling.patch +++ /dev/null @@ -1,67 +0,0 @@ -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/ConnectionProtocol.java b/src/main/java/net/minecraft/network/ConnectionProtocol.java -index 672e296cec289abd3bf797d84e16983ca50907be..aec921477e035095d569eab3335175b93a65e672 100644 ---- a/src/main/java/net/minecraft/network/ConnectionProtocol.java -+++ b/src/main/java/net/minecraft/network/ConnectionProtocol.java -@@ -302,6 +302,7 @@ public enum ConnectionProtocol { - - @Nullable - public Packet createPacket(int id, FriendlyByteBuf buf) { -+ if (id < 0 || id >= this.idToDeserializer.size()) return null; // Paper - Function> function = this.idToDeserializer.get(id); - return function != null ? function.apply(buf) : null; - } -diff --git a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java -index 99b581052f937b0f2d6b5d73de699008c1d51774..ed54479b14dcfc736ac90749106557f0ff537550 100644 ---- a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java -+++ b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java -@@ -8,8 +8,20 @@ import io.netty.handler.codec.CorruptedFrameException; - import java.util.List; - - public class Varint21FrameDecoder extends ByteToMessageDecoder { -+ private final byte[] lenBuf = new byte[3]; // Paper -+ @Override - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) { -+ // Paper start - if channel is not active just discard the packet -+ if (!channelHandlerContext.channel().isActive()) { -+ byteBuf.skipBytes(byteBuf.readableBytes()); -+ return; -+ } -+ // Paper end - byteBuf.markReaderIndex(); -+ // Paper start - reuse temporary length buffer -+ byte[] abyte = lenBuf; -+ java.util.Arrays.fill(abyte, (byte) 0); -+ // Paper end - byte[] bs = new byte[3]; - - for(int i = 0; i < bs.length; ++i) { -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index 049e64c355d5f064009b1107ad15d28c44f999dd..acfa1907bfc9c29d261cfccc00d65bad9ad1a002 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -30,11 +30,15 @@ public class PacketUtils { - try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings - packet.handle(listener); - } catch (Exception exception) { -- if (listener.shouldPropagateHandlingExceptions()) { -- throw exception; -+ net.minecraft.network.Connection networkmanager = listener.getConnection(); -+ if (networkmanager.getPlayer() != null) { -+ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception); -+ } else { -+ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception); - } -- -- PacketUtils.LOGGER.error("Failed to handle packet {}, suppressing error", packet, exception); -+ net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error"); -+ networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error))); -+ networkmanager.setReadOnly(); - } - } else { - PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); diff --git a/patches/server/0465-Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/server/0465-Optimize-the-advancement-data-player-iteration-to-be.patch new file mode 100644 index 0000000000..0763953dd5 --- /dev/null +++ b/patches/server/0465-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/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 3f7f6a43ac0bf2efb0e66dc96438febbd92113e9..0c2f12e7930646a3da53a50f38be62e0cb1ed2b7 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -435,6 +435,16 @@ public class PlayerAdvancements { + } + + private void ensureVisibility(Advancement advancement) { ++ // Paper start ++ ensureVisibility(advancement, IterationEntryPoint.ROOT); ++ } ++ private enum IterationEntryPoint { ++ ROOT, ++ ITERATOR, ++ PARENT_OF_ITERATOR ++ } ++ private void ensureVisibility(Advancement advancement, IterationEntryPoint entryPoint) { ++ // Paper end + boolean flag = this.shouldBeVisible(advancement); + boolean flag1 = this.visible.contains(advancement); + +@@ -450,15 +460,23 @@ public class PlayerAdvancements { + } + + if (flag != flag1 && advancement.getParent() != null) { +- this.ensureVisibility(advancement.getParent()); ++ // 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.ensureVisibility(advancement.getParent(), 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.getChildren().iterator(); + + while (iterator.hasNext()) { + Advancement advancement1 = (Advancement) iterator.next(); + +- this.ensureVisibility(advancement1); ++ this.ensureVisibility(advancement1, IterationEntryPoint.ITERATOR); // Paper - Mark this call as being from iteration + } + + } diff --git a/patches/server/0466-Fix-arrows-never-despawning-MC-125757.patch b/patches/server/0466-Fix-arrows-never-despawning-MC-125757.patch new file mode 100644 index 0000000000..8bdc8589fd --- /dev/null +++ b/patches/server/0466-Fix-arrows-never-despawning-MC-125757.patch @@ -0,0 +1,22 @@ +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/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 7f1f4813ac007fbf79e8ba254075c015fe15e3a1..f02fb03c63975e5c1ccdd848f5727559929cce00 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -199,6 +199,7 @@ public abstract class AbstractArrow extends Projectile { + + ++this.inGroundTime; + } else { ++ if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds + this.inGroundTime = 0; + Vec3 vec3d2 = this.position(); + diff --git a/patches/server/0466-Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/server/0466-Optimize-the-advancement-data-player-iteration-to-be.patch deleted file mode 100644 index 0763953dd5..0000000000 --- a/patches/server/0466-Optimize-the-advancement-data-player-iteration-to-be.patch +++ /dev/null @@ -1,54 +0,0 @@ -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/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 3f7f6a43ac0bf2efb0e66dc96438febbd92113e9..0c2f12e7930646a3da53a50f38be62e0cb1ed2b7 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -435,6 +435,16 @@ public class PlayerAdvancements { - } - - private void ensureVisibility(Advancement advancement) { -+ // Paper start -+ ensureVisibility(advancement, IterationEntryPoint.ROOT); -+ } -+ private enum IterationEntryPoint { -+ ROOT, -+ ITERATOR, -+ PARENT_OF_ITERATOR -+ } -+ private void ensureVisibility(Advancement advancement, IterationEntryPoint entryPoint) { -+ // Paper end - boolean flag = this.shouldBeVisible(advancement); - boolean flag1 = this.visible.contains(advancement); - -@@ -450,15 +460,23 @@ public class PlayerAdvancements { - } - - if (flag != flag1 && advancement.getParent() != null) { -- this.ensureVisibility(advancement.getParent()); -+ // 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.ensureVisibility(advancement.getParent(), 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.getChildren().iterator(); - - while (iterator.hasNext()) { - Advancement advancement1 = (Advancement) iterator.next(); - -- this.ensureVisibility(advancement1); -+ this.ensureVisibility(advancement1, IterationEntryPoint.ITERATOR); // Paper - Mark this call as being from iteration - } - - } diff --git a/patches/server/0467-Fix-arrows-never-despawning-MC-125757.patch b/patches/server/0467-Fix-arrows-never-despawning-MC-125757.patch deleted file mode 100644 index 8bdc8589fd..0000000000 --- a/patches/server/0467-Fix-arrows-never-despawning-MC-125757.patch +++ /dev/null @@ -1,22 +0,0 @@ -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/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 7f1f4813ac007fbf79e8ba254075c015fe15e3a1..f02fb03c63975e5c1ccdd848f5727559929cce00 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -199,6 +199,7 @@ public abstract class AbstractArrow extends Projectile { - - ++this.inGroundTime; - } else { -+ if (tickCount > 200) this.tickDespawn(); // Paper - tick despawnCounter regardless after 10 seconds - this.inGroundTime = 0; - Vec3 vec3d2 = this.position(); - diff --git a/patches/server/0467-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/0467-Thread-Safe-Vanilla-Command-permission-checking.patch new file mode 100644 index 0000000000..09b482e662 --- /dev/null +++ b/patches/server/0467-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 20a7cdf87f307878d66922aaac0c60cff218e46c..39844531b03eb8a6c70700b4ecbf0ff1a557424d 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 CommandSourceStack) { + try { +- ((CommandSourceStack) source).currentCommand = this; ++ ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper + return this.requirement.test(source); + } finally { +- ((CommandSourceStack) source).currentCommand = null; ++ ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper + } + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index aadddbc16aa719677c3b6fc4969b6145b9b9ee0b..6fdbe747645eb83f31b56bca77a9d7962237aed8 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -59,7 +59,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + private final Vec2 rotation; + private final CommandSigningContext signingContext; + private final TaskChainer chatMessageChainer; +- public volatile CommandNode currentCommand; // CraftBukkit ++ public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { + this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { +@@ -195,9 +195,11 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + @Override + public boolean hasPermission(int level) { + // CraftBukkit start +- CommandNode currentCommand = this.currentCommand; ++ // Paper start - fix concurrency issue ++ CommandNode currentCommand = this.currentCommand.get(Thread.currentThread()); + if (currentCommand != null) { + return this.hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand)); ++ // Paper end + } + // CraftBukkit end + diff --git a/patches/server/0468-Fix-SPIGOT-5989.patch b/patches/server/0468-Fix-SPIGOT-5989.patch new file mode 100644 index 0000000000..111dc3b220 --- /dev/null +++ b/patches/server/0468-Fix-SPIGOT-5989.patch @@ -0,0 +1,61 @@ +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 1cb6900489ad25ddd40a3b8c39f436c03a72b3dc..8fd4494fcd29e92856938792b922ee3ef0102604 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -842,6 +842,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 +@@ -852,7 +853,7 @@ public abstract class PlayerList { + Optional optional; + + if (blockposition != null) { +- optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, flag); ++ optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, true); // Paper - Fix SPIGOT-5989 + } else { + optional = Optional.empty(); + } +@@ -896,7 +897,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 { +@@ -934,8 +940,12 @@ public abstract class PlayerList { + } + // entityplayer1.initInventoryMenu(); + entityplayer1.setHealth(entityplayer1.getHealth()); +- if (flag2) { +- entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); ++ // Paper start - Fix SPIGOT-5989 ++ if (flag2 && !isLocAltered) { ++ BlockState data = worldserver1.getBlockState(blockposition); ++ worldserver1.setBlock(blockposition, data.setValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE, data.getValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE) - 1), 3); ++ entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) location.getX(), (double) location.getY(), (double) location.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); ++ // Paper end + } + // Added from changeDimension + this.sendAllPlayerInfo(entityplayer); // Update health, etc... diff --git a/patches/server/0468-Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/0468-Thread-Safe-Vanilla-Command-permission-checking.patch deleted file mode 100644 index 09b482e662..0000000000 --- a/patches/server/0468-Thread-Safe-Vanilla-Command-permission-checking.patch +++ /dev/null @@ -1,53 +0,0 @@ -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 20a7cdf87f307878d66922aaac0c60cff218e46c..39844531b03eb8a6c70700b4ecbf0ff1a557424d 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 CommandSourceStack) { - try { -- ((CommandSourceStack) source).currentCommand = this; -+ ((CommandSourceStack) source).currentCommand.put(Thread.currentThread(), this); // Paper - return this.requirement.test(source); - } finally { -- ((CommandSourceStack) source).currentCommand = null; -+ ((CommandSourceStack) source).currentCommand.remove(Thread.currentThread()); // Paper - } - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index aadddbc16aa719677c3b6fc4969b6145b9b9ee0b..6fdbe747645eb83f31b56bca77a9d7962237aed8 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -59,7 +59,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy - private final Vec2 rotation; - private final CommandSigningContext signingContext; - private final TaskChainer chatMessageChainer; -- public volatile CommandNode currentCommand; // CraftBukkit -+ public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper - - public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { - this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { -@@ -195,9 +195,11 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy - @Override - public boolean hasPermission(int level) { - // CraftBukkit start -- CommandNode currentCommand = this.currentCommand; -+ // Paper start - fix concurrency issue -+ CommandNode currentCommand = this.currentCommand.get(Thread.currentThread()); - if (currentCommand != null) { - return this.hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand)); -+ // Paper end - } - // CraftBukkit end - diff --git a/patches/server/0469-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server/0469-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch new file mode 100644 index 0000000000..c7303d0b5c --- /dev/null +++ b/patches/server/0469-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch @@ -0,0 +1,50 @@ +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 151b13e257c09fc5c4bbccfc388b15ad76133909..3f7dde4fe1fdce3638d1db5e96a546b9fae90269 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -154,8 +154,17 @@ public class Main { + return; + } + +- File file = (File) optionset.valueOf("universe"); // CraftBukkit +- Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper ++ // Paper start - fix SPIGOT-5824 ++ File file; ++ File userCacheFile = new File(Services.USERID_CACHE_FILE); ++ if (optionset.has("universe")) { ++ file = (File) optionset.valueOf("universe"); // CraftBukkit ++ userCacheFile = new File(file, Services.USERID_CACHE_FILE); ++ } else { ++ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); ++ } ++ // Paper end - fix SPIGOT-5824 ++ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper + // CraftBukkit start + String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); + LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); +diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java +index d7d65d0faefa5551480a4090de3a881828238ffd..ef6ff78af2ae747e939895b82ee9d11c75012dcd 100644 +--- a/src/main/java/net/minecraft/server/Services.java ++++ b/src/main/java/net/minecraft/server/Services.java +@@ -19,12 +19,12 @@ public record Services(MinecraftSessionService sessionService, SignatureValidato + return java.util.Objects.requireNonNull(this.paperConfigurations); + } + // Paper end +- private static final String USERID_CACHE_FILE = "usercache.json"; ++ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public + +- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, joptsimple.OptionSet optionSet) throws Exception { // Paper ++ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper + MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); + GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); +- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json")); ++ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper + SignatureValidator signatureValidator = SignatureValidator.from(authenticationService.getServicesKey()); + // Paper start + final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); diff --git a/patches/server/0469-Fix-SPIGOT-5989.patch b/patches/server/0469-Fix-SPIGOT-5989.patch deleted file mode 100644 index 111dc3b220..0000000000 --- a/patches/server/0469-Fix-SPIGOT-5989.patch +++ /dev/null @@ -1,61 +0,0 @@ -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 1cb6900489ad25ddd40a3b8c39f436c03a72b3dc..8fd4494fcd29e92856938792b922ee3ef0102604 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -842,6 +842,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 -@@ -852,7 +853,7 @@ public abstract class PlayerList { - Optional optional; - - if (blockposition != null) { -- optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, flag); -+ optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, true); // Paper - Fix SPIGOT-5989 - } else { - optional = Optional.empty(); - } -@@ -896,7 +897,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 { -@@ -934,8 +940,12 @@ public abstract class PlayerList { - } - // entityplayer1.initInventoryMenu(); - entityplayer1.setHealth(entityplayer1.getHealth()); -- if (flag2) { -- entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); -+ // Paper start - Fix SPIGOT-5989 -+ if (flag2 && !isLocAltered) { -+ BlockState data = worldserver1.getBlockState(blockposition); -+ worldserver1.setBlock(blockposition, data.setValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE, data.getValue(net.minecraft.world.level.block.RespawnAnchorBlock.CHARGE) - 1), 3); -+ entityplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) location.getX(), (double) location.getY(), (double) location.getZ(), 1.0F, 1.0F, worldserver1.getRandom().nextLong())); -+ // Paper end - } - // Added from changeDimension - this.sendAllPlayerInfo(entityplayer); // Update health, etc... diff --git a/patches/server/0470-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch b/patches/server/0470-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch deleted file mode 100644 index c7303d0b5c..0000000000 --- a/patches/server/0470-Fix-SPIGOT-5824-Bukkit-world-container-is-not-used.patch +++ /dev/null @@ -1,50 +0,0 @@ -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 151b13e257c09fc5c4bbccfc388b15ad76133909..3f7dde4fe1fdce3638d1db5e96a546b9fae90269 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -154,8 +154,17 @@ public class Main { - return; - } - -- File file = (File) optionset.valueOf("universe"); // CraftBukkit -- Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, optionset); // Paper -+ // Paper start - fix SPIGOT-5824 -+ File file; -+ File userCacheFile = new File(Services.USERID_CACHE_FILE); -+ if (optionset.has("universe")) { -+ file = (File) optionset.valueOf("universe"); // CraftBukkit -+ userCacheFile = new File(file, Services.USERID_CACHE_FILE); -+ } else { -+ file = new File(bukkitConfiguration.getString("settings.world-container", ".")); -+ } -+ // Paper end - fix SPIGOT-5824 -+ Services services = Services.create(new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY), file, userCacheFile, optionset); // Paper - // CraftBukkit start - String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName); - LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath()); -diff --git a/src/main/java/net/minecraft/server/Services.java b/src/main/java/net/minecraft/server/Services.java -index d7d65d0faefa5551480a4090de3a881828238ffd..ef6ff78af2ae747e939895b82ee9d11c75012dcd 100644 ---- a/src/main/java/net/minecraft/server/Services.java -+++ b/src/main/java/net/minecraft/server/Services.java -@@ -19,12 +19,12 @@ public record Services(MinecraftSessionService sessionService, SignatureValidato - return java.util.Objects.requireNonNull(this.paperConfigurations); - } - // Paper end -- private static final String USERID_CACHE_FILE = "usercache.json"; -+ public static final String USERID_CACHE_FILE = "usercache.json"; // Paper - private -> public - -- public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, joptsimple.OptionSet optionSet) throws Exception { // Paper -+ public static Services create(YggdrasilAuthenticationService authenticationService, File rootDirectory, File userCacheFile, joptsimple.OptionSet optionSet) throws Exception { // Paper - MinecraftSessionService minecraftSessionService = authenticationService.createMinecraftSessionService(); - GameProfileRepository gameProfileRepository = authenticationService.createProfileRepository(); -- GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, new File(rootDirectory, "usercache.json")); -+ GameProfileCache gameProfileCache = new GameProfileCache(gameProfileRepository, userCacheFile); // Paper - SignatureValidator signatureValidator = SignatureValidator.from(authenticationService.getServicesKey()); - // Paper start - final java.nio.file.Path legacyConfigPath = ((File) optionSet.valueOf("paper-settings")).toPath(); diff --git a/patches/server/0470-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/0470-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch new file mode 100644 index 0000000000..5010bc5bf1 --- /dev/null +++ b/patches/server/0470-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 3f7dde4fe1fdce3638d1db5e96a546b9fae90269..13ef3bb2b84fac9a1be72b09e7e3c022fa08221a 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -154,6 +154,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(Services.USERID_CACHE_FILE); diff --git a/patches/server/0471-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/0471-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch new file mode 100644 index 0000000000..484c50da0a --- /dev/null +++ b/patches/server/0471-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch @@ -0,0 +1,74 @@ +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/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +index 06fc39b19385d36fd0c5bb9a7042a238eb6e8a57..bb1f0e9dbcb792d015d1cb65664a96fdd3e0489e 100644 +--- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -14,22 +14,24 @@ import net.minecraft.server.level.ServerPlayer; + import net.minecraft.world.level.storage.loot.LootContext; + + public abstract class SimpleCriterionTrigger implements CriterionTrigger { +- private final Map>> players = Maps.newIdentityHashMap(); ++ //private final Map>> players = Maps.newIdentityHashMap(); // Paper - moved into AdvancementDataPlayer to fix memory leak ++ ++ public SimpleCriterionTrigger() {} + + @Override + public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- this.players.computeIfAbsent(manager, (managerx) -> { ++ manager.criterionData.computeIfAbsent(this, (managerx) -> { // Paper - fix AdvancementDataPlayer leak + return Sets.newHashSet(); + }).add(conditions); + } + + @Override + public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { +- Set> set = this.players.get(manager); ++ Set> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null) { + set.remove(conditions); + if (set.isEmpty()) { +- this.players.remove(manager); ++ manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak + } + } + +@@ -37,7 +39,7 @@ public abstract class SimpleCriterionTrigger predicate) { + PlayerAdvancements playerAdvancements = player.getAdvancements(); +- Set> set = this.players.get(playerAdvancements); ++ Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak + if (set != null && !set.isEmpty()) { + LootContext lootContext = EntityPredicate.createContext(player, player); + List> list = null; +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index 0c2f12e7930646a3da53a50f38be62e0cb1ed2b7..e7ec5e1144c1596b035f97fb1fb86d18e61be3c9 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -68,6 +68,8 @@ public class PlayerAdvancements { + private Advancement lastSelectedTab; + private boolean isFirstPacket = true; + ++ public final Map> criterionData = Maps.newIdentityHashMap(); // Paper - fix advancement data player leakage ++ + public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, File advancementFile, ServerPlayer owner) { + this.dataFixer = dataFixer; + this.playerList = playerManager; diff --git a/patches/server/0471-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch b/patches/server/0471-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch deleted file mode 100644 index 5010bc5bf1..0000000000 --- a/patches/server/0471-Fix-SPIGOT-5885-Unable-to-disable-advancements.patch +++ /dev/null @@ -1,18 +0,0 @@ -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 3f7dde4fe1fdce3638d1db5e96a546b9fae90269..13ef3bb2b84fac9a1be72b09e7e3c022fa08221a 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -154,6 +154,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(Services.USERID_CACHE_FILE); diff --git a/patches/server/0472-Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/patches/server/0472-Add-missing-strikeLighting-call-to-World-spigot-stri.patch new file mode 100644 index 0000000000..4c6955f7fd --- /dev/null +++ b/patches/server/0472-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 710270775cd47e8df1146ad0636648568d45ac75..04896192121039ef6a3be103d59a9d687d7f6c05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2136,6 +2136,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + lightning.moveTo( loc.getX(), loc.getY(), loc.getZ() ); + lightning.visualOnly = true; + lightning.isSilent = isSilent; ++ world.strikeLightning( lightning ); + return (LightningStrike) lightning.getBukkitEntity(); + } + }; diff --git a/patches/server/0472-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch b/patches/server/0472-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch deleted file mode 100644 index 484c50da0a..0000000000 --- a/patches/server/0472-Fix-AdvancementDataPlayer-leak-due-from-quitting-ear.patch +++ /dev/null @@ -1,74 +0,0 @@ -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/SimpleCriterionTrigger.java b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -index 06fc39b19385d36fd0c5bb9a7042a238eb6e8a57..bb1f0e9dbcb792d015d1cb65664a96fdd3e0489e 100644 ---- a/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -+++ b/src/main/java/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java -@@ -14,22 +14,24 @@ import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.level.storage.loot.LootContext; - - public abstract class SimpleCriterionTrigger implements CriterionTrigger { -- private final Map>> players = Maps.newIdentityHashMap(); -+ //private final Map>> players = Maps.newIdentityHashMap(); // Paper - moved into AdvancementDataPlayer to fix memory leak -+ -+ public SimpleCriterionTrigger() {} - - @Override - public final void addPlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { -- this.players.computeIfAbsent(manager, (managerx) -> { -+ manager.criterionData.computeIfAbsent(this, (managerx) -> { // Paper - fix AdvancementDataPlayer leak - return Sets.newHashSet(); - }).add(conditions); - } - - @Override - public final void removePlayerListener(PlayerAdvancements manager, CriterionTrigger.Listener conditions) { -- Set> set = this.players.get(manager); -+ Set> set = (Set) manager.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak - if (set != null) { - set.remove(conditions); - if (set.isEmpty()) { -- this.players.remove(manager); -+ manager.criterionData.remove(this); // Paper - fix AdvancementDataPlayer leak - } - } - -@@ -37,7 +39,7 @@ public abstract class SimpleCriterionTrigger predicate) { - PlayerAdvancements playerAdvancements = player.getAdvancements(); -- Set> set = this.players.get(playerAdvancements); -+ Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak - if (set != null && !set.isEmpty()) { - LootContext lootContext = EntityPredicate.createContext(player, player); - List> list = null; -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 0c2f12e7930646a3da53a50f38be62e0cb1ed2b7..e7ec5e1144c1596b035f97fb1fb86d18e61be3c9 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -68,6 +68,8 @@ public class PlayerAdvancements { - private Advancement lastSelectedTab; - private boolean isFirstPacket = true; - -+ public final Map> criterionData = Maps.newIdentityHashMap(); // Paper - fix advancement data player leakage -+ - public PlayerAdvancements(DataFixer dataFixer, PlayerList playerManager, ServerAdvancementManager advancementLoader, File advancementFile, ServerPlayer owner) { - this.dataFixer = dataFixer; - this.playerList = playerManager; diff --git a/patches/server/0473-Add-missing-strikeLighting-call-to-World-spigot-stri.patch b/patches/server/0473-Add-missing-strikeLighting-call-to-World-spigot-stri.patch deleted file mode 100644 index 4c6955f7fd..0000000000 --- a/patches/server/0473-Add-missing-strikeLighting-call-to-World-spigot-stri.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 710270775cd47e8df1146ad0636648568d45ac75..04896192121039ef6a3be103d59a9d687d7f6c05 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2136,6 +2136,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - lightning.moveTo( loc.getX(), loc.getY(), loc.getZ() ); - lightning.visualOnly = true; - lightning.isSilent = isSilent; -+ world.strikeLightning( lightning ); - return (LightningStrike) lightning.getBukkitEntity(); - } - }; diff --git a/patches/server/0473-Fix-some-rails-connecting-improperly.patch b/patches/server/0473-Fix-some-rails-connecting-improperly.patch new file mode 100644 index 0000000000..4dc21a2695 --- /dev/null +++ b/patches/server/0473-Fix-some-rails-connecting-improperly.patch @@ -0,0 +1,84 @@ +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/BaseRailBlock.java b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +index 9ae45eaf77ead0a6801046d8946d50eec24612fa..a3f877bf03f75cbfbd128c856322bcd427b95d21 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java +@@ -65,6 +65,7 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl + state = this.updateDir(world, pos, state, true); + if (this.isStraight) { + world.neighborChanged(state, pos, this, pos, notify); ++ state = world.getBlockState(pos); // Paper - don't desync, update again + } + + return state; +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index fe9b6c89a1f0c98a9c73a409f2aca6a4a1c0f9e7..932a2c279f46c951182d2604b525b473b6945895 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -70,6 +70,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + private void checkPressed(Level world, BlockPos pos, BlockState state) { + if (this.canSurvive(state, world, pos)) { ++ if (state.getBlock() != this) { return; } // Paper - not our block, don't do anything + boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); + boolean flag1 = false; + List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> { +diff --git a/src/main/java/net/minecraft/world/level/block/RailState.java b/src/main/java/net/minecraft/world/level/block/RailState.java +index 0cbfad97371b59de95963a09aa16f3dad7a37222..d873625c1b8439a727d39ce207b5e84a1d86e5eb 100644 +--- a/src/main/java/net/minecraft/world/level/block/RailState.java ++++ b/src/main/java/net/minecraft/world/level/block/RailState.java +@@ -17,6 +17,12 @@ public class RailState { + private final boolean isStraight; + private final List connections = Lists.newArrayList(); + ++ // Paper start - prevent desync ++ public boolean isValid() { ++ return this.level.getBlockState(this.pos).getBlock() == this.state.getBlock(); ++ } ++ // Paper end - prevent desync ++ + public RailState(Level world, BlockPos pos, BlockState state) { + this.level = world; + this.pos = pos; +@@ -143,6 +149,11 @@ public class RailState { + } + + private void connectTo(RailState placementHelper) { ++ // Paper start - prevent desync ++ if (!this.isValid() || !placementHelper.isValid()) { ++ return; ++ } ++ // Paper end - prevent desync + this.connections.add(placementHelper.pos); + BlockPos blockPos = this.pos.north(); + BlockPos blockPos2 = this.pos.south(); +@@ -333,10 +344,15 @@ public class RailState { + this.state = this.state.setValue(this.block.getShapeProperty(), railShape2); + if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { + this.level.setBlock(this.pos, this.state, 3); ++ // Paper start - prevent desync ++ if (!this.isValid()) { ++ return this; ++ } ++ // Paper end - prevent desync + + for(int i = 0; i < this.connections.size(); ++i) { + RailState railState = this.getRail(this.connections.get(i)); +- if (railState != null) { ++ if (railState != null && railState.isValid()) { // Paper - prevent desync + railState.removeSoftConnections(); + if (railState.canConnectTo(this)) { + railState.connectTo(this); +@@ -349,6 +365,6 @@ public class RailState { + } + + public BlockState getState() { +- return this.state; ++ return this.level.getBlockState(this.pos); // Paper - prevent desync + } + } diff --git a/patches/server/0474-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server/0474-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch new file mode 100644 index 0000000000..be48fd5ae2 --- /dev/null +++ b/patches/server/0474-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 a7f4054002bd176fccf8357e9a23de66dd9e0dc5..207e4302161b3abe2ade56c9dc9c31820010fa42 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.TagParser; + 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 TagParser MOJANGSON_PARSER = new TagParser(new StringReader("")); + diff --git a/patches/server/0474-Fix-some-rails-connecting-improperly.patch b/patches/server/0474-Fix-some-rails-connecting-improperly.patch deleted file mode 100644 index 4dc21a2695..0000000000 --- a/patches/server/0474-Fix-some-rails-connecting-improperly.patch +++ /dev/null @@ -1,84 +0,0 @@ -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/BaseRailBlock.java b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -index 9ae45eaf77ead0a6801046d8946d50eec24612fa..a3f877bf03f75cbfbd128c856322bcd427b95d21 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseRailBlock.java -@@ -65,6 +65,7 @@ public abstract class BaseRailBlock extends Block implements SimpleWaterloggedBl - state = this.updateDir(world, pos, state, true); - if (this.isStraight) { - world.neighborChanged(state, pos, this, pos, notify); -+ state = world.getBlockState(pos); // Paper - don't desync, update again - } - - return state; -diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -index fe9b6c89a1f0c98a9c73a409f2aca6a4a1c0f9e7..932a2c279f46c951182d2604b525b473b6945895 100644 ---- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -@@ -70,6 +70,7 @@ public class DetectorRailBlock extends BaseRailBlock { - - private void checkPressed(Level world, BlockPos pos, BlockState state) { - if (this.canSurvive(state, world, pos)) { -+ if (state.getBlock() != this) { return; } // Paper - not our block, don't do anything - boolean flag = (Boolean) state.getValue(DetectorRailBlock.POWERED); - boolean flag1 = false; - List list = this.getInteractingMinecartOfType(world, pos, AbstractMinecart.class, (entity) -> { -diff --git a/src/main/java/net/minecraft/world/level/block/RailState.java b/src/main/java/net/minecraft/world/level/block/RailState.java -index 0cbfad97371b59de95963a09aa16f3dad7a37222..d873625c1b8439a727d39ce207b5e84a1d86e5eb 100644 ---- a/src/main/java/net/minecraft/world/level/block/RailState.java -+++ b/src/main/java/net/minecraft/world/level/block/RailState.java -@@ -17,6 +17,12 @@ public class RailState { - private final boolean isStraight; - private final List connections = Lists.newArrayList(); - -+ // Paper start - prevent desync -+ public boolean isValid() { -+ return this.level.getBlockState(this.pos).getBlock() == this.state.getBlock(); -+ } -+ // Paper end - prevent desync -+ - public RailState(Level world, BlockPos pos, BlockState state) { - this.level = world; - this.pos = pos; -@@ -143,6 +149,11 @@ public class RailState { - } - - private void connectTo(RailState placementHelper) { -+ // Paper start - prevent desync -+ if (!this.isValid() || !placementHelper.isValid()) { -+ return; -+ } -+ // Paper end - prevent desync - this.connections.add(placementHelper.pos); - BlockPos blockPos = this.pos.north(); - BlockPos blockPos2 = this.pos.south(); -@@ -333,10 +344,15 @@ public class RailState { - this.state = this.state.setValue(this.block.getShapeProperty(), railShape2); - if (forceUpdate || this.level.getBlockState(this.pos) != this.state) { - this.level.setBlock(this.pos, this.state, 3); -+ // Paper start - prevent desync -+ if (!this.isValid()) { -+ return this; -+ } -+ // Paper end - prevent desync - - for(int i = 0; i < this.connections.size(); ++i) { - RailState railState = this.getRail(this.connections.get(i)); -- if (railState != null) { -+ if (railState != null && railState.isValid()) { // Paper - prevent desync - railState.removeSoftConnections(); - if (railState.canConnectTo(this)) { - railState.connectTo(this); -@@ -349,6 +365,6 @@ public class RailState { - } - - public BlockState getState() { -- return this.state; -+ return this.level.getBlockState(this.pos); // Paper - prevent desync - } - } diff --git a/patches/server/0475-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0475-Do-not-let-the-server-load-chunks-from-newer-version.patch new file mode 100644 index 0000000000..6fb1c85c61 --- /dev/null +++ b/patches/server/0475-Do-not-let-the-server-load-chunks-from-newer-version.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown +Date: Tue, 23 Jul 2019 20:44:47 -0500 +Subject: [PATCH] Do not let the server load chunks from newer versions + +If the server attempts to load a chunk generated by a newer version of +the game, immediately stop the server to prevent data corruption. + +You can override this functionality at your own peril. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index f26a08f81495dde6205b34254d159b042e5a6ea9..12e3831324abdc1112cbe65cab0f0c75ce77a9ef 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -118,9 +118,22 @@ public class ChunkSerializer { + return holder.protoChunk; + } + ++ // Paper start ++ private static final int CURRENT_DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); ++ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); ++ // Paper end + public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { + java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); + // Paper end ++ // Paper start - Do NOT attempt to load chunks saved with newer versions ++ if (nbt.contains("DataVersion", 99)) { ++ int dataVersion = nbt.getInt("DataVersion"); ++ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { ++ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); ++ System.exit(1); ++ } ++ } ++ // Paper end + ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate + + if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/server/0475-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch b/patches/server/0475-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch deleted file mode 100644 index be48fd5ae2..0000000000 --- a/patches/server/0475-Fix-regex-mistake-in-CB-NBT-int-deserialization.patch +++ /dev/null @@ -1,27 +0,0 @@ -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 a7f4054002bd176fccf8357e9a23de66dd9e0dc5..207e4302161b3abe2ade56c9dc9c31820010fa42 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.TagParser; - 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 TagParser MOJANGSON_PARSER = new TagParser(new StringReader("")); - diff --git a/patches/server/0476-Brand-support.patch b/patches/server/0476-Brand-support.patch new file mode 100644 index 0000000000..96c458463b --- /dev/null +++ b/patches/server/0476-Brand-support.patch @@ -0,0 +1,75 @@ +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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c59e90ba0de83eeda3719b6303bee9796b4a1af0..da6a0171bd63ac68635de1c23fc9eafa732503bd 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -298,6 +298,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper + ++ private String clientBrandName = null; // Paper - Brand name ++ + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { + this.lastChatTimeStamp = new AtomicReference(Instant.EPOCH); + this.lastSeenMessagesValidator = new LastSeenMessagesValidator(); +@@ -3453,6 +3455,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + private static final ResourceLocation CUSTOM_REGISTER = new ResourceLocation("register"); + private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister"); + ++ private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support ++ + @Override + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +@@ -3480,6 +3484,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + try { + byte[] data = new byte[packet.data.readableBytes()]; + packet.data.readBytes(data); ++ // Paper start - Brand support ++ if (packet.identifier.equals(MINECRAFT_BRAND)) { ++ try { ++ this.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); ++ } catch (StringIndexOutOfBoundsException ex) { ++ this.clientBrandName = "illegal"; ++ } ++ } ++ // Paper end + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), packet.identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +@@ -3489,6 +3502,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + } + ++ // Paper start - brand support ++ public String getClientBrandName() { ++ return clientBrandName; ++ } ++ // Paper end ++ + public final boolean isDisconnected() { + return (!this.player.joining && !this.connection.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 0fda8c27c717bd030b826c5c7267b880f9d1f6b9..10e1be2349df779b911848f70e4b8f0e351b1de7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2681,6 +2681,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + // Paper end + }; + ++ // Paper start - brand support ++ @Override ++ public String getClientBrandName() { ++ return getHandle().connection != null ? getHandle().connection.getClientBrandName() : null; ++ } ++ // Paper end ++ + public Player.Spigot spigot() + { + return this.spigot; diff --git a/patches/server/0476-Do-not-let-the-server-load-chunks-from-newer-version.patch b/patches/server/0476-Do-not-let-the-server-load-chunks-from-newer-version.patch deleted file mode 100644 index 6fb1c85c61..0000000000 --- a/patches/server/0476-Do-not-let-the-server-load-chunks-from-newer-version.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown -Date: Tue, 23 Jul 2019 20:44:47 -0500 -Subject: [PATCH] Do not let the server load chunks from newer versions - -If the server attempts to load a chunk generated by a newer version of -the game, immediately stop the server to prevent data corruption. - -You can override this functionality at your own peril. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index f26a08f81495dde6205b34254d159b042e5a6ea9..12e3831324abdc1112cbe65cab0f0c75ce77a9ef 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -118,9 +118,22 @@ public class ChunkSerializer { - return holder.protoChunk; - } - -+ // Paper start -+ private static final int CURRENT_DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); -+ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); -+ // Paper end - public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { - java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); - // Paper end -+ // Paper start - Do NOT attempt to load chunks saved with newer versions -+ if (nbt.contains("DataVersion", 99)) { -+ int dataVersion = nbt.getInt("DataVersion"); -+ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) { -+ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace(); -+ System.exit(1); -+ } -+ } -+ // Paper end - ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(chunkPos, chunkcoordintpair1)) { diff --git a/patches/server/0477-Add-setMaxPlayers-API.patch b/patches/server/0477-Add-setMaxPlayers-API.patch new file mode 100644 index 0000000000..0efd813c6f --- /dev/null +++ b/patches/server/0477-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 8fd4494fcd29e92856938792b922ee3ef0102604..ceb5e9540e54275e97f08533d16c820061e76c8f 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -147,7 +147,7 @@ public abstract class PlayerList { + public final PlayerDataStorage playerIo; + private boolean doWhiteList; + private final RegistryAccess.Frozen registryHolder; +- 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 int simulationDistance; + private boolean allowCheatsForAllPlayers; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 58f361ad664ba45c496ab0817c7ab5e1a017b807..f156620c14e28513fd0f825d5f34de5c5f831b75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -674,6 +674,13 @@ public final class CraftServer implements Server { + return this.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/0477-Brand-support.patch b/patches/server/0477-Brand-support.patch deleted file mode 100644 index 5e746a6bca..0000000000 --- a/patches/server/0477-Brand-support.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: DigitalRegent -Date: Sat, 11 Apr 2020 13:10:58 +0200 -Subject: [PATCH] Brand support - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d7118172a9f587716f24cd2aa5deb1566bef2daf..c4b36f8eab6f73bccfe92a5d4b66ef44306a5e36 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -298,6 +298,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit - private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - -+ private String clientBrandName = null; // Paper - Brand name -+ - public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player) { - this.lastChatTimeStamp = new AtomicReference(Instant.EPOCH); - this.lastSeenMessagesValidator = new LastSeenMessagesValidator(); -@@ -3449,6 +3451,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - private static final ResourceLocation CUSTOM_REGISTER = new ResourceLocation("register"); - private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister"); - -+ private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support -+ - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); -@@ -3476,6 +3480,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - try { - byte[] data = new byte[packet.data.readableBytes()]; - packet.data.readBytes(data); -+ // Paper start - Brand support -+ if (packet.identifier.equals(MINECRAFT_BRAND)) { -+ try { -+ this.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.copiedBuffer(data)).readUtf(256); -+ } catch (StringIndexOutOfBoundsException ex) { -+ this.clientBrandName = "illegal"; -+ } -+ } -+ // Paper end - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), packet.identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -@@ -3485,6 +3498,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - } - -+ // Paper start - brand support -+ public String getClientBrandName() { -+ return clientBrandName; -+ } -+ // Paper end -+ - public final boolean isDisconnected() { - return (!this.player.joining && !this.connection.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 0fda8c27c717bd030b826c5c7267b880f9d1f6b9..10e1be2349df779b911848f70e4b8f0e351b1de7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2681,6 +2681,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper end - }; - -+ // Paper start - brand support -+ @Override -+ public String getClientBrandName() { -+ return getHandle().connection != null ? getHandle().connection.getClientBrandName() : null; -+ } -+ // Paper end -+ - public Player.Spigot spigot() - { - return this.spigot; diff --git a/patches/server/0478-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0478-Add-playPickupItemAnimation-to-LivingEntity.patch new file mode 100644 index 0000000000..93c4be55ff --- /dev/null +++ b/patches/server/0478-Add-playPickupItemAnimation-to-LivingEntity.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 19:36:22 +0200 +Subject: [PATCH] Add playPickupItemAnimation to LivingEntity + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 55881c189e96bccd6538dfb6b4ea897b72d3936d..679e08544fc1e6973605dd2f3953c12a0d338ddd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -884,5 +884,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + ((Mob) getHandle()).getJumpControl().jump(); + } + } ++ ++ @Override ++ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { ++ getHandle().take(((CraftItem) item).getHandle(), quantity); ++ } + // Paper end + } diff --git a/patches/server/0478-Add-setMaxPlayers-API.patch b/patches/server/0478-Add-setMaxPlayers-API.patch deleted file mode 100644 index 0efd813c6f..0000000000 --- a/patches/server/0478-Add-setMaxPlayers-API.patch +++ /dev/null @@ -1,37 +0,0 @@ -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 8fd4494fcd29e92856938792b922ee3ef0102604..ceb5e9540e54275e97f08533d16c820061e76c8f 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -147,7 +147,7 @@ public abstract class PlayerList { - public final PlayerDataStorage playerIo; - private boolean doWhiteList; - private final RegistryAccess.Frozen registryHolder; -- 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 int simulationDistance; - private boolean allowCheatsForAllPlayers; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 58f361ad664ba45c496ab0817c7ab5e1a017b807..f156620c14e28513fd0f825d5f34de5c5f831b75 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -674,6 +674,13 @@ public final class CraftServer implements Server { - return this.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/0479-Add-playPickupItemAnimation-to-LivingEntity.patch b/patches/server/0479-Add-playPickupItemAnimation-to-LivingEntity.patch deleted file mode 100644 index deca8c1301..0000000000 --- a/patches/server/0479-Add-playPickupItemAnimation-to-LivingEntity.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 19:36:22 +0200 -Subject: [PATCH] Add playPickupItemAnimation to LivingEntity - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index a9e7ef578c89905ff23beb85f0114f984eaaaaa7..594db7ab7c78f4f7f17781f339431bf5f133b8bf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -843,5 +843,10 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - ((Mob) getHandle()).getJumpControl().jump(); - } - } -+ -+ @Override -+ public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { -+ getHandle().take(((CraftItem) item).getHandle(), quantity); -+ } - // Paper end - } diff --git a/patches/server/0479-Don-t-require-FACING-data.patch b/patches/server/0479-Don-t-require-FACING-data.patch new file mode 100644 index 0000000000..fd2ab14906 --- /dev/null +++ b/patches/server/0479-Don-t-require-FACING-data.patch @@ -0,0 +1,37 @@ +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/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index da6a145b06e217ff82b0a1b04410238dc50d4869..1e6ba6d9cceda1d4867b183c3dbc03d317ed287f 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -14,6 +14,7 @@ import org.bukkit.event.block.BlockDispenseEvent; + // CraftBukkit end + + public class DefaultDispenseItemBehavior implements DispenseItemBehavior { ++ private Direction enumdirection; // Paper + + // CraftBukkit start + private boolean dropper; +@@ -27,15 +28,16 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + + @Override + public final ItemStack dispense(BlockSource pointer, ItemStack stack) { ++ enumdirection = pointer.getBlockState().getValue(DispenserBlock.FACING); // Paper - cache facing direction + ItemStack itemstack1 = this.execute(pointer, stack); + + this.playSound(pointer); +- this.playAnimation(pointer, (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING)); ++ this.playAnimation(pointer, enumdirection); // Paper - cache facing direction + return itemstack1; + } + + protected ItemStack execute(BlockSource pointer, ItemStack stack) { +- Direction enumdirection = (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING); ++ // Paper - cached enum direction + Position iposition = DispenserBlock.getDispensePosition(pointer); + ItemStack itemstack1 = stack.split(1); + diff --git a/patches/server/0480-Don-t-require-FACING-data.patch b/patches/server/0480-Don-t-require-FACING-data.patch deleted file mode 100644 index fd2ab14906..0000000000 --- a/patches/server/0480-Don-t-require-FACING-data.patch +++ /dev/null @@ -1,37 +0,0 @@ -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/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -index da6a145b06e217ff82b0a1b04410238dc50d4869..1e6ba6d9cceda1d4867b183c3dbc03d317ed287f 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java -@@ -14,6 +14,7 @@ import org.bukkit.event.block.BlockDispenseEvent; - // CraftBukkit end - - public class DefaultDispenseItemBehavior implements DispenseItemBehavior { -+ private Direction enumdirection; // Paper - - // CraftBukkit start - private boolean dropper; -@@ -27,15 +28,16 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { - - @Override - public final ItemStack dispense(BlockSource pointer, ItemStack stack) { -+ enumdirection = pointer.getBlockState().getValue(DispenserBlock.FACING); // Paper - cache facing direction - ItemStack itemstack1 = this.execute(pointer, stack); - - this.playSound(pointer); -- this.playAnimation(pointer, (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING)); -+ this.playAnimation(pointer, enumdirection); // Paper - cache facing direction - return itemstack1; - } - - protected ItemStack execute(BlockSource pointer, ItemStack stack) { -- Direction enumdirection = (Direction) pointer.getBlockState().getValue(DispenserBlock.FACING); -+ // Paper - cached enum direction - Position iposition = DispenserBlock.getDispensePosition(pointer); - ItemStack itemstack1 = stack.split(1); - diff --git a/patches/server/0480-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0480-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch new file mode 100644 index 0000000000..79f5aab58a --- /dev/null +++ b/patches/server/0480-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 22 Aug 2020 23:36:21 +0200 +Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 8b5eac2ad96c0ebb6eae04585998cade578ff74b..2ba432f7be370ce1a5861e90de9541d76acdcfd2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1855,6 +1855,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); + + this.levelData.setSpawn(pos, angle); ++ 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().spawn.keepSpawnLoadedRange * 16, prevSpawn); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 04896192121039ef6a3be103d59a9d687d7f6c05..b95aed97b48bf31091ee2f44a21ad88071e97bf3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -263,11 +263,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean setSpawnLocation(int x, int y, int z, float angle) { + try { + Location previousLocation = this.getSpawnLocation(); +- world.levelData.setSpawn(new BlockPos(x, y, z), angle); ++ world.setDefaultSpawnPos(new BlockPos(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); +- this.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/0481-Add-moon-phase-API.patch b/patches/server/0481-Add-moon-phase-API.patch new file mode 100644 index 0000000000..c400ce80c7 --- /dev/null +++ b/patches/server/0481-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/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index e395628ccf59c1b7a4efcabb3c38e8a879b95e38..ee5e59c37301d9a806e2f696d52d9d217b232833 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -932,4 +932,11 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + + throw new IllegalArgumentException("Cannot spawn an entity for " + clazz.getName()); + } ++ ++ // Paper start ++ @Override ++ public io.papermc.paper.world.MoonPhase getMoonPhase() { ++ return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); ++ } ++ // Paper end + } diff --git a/patches/server/0481-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0481-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch deleted file mode 100644 index 79f5aab58a..0000000000 --- a/patches/server/0481-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 22 Aug 2020 23:36:21 +0200 -Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 8b5eac2ad96c0ebb6eae04585998cade578ff74b..2ba432f7be370ce1a5861e90de9541d76acdcfd2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1855,6 +1855,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c())); - - this.levelData.setSpawn(pos, angle); -+ 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().spawn.keepSpawnLoadedRange * 16, prevSpawn); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 04896192121039ef6a3be103d59a9d687d7f6c05..b95aed97b48bf31091ee2f44a21ad88071e97bf3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -263,11 +263,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean setSpawnLocation(int x, int y, int z, float angle) { - try { - Location previousLocation = this.getSpawnLocation(); -- world.levelData.setSpawn(new BlockPos(x, y, z), angle); -+ world.setDefaultSpawnPos(new BlockPos(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); -- this.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/0482-Add-moon-phase-API.patch b/patches/server/0482-Add-moon-phase-API.patch deleted file mode 100644 index c400ce80c7..0000000000 --- a/patches/server/0482-Add-moon-phase-API.patch +++ /dev/null @@ -1,22 +0,0 @@ -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/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index e395628ccf59c1b7a4efcabb3c38e8a879b95e38..ee5e59c37301d9a806e2f696d52d9d217b232833 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -932,4 +932,11 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - - throw new IllegalArgumentException("Cannot spawn an entity for " + clazz.getName()); - } -+ -+ // Paper start -+ @Override -+ public io.papermc.paper.world.MoonPhase getMoonPhase() { -+ return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); -+ } -+ // Paper end - } diff --git a/patches/server/0482-Improve-Chunk-Status-Transition-Speed.patch b/patches/server/0482-Improve-Chunk-Status-Transition-Speed.patch new file mode 100644 index 0000000000..352e70742e --- /dev/null +++ b/patches/server/0482-Improve-Chunk-Status-Transition-Speed.patch @@ -0,0 +1,81 @@ +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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index ac42029596ae0c824bf33a4058ac1009740e29ea..a699107b1afea1c52e5a7e93af8f39ae9e1955b3 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -101,6 +101,13 @@ public class ChunkHolder { + // Paper end - optimise anyPlayerCloseEnoughForSpawning + long lastAutoSaveTime; // Paper - incremental autosave + long inactiveTimeStart; // Paper - incremental autosave ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ public boolean canAdvanceStatus() { ++ ChunkStatus status = getChunkHolderStatus(); ++ ChunkAccess chunk = getAvailableChunkNow(); ++ return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status))); ++ } ++ // Paper end + + public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 7493da0f1c3f8ab0ebc517347ef23fbe2747a306..5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -726,7 +726,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return either.mapLeft((list) -> { + return (LevelChunk) list.get(list.size() / 2); + }); +- }, this.mainThreadExecutor); ++ }, this.mainInvokingExecutor); // Paper + } + + @Nullable +@@ -1144,6 +1144,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return "chunkGenerate " + requiredStatus.getName(); + }); + Executor executor = (runnable) -> { ++ // Paper start - optimize chunk status progression without jumping through thread pool ++ if (holder.canAdvanceStatus()) { ++ this.mainInvokingExecutor.execute(runnable); ++ return; ++ } ++ // Paper end + this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); + }; + diff --git a/patches/server/0483-Improve-Chunk-Status-Transition-Speed.patch b/patches/server/0483-Improve-Chunk-Status-Transition-Speed.patch deleted file mode 100644 index 352e70742e..0000000000 --- a/patches/server/0483-Improve-Chunk-Status-Transition-Speed.patch +++ /dev/null @@ -1,81 +0,0 @@ -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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index ac42029596ae0c824bf33a4058ac1009740e29ea..a699107b1afea1c52e5a7e93af8f39ae9e1955b3 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -101,6 +101,13 @@ public class ChunkHolder { - // Paper end - optimise anyPlayerCloseEnoughForSpawning - long lastAutoSaveTime; // Paper - incremental autosave - long inactiveTimeStart; // Paper - incremental autosave -+ // Paper start - optimize chunk status progression without jumping through thread pool -+ public boolean canAdvanceStatus() { -+ ChunkStatus status = getChunkHolderStatus(); -+ ChunkAccess chunk = getAvailableChunkNow(); -+ return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status))); -+ } -+ // Paper end - - public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { - this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 7493da0f1c3f8ab0ebc517347ef23fbe2747a306..5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -726,7 +726,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return either.mapLeft((list) -> { - return (LevelChunk) list.get(list.size() / 2); - }); -- }, this.mainThreadExecutor); -+ }, this.mainInvokingExecutor); // Paper - } - - @Nullable -@@ -1144,6 +1144,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - return "chunkGenerate " + requiredStatus.getName(); - }); - Executor executor = (runnable) -> { -+ // Paper start - optimize chunk status progression without jumping through thread pool -+ if (holder.canAdvanceStatus()) { -+ this.mainInvokingExecutor.execute(runnable); -+ return; -+ } -+ // Paper end - this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); - }; - diff --git a/patches/server/0483-Prevent-headless-pistons-from-being-created.patch b/patches/server/0483-Prevent-headless-pistons-from-being-created.patch new file mode 100644 index 0000000000..b31a2a9b3a --- /dev/null +++ b/patches/server/0483-Prevent-headless-pistons-from-being-created.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: commandblockguy +Date: Fri, 14 Aug 2020 14:44:14 -0500 +Subject: [PATCH] Prevent headless pistons from being created + +Prevent headless pistons from being created by explosions or tree/mushroom growth. + +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index b18b0e1b5e059f08fd3117eaa0fb28a10fac6562..01477e7240f9e33d08d416a7d40ee10f3e5d4abf 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -187,6 +187,15 @@ public class Explosion { + + if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { + set.add(blockposition); ++ // Paper start - prevent headless pistons from forming ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ BlockEntity extension = this.level.getBlockEntity(blockposition); ++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ set.add(blockposition.relative(direction.getOpposite())); ++ } ++ } ++ // Paper end + } + + d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0484-Add-BellRingEvent.patch b/patches/server/0484-Add-BellRingEvent.patch new file mode 100644 index 0000000000..e6528721cf --- /dev/null +++ b/patches/server/0484-Add-BellRingEvent.patch @@ -0,0 +1,28 @@ +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/BellBlock.java b/src/main/java/net/minecraft/world/level/block/BellBlock.java +index a4da6418c17e145333aa5efe427826ba53293e4d..14002e1f67e3dce421584c01e8f91769509638b7 100644 +--- a/src/main/java/net/minecraft/world/level/block/BellBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BellBlock.java +@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; + import javax.annotation.Nullable; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; ++import net.minecraft.server.MCUtil; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.stats.Stats; +@@ -131,6 +132,7 @@ public class BellBlock extends BaseEntityBlock { + direction = world.getBlockState(pos).getValue(FACING); + } + ++ if (!new io.papermc.paper.event.block.BellRingEvent(world.getWorld().getBlockAt(MCUtil.toLocation(world, pos)), entity == null ? null : entity.getBukkitEntity()).callEvent()) return false; // Paper - BellRingEvent + ((BellBlockEntity)blockEntity).onHit(direction); + world.playSound((Player)null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F); + world.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos); diff --git a/patches/server/0484-Prevent-headless-pistons-from-being-created.patch b/patches/server/0484-Prevent-headless-pistons-from-being-created.patch deleted file mode 100644 index b31a2a9b3a..0000000000 --- a/patches/server/0484-Prevent-headless-pistons-from-being-created.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: commandblockguy -Date: Fri, 14 Aug 2020 14:44:14 -0500 -Subject: [PATCH] Prevent headless pistons from being created - -Prevent headless pistons from being created by explosions or tree/mushroom growth. - -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index b18b0e1b5e059f08fd3117eaa0fb28a10fac6562..01477e7240f9e33d08d416a7d40ee10f3e5d4abf 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -187,6 +187,15 @@ public class Explosion { - - if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { - set.add(blockposition); -+ // Paper start - prevent headless pistons from forming -+ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { -+ BlockEntity extension = this.level.getBlockEntity(blockposition); -+ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { -+ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); -+ set.add(blockposition.relative(direction.getOpposite())); -+ } -+ } -+ // Paper end - } - - d4 += d0 * 0.30000001192092896D; diff --git a/patches/server/0485-Add-BellRingEvent.patch b/patches/server/0485-Add-BellRingEvent.patch deleted file mode 100644 index e6528721cf..0000000000 --- a/patches/server/0485-Add-BellRingEvent.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Eearslya Sleiarion -Date: Sun, 23 Aug 2020 13:04:02 +0200 -Subject: [PATCH] Add BellRingEvent - -Add a new event, BellRingEvent, to trigger whenever a player rings a -village bell. Passes along the bell block and the player who rang it. - -diff --git a/src/main/java/net/minecraft/world/level/block/BellBlock.java b/src/main/java/net/minecraft/world/level/block/BellBlock.java -index a4da6418c17e145333aa5efe427826ba53293e4d..14002e1f67e3dce421584c01e8f91769509638b7 100644 ---- a/src/main/java/net/minecraft/world/level/block/BellBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BellBlock.java -@@ -3,6 +3,7 @@ package net.minecraft.world.level.block; - import javax.annotation.Nullable; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; -+import net.minecraft.server.MCUtil; - import net.minecraft.sounds.SoundEvents; - import net.minecraft.sounds.SoundSource; - import net.minecraft.stats.Stats; -@@ -131,6 +132,7 @@ public class BellBlock extends BaseEntityBlock { - direction = world.getBlockState(pos).getValue(FACING); - } - -+ if (!new io.papermc.paper.event.block.BellRingEvent(world.getWorld().getBlockAt(MCUtil.toLocation(world, pos)), entity == null ? null : entity.getBukkitEntity()).callEvent()) return false; // Paper - BellRingEvent - ((BellBlockEntity)blockEntity).onHit(direction); - world.playSound((Player)null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F); - world.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos); diff --git a/patches/server/0485-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0485-Add-zombie-targets-turtle-egg-config.patch new file mode 100644 index 0000000000..a92287451d --- /dev/null +++ b/patches/server/0485-Add-zombie-targets-turtle-egg-config.patch @@ -0,0 +1,19 @@ +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/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 3c3095e7e684079bcba0ea5a6b44c8fe2a3f47c4..67fe3b79b6fe4064b26f87734174a394b9e2b3e3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -107,7 +107,7 @@ public class Zombie extends Monster { + + @Override + protected void registerGoals() { +- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); ++ if (level.paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.addBehaviourGoals(); diff --git a/patches/server/0486-Add-zombie-targets-turtle-egg-config.patch b/patches/server/0486-Add-zombie-targets-turtle-egg-config.patch deleted file mode 100644 index a92287451d..0000000000 --- a/patches/server/0486-Add-zombie-targets-turtle-egg-config.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 3c3095e7e684079bcba0ea5a6b44c8fe2a3f47c4..67fe3b79b6fe4064b26f87734174a394b9e2b3e3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -107,7 +107,7 @@ public class Zombie extends Monster { - - @Override - protected void registerGoals() { -- this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); -+ if (level.paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); - this.addBehaviourGoals(); diff --git a/patches/server/0486-Buffer-joins-to-world.patch b/patches/server/0486-Buffer-joins-to-world.patch new file mode 100644 index 0000000000..f21684f9df --- /dev/null +++ b/patches/server/0486-Buffer-joins-to-world.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 19 Aug 2020 05:05:54 +0100 +Subject: [PATCH] Buffer joins to world + +This patch buffers the number of logins which will attempt to join +the world per tick, this attempts to reduce the impact that join floods +has on the server + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 7f0bcb69a1a14a01bd80ffcba1afb25ceeab19b8..4d2b26fb1ac5663b667ffd16eed379f4a3311677 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -396,8 +396,23 @@ public class Connection extends SimpleChannelInboundHandler> { + } + // Paper end + ++ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper ++ private static int joinAttemptsThisTick; // Paper ++ private static int currTick; // Paper + public void tick() { + this.flushQueue(); ++ // Paper start ++ if (currTick != net.minecraft.server.MinecraftServer.currentTick) { ++ currTick = net.minecraft.server.MinecraftServer.currentTick; ++ joinAttemptsThisTick = 0; ++ } ++ // Paper end ++ if (this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl) { ++ if ( ((net.minecraft.server.network.ServerLoginPacketListenerImpl) this.packetListener).state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT // Paper ++ || (joinAttemptsThisTick++ < MAX_PER_TICK)) { // Paper - limit the number of joins which can be processed each tick ++ ((net.minecraft.server.network.ServerLoginPacketListenerImpl) this.packetListener).tick(); ++ } // Paper ++ } + PacketListener packetlistener = this.packetListener; + + if (packetlistener instanceof TickablePacketListener) { diff --git a/patches/server/0487-Buffer-joins-to-world.patch b/patches/server/0487-Buffer-joins-to-world.patch deleted file mode 100644 index f21684f9df..0000000000 --- a/patches/server/0487-Buffer-joins-to-world.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 19 Aug 2020 05:05:54 +0100 -Subject: [PATCH] Buffer joins to world - -This patch buffers the number of logins which will attempt to join -the world per tick, this attempts to reduce the impact that join floods -has on the server - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 7f0bcb69a1a14a01bd80ffcba1afb25ceeab19b8..4d2b26fb1ac5663b667ffd16eed379f4a3311677 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -396,8 +396,23 @@ public class Connection extends SimpleChannelInboundHandler> { - } - // Paper end - -+ private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper -+ private static int joinAttemptsThisTick; // Paper -+ private static int currTick; // Paper - public void tick() { - this.flushQueue(); -+ // Paper start -+ if (currTick != net.minecraft.server.MinecraftServer.currentTick) { -+ currTick = net.minecraft.server.MinecraftServer.currentTick; -+ joinAttemptsThisTick = 0; -+ } -+ // Paper end -+ if (this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl) { -+ if ( ((net.minecraft.server.network.ServerLoginPacketListenerImpl) this.packetListener).state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT // Paper -+ || (joinAttemptsThisTick++ < MAX_PER_TICK)) { // Paper - limit the number of joins which can be processed each tick -+ ((net.minecraft.server.network.ServerLoginPacketListenerImpl) this.packetListener).tick(); -+ } // Paper -+ } - PacketListener packetlistener = this.packetListener; - - if (packetlistener instanceof TickablePacketListener) { diff --git a/patches/server/0487-Eigencraft-redstone-implementation.patch b/patches/server/0487-Eigencraft-redstone-implementation.patch new file mode 100644 index 0000000000..e4a4e5398a --- /dev/null +++ b/patches/server/0487-Eigencraft-redstone-implementation.patch @@ -0,0 +1,1139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: theosib +Date: Thu, 27 Sep 2018 01:43:35 -0600 +Subject: [PATCH] Eigencraft redstone implementation + +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/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..22a2547810d0c029f29685faddf7ac21cde2df0b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java +@@ -0,0 +1,957 @@ ++package com.destroystokyo.paper.util; ++ ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.ThreadLocalRandom; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.Items; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.RedStoneWireBlock; ++import net.minecraft.world.level.block.state.BlockState; ++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 RedStoneWireBlock 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(RedStoneWireBlock 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 BlockPos[] computeAllNeighbors(final BlockPos pos) { ++ final int x = pos.getX(); ++ final int y = pos.getY(); ++ final int z = pos.getZ(); ++ final BlockPos[] n = new BlockPos[24]; ++ ++ // Immediate neighbors, in the same order as ++ // World.notifyNeighborsOfStateChange, etc.: ++ // west, east, down, up, north, south ++ n[0] = new BlockPos(x - 1, y, z); ++ n[1] = new BlockPos(x + 1, y, z); ++ n[2] = new BlockPos(x, y - 1, z); ++ n[3] = new BlockPos(x, y + 1, z); ++ n[4] = new BlockPos(x, y, z - 1); ++ n[5] = new BlockPos(x, y, z + 1); ++ ++ // Neighbors of neighbors, in the same order, ++ // except that duplicates are not included ++ n[6] = new BlockPos(x - 2, y, z); ++ n[7] = new BlockPos(x - 1, y - 1, z); ++ n[8] = new BlockPos(x - 1, y + 1, z); ++ n[9] = new BlockPos(x - 1, y, z - 1); ++ n[10] = new BlockPos(x - 1, y, z + 1); ++ n[11] = new BlockPos(x + 2, y, z); ++ n[12] = new BlockPos(x + 1, y - 1, z); ++ n[13] = new BlockPos(x + 1, y + 1, z); ++ n[14] = new BlockPos(x + 1, y, z - 1); ++ n[15] = new BlockPos(x + 1, y, z + 1); ++ n[16] = new BlockPos(x, y - 2, z); ++ n[17] = new BlockPos(x, y - 1, z - 1); ++ n[18] = new BlockPos(x, y - 1, z + 1); ++ n[19] = new BlockPos(x, y + 2, z); ++ n[20] = new BlockPos(x, y + 1, z - 1); ++ n[21] = new BlockPos(x, y + 1, z + 1); ++ n[22] = new BlockPos(x, y, z - 2); ++ n[23] = new BlockPos(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 ++ } ++ ++ BlockState currentState; // Keep track of redstone wire value ++ UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) ++ BlockPos self; // UpdateNode's own position ++ BlockPos 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 Level worldIn, final UpdateNode upd1) { ++ final BlockPos pos = upd1.self; ++ final BlockState oldState = worldIn.getBlockState(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.canSurvive(null, worldIn, pos)) { ++ // Pop off the redstone dust ++ Block.popResource(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO ++ worldIn.removeBlock(pos, false); ++ ++ // 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 Level worldIn, final UpdateNode upd1, final int layer) { ++ final BlockPos 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 BlockState 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. ++ BlockState 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 Level worldIn, final UpdateNode upd1) { ++ final BlockPos pos = upd1.self; ++ ++ // Get the list of neighbor coordinates ++ final BlockPos[] 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 BlockPos 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 Level 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 BlockPos 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 Level 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); ++ ++ // [Space Walker] ++ // The neighbor update system got a significant overhaul in 1.19. ++ // Shape and block updates are now added to a stack before being ++ // processed. These changes make it so any neighbor updates emitted ++ // by this accelerator will not be processed until after the entire ++ // wire network has updated. This has a significant impact on the ++ // behavior and introduces Vanilla parity issues. ++ // To circumvent this issue we bypass the neighbor update stack and ++ // call BlockStateBase#neighborChanged directly. This change mostly ++ // restores old behavior, at the cost of bypassing the ++ // max-chained-neighbor-updates server property. ++ worldIn.getBlockState(upd.self).neighborChanged(worldIn, upd.self, wire, upd.parent, false); ++ } ++ } ++ ++ // 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 BlockState scheduleReentrantNeighborChanged(final Level worldIn, final BlockPos pos, final BlockState newState, final BlockPos 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 BlockState updateSurroundingRedstone(final Level worldIn, final BlockPos pos, final BlockState state, final BlockPos 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 BlockState 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 BlockState calculateCurrentChanges(final Level worldIn, final UpdateNode upd) { ++ BlockState state = upd.currentState; ++ final int i = state.getValue(RedStoneWireBlock.POWER).intValue(); ++ int j = 0; ++ j = getMaxCurrentStrength(upd, j); ++ int l = 0; ++ ++ wire.shouldSignal = 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.getBestNeighborSignal(upd.self); ++ wire.shouldSignal = 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.isRedstoneConductor(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.isRedstoneConductor(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.getCraftServer().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. ++ BlockPos pos = new BlockPos(upd.self.getX(), upd.self.getY(), upd.self.getZ()); ++ if (wire.canSurvive(null, worldIn, pos)) { ++ state = state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(j)); ++ // [Space Walker] suppress shape updates and emit those manually to ++ // bypass the new neighbor update stack. ++ if (worldIn.setBlock(upd.self, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) ++ updateNeighborShapes(worldIn, upd.self, state); ++ } ++ } ++ ++ return state; ++ } ++ ++ private static final Direction[] UPDATE_SHAPE_ORDER = { Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP }; ++ ++ /* ++ * [Space Walker] ++ * This method emits shape updates around the given block, ++ * bypassing the new neighbor update stack. Diagonal shape ++ * updates are omitted, as they are mostly unnecessary. ++ * Diagonal shape updates are emitted exclusively to other ++ * redstone wires, in order to update their connection properties. ++ * Wire connections should never change as a result of power ++ * changes, so the only behavioral change will be in scenarios ++ * where earlier shape updates have been suppressed to keep a ++ * redstone wire in an invalid state. ++ */ ++ public void updateNeighborShapes(Level level, BlockPos pos, BlockState state) { ++ // these updates will be added to the stack and processed after the entire network has updated ++ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS); ++ ++ for (Direction dir : UPDATE_SHAPE_ORDER) { ++ BlockPos neighborPos = pos.relative(dir); ++ BlockState neighborState = level.getBlockState(neighborPos); ++ ++ BlockState newState = neighborState.updateShape(dir.getOpposite(), state, level, neighborPos, pos); ++ Block.updateOrDestroy(neighborState, newState, level, neighborPos, Block.UPDATE_CLIENTS); ++ } ++ } ++ ++ /* ++ * 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.getValue(RedStoneWireBlock.POWER).intValue(); ++ return i > strength ? i : strength; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +index 74241146a034c5817cddc608c095d829d765f06a..3cba4921daad4b346a3f964f0fa48e1bb4d634a3 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -253,6 +253,116 @@ public class RedStoneWireBlock extends Block { + return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); + } + ++ // Paper start - Optimize redstone ++ // The bulk of the new functionality is found in RedstoneWireTurbo.java ++ com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.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(Level worldIn, BlockPos pos, BlockState state, BlockPos source) { ++ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { ++ turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ return; ++ } ++ updatePowerStrength(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 BlockState calculateCurrentChanges(Level worldIn, BlockPos pos1, BlockPos pos2, BlockState state) { ++ BlockState iblockstate = state; ++ int i = state.getValue(POWER); ++ int j = 0; ++ j = this.getPower(j, worldIn.getBlockState(pos2)); ++ this.shouldSignal = false; ++ int k = worldIn.getBestNeighborSignal(pos1); ++ this.shouldSignal = true; ++ ++ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA) { ++ // 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().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA || k < 15) { ++ for (Direction enumfacing : Direction.Plane.HORIZONTAL) { ++ BlockPos blockpos = pos1.relative(enumfacing); ++ boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); ++ ++ if (flag) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos)); ++ } ++ ++ if (worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && !worldIn.getBlockState(pos1.above()).isRedstoneConductor(worldIn, pos1)) { ++ if (flag && pos1.getY() >= pos2.getY()) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos.above())); ++ } ++ } else if (!worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { ++ l = this.getPower(l, worldIn.getBlockState(blockpos.below())); ++ } ++ } ++ } ++ ++ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA) { ++ // 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) { ++ org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(worldIn.getWorld().getBlockAt(pos1.getX(), pos1.getY(), pos1.getZ()), i, j); ++ worldIn.getCraftServer().getPluginManager().callEvent(event); ++ ++ j = event.getNewCurrent(); ++ state = state.setValue(POWER, j); ++ ++ if (worldIn.getBlockState(pos1) == iblockstate) { ++ // [Space Walker] suppress shape updates and emit those manually to ++ // bypass the new neighbor update stack. ++ if (worldIn.setBlock(pos1, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) ++ turbo.updateNeighborShapes(worldIn, pos1, state); ++ } ++ } ++ ++ return state; ++ } ++ // Paper end ++ + private void updatePowerStrength(Level world, BlockPos pos, BlockState state) { + int i = this.calculateTargetStrength(world, pos); + +@@ -322,6 +432,7 @@ public class RedStoneWireBlock extends Block { + return Math.max(i, j - 1); + } + ++ private int getPower(int min, BlockState iblockdata) { return Math.max(min, getWireSignal(iblockdata)); } // Paper - Optimize redstone + private int getWireSignal(BlockState state) { + return state.is((Block) this) ? (Integer) state.getValue(RedStoneWireBlock.POWER) : 0; + } +@@ -344,7 +455,7 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -371,7 +482,7 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -406,7 +517,7 @@ public class RedStoneWireBlock extends Block { + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + if (!world.isClientSide) { + if (state.canSurvive(world, pos)) { +- this.updatePowerStrength(world, pos, state); ++ this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); diff --git a/patches/server/0488-Eigencraft-redstone-implementation.patch b/patches/server/0488-Eigencraft-redstone-implementation.patch deleted file mode 100644 index e4a4e5398a..0000000000 --- a/patches/server/0488-Eigencraft-redstone-implementation.patch +++ /dev/null @@ -1,1139 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: theosib -Date: Thu, 27 Sep 2018 01:43:35 -0600 -Subject: [PATCH] Eigencraft redstone implementation - -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/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java -new file mode 100644 -index 0000000000000000000000000000000000000000..22a2547810d0c029f29685faddf7ac21cde2df0b ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java -@@ -0,0 +1,957 @@ -+package com.destroystokyo.paper.util; -+ -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.ThreadLocalRandom; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.RedStoneWireBlock; -+import net.minecraft.world.level.block.state.BlockState; -+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 RedStoneWireBlock 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(RedStoneWireBlock 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 BlockPos[] computeAllNeighbors(final BlockPos pos) { -+ final int x = pos.getX(); -+ final int y = pos.getY(); -+ final int z = pos.getZ(); -+ final BlockPos[] n = new BlockPos[24]; -+ -+ // Immediate neighbors, in the same order as -+ // World.notifyNeighborsOfStateChange, etc.: -+ // west, east, down, up, north, south -+ n[0] = new BlockPos(x - 1, y, z); -+ n[1] = new BlockPos(x + 1, y, z); -+ n[2] = new BlockPos(x, y - 1, z); -+ n[3] = new BlockPos(x, y + 1, z); -+ n[4] = new BlockPos(x, y, z - 1); -+ n[5] = new BlockPos(x, y, z + 1); -+ -+ // Neighbors of neighbors, in the same order, -+ // except that duplicates are not included -+ n[6] = new BlockPos(x - 2, y, z); -+ n[7] = new BlockPos(x - 1, y - 1, z); -+ n[8] = new BlockPos(x - 1, y + 1, z); -+ n[9] = new BlockPos(x - 1, y, z - 1); -+ n[10] = new BlockPos(x - 1, y, z + 1); -+ n[11] = new BlockPos(x + 2, y, z); -+ n[12] = new BlockPos(x + 1, y - 1, z); -+ n[13] = new BlockPos(x + 1, y + 1, z); -+ n[14] = new BlockPos(x + 1, y, z - 1); -+ n[15] = new BlockPos(x + 1, y, z + 1); -+ n[16] = new BlockPos(x, y - 2, z); -+ n[17] = new BlockPos(x, y - 1, z - 1); -+ n[18] = new BlockPos(x, y - 1, z + 1); -+ n[19] = new BlockPos(x, y + 2, z); -+ n[20] = new BlockPos(x, y + 1, z - 1); -+ n[21] = new BlockPos(x, y + 1, z + 1); -+ n[22] = new BlockPos(x, y, z - 2); -+ n[23] = new BlockPos(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 -+ } -+ -+ BlockState currentState; // Keep track of redstone wire value -+ UpdateNode[] neighbor_nodes; // References to neighbors (directed graph edges) -+ BlockPos self; // UpdateNode's own position -+ BlockPos 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 Level worldIn, final UpdateNode upd1) { -+ final BlockPos pos = upd1.self; -+ final BlockState oldState = worldIn.getBlockState(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.canSurvive(null, worldIn, pos)) { -+ // Pop off the redstone dust -+ Block.popResource(worldIn, pos, new ItemStack(Items.REDSTONE)); // TODO -+ worldIn.removeBlock(pos, false); -+ -+ // 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 Level worldIn, final UpdateNode upd1, final int layer) { -+ final BlockPos 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 BlockState 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. -+ BlockState 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 Level worldIn, final UpdateNode upd1) { -+ final BlockPos pos = upd1.self; -+ -+ // Get the list of neighbor coordinates -+ final BlockPos[] 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 BlockPos 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 Level 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 BlockPos 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 Level 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); -+ -+ // [Space Walker] -+ // The neighbor update system got a significant overhaul in 1.19. -+ // Shape and block updates are now added to a stack before being -+ // processed. These changes make it so any neighbor updates emitted -+ // by this accelerator will not be processed until after the entire -+ // wire network has updated. This has a significant impact on the -+ // behavior and introduces Vanilla parity issues. -+ // To circumvent this issue we bypass the neighbor update stack and -+ // call BlockStateBase#neighborChanged directly. This change mostly -+ // restores old behavior, at the cost of bypassing the -+ // max-chained-neighbor-updates server property. -+ worldIn.getBlockState(upd.self).neighborChanged(worldIn, upd.self, wire, upd.parent, false); -+ } -+ } -+ -+ // 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 BlockState scheduleReentrantNeighborChanged(final Level worldIn, final BlockPos pos, final BlockState newState, final BlockPos 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 BlockState updateSurroundingRedstone(final Level worldIn, final BlockPos pos, final BlockState state, final BlockPos 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 BlockState 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 BlockState calculateCurrentChanges(final Level worldIn, final UpdateNode upd) { -+ BlockState state = upd.currentState; -+ final int i = state.getValue(RedStoneWireBlock.POWER).intValue(); -+ int j = 0; -+ j = getMaxCurrentStrength(upd, j); -+ int l = 0; -+ -+ wire.shouldSignal = 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.getBestNeighborSignal(upd.self); -+ wire.shouldSignal = 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.isRedstoneConductor(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.isRedstoneConductor(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.getCraftServer().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. -+ BlockPos pos = new BlockPos(upd.self.getX(), upd.self.getY(), upd.self.getZ()); -+ if (wire.canSurvive(null, worldIn, pos)) { -+ state = state.setValue(RedStoneWireBlock.POWER, Integer.valueOf(j)); -+ // [Space Walker] suppress shape updates and emit those manually to -+ // bypass the new neighbor update stack. -+ if (worldIn.setBlock(upd.self, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) -+ updateNeighborShapes(worldIn, upd.self, state); -+ } -+ } -+ -+ return state; -+ } -+ -+ private static final Direction[] UPDATE_SHAPE_ORDER = { Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP }; -+ -+ /* -+ * [Space Walker] -+ * This method emits shape updates around the given block, -+ * bypassing the new neighbor update stack. Diagonal shape -+ * updates are omitted, as they are mostly unnecessary. -+ * Diagonal shape updates are emitted exclusively to other -+ * redstone wires, in order to update their connection properties. -+ * Wire connections should never change as a result of power -+ * changes, so the only behavioral change will be in scenarios -+ * where earlier shape updates have been suppressed to keep a -+ * redstone wire in an invalid state. -+ */ -+ public void updateNeighborShapes(Level level, BlockPos pos, BlockState state) { -+ // these updates will be added to the stack and processed after the entire network has updated -+ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS); -+ -+ for (Direction dir : UPDATE_SHAPE_ORDER) { -+ BlockPos neighborPos = pos.relative(dir); -+ BlockState neighborState = level.getBlockState(neighborPos); -+ -+ BlockState newState = neighborState.updateShape(dir.getOpposite(), state, level, neighborPos, pos); -+ Block.updateOrDestroy(neighborState, newState, level, neighborPos, Block.UPDATE_CLIENTS); -+ } -+ } -+ -+ /* -+ * 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.getValue(RedStoneWireBlock.POWER).intValue(); -+ return i > strength ? i : strength; -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -index 74241146a034c5817cddc608c095d829d765f06a..3cba4921daad4b346a3f964f0fa48e1bb4d634a3 100644 ---- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -253,6 +253,116 @@ public class RedStoneWireBlock extends Block { - return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); - } - -+ // Paper start - Optimize redstone -+ // The bulk of the new functionality is found in RedstoneWireTurbo.java -+ com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.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(Level worldIn, BlockPos pos, BlockState state, BlockPos source) { -+ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { -+ turbo.updateSurroundingRedstone(worldIn, pos, state, source); -+ return; -+ } -+ updatePowerStrength(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 BlockState calculateCurrentChanges(Level worldIn, BlockPos pos1, BlockPos pos2, BlockState state) { -+ BlockState iblockstate = state; -+ int i = state.getValue(POWER); -+ int j = 0; -+ j = this.getPower(j, worldIn.getBlockState(pos2)); -+ this.shouldSignal = false; -+ int k = worldIn.getBestNeighborSignal(pos1); -+ this.shouldSignal = true; -+ -+ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA) { -+ // 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().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA || k < 15) { -+ for (Direction enumfacing : Direction.Plane.HORIZONTAL) { -+ BlockPos blockpos = pos1.relative(enumfacing); -+ boolean flag = blockpos.getX() != pos2.getX() || blockpos.getZ() != pos2.getZ(); -+ -+ if (flag) { -+ l = this.getPower(l, worldIn.getBlockState(blockpos)); -+ } -+ -+ if (worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && !worldIn.getBlockState(pos1.above()).isRedstoneConductor(worldIn, pos1)) { -+ if (flag && pos1.getY() >= pos2.getY()) { -+ l = this.getPower(l, worldIn.getBlockState(blockpos.above())); -+ } -+ } else if (!worldIn.getBlockState(blockpos).isRedstoneConductor(worldIn, blockpos) && flag && pos1.getY() <= pos2.getY()) { -+ l = this.getPower(l, worldIn.getBlockState(blockpos.below())); -+ } -+ } -+ } -+ -+ if (worldIn.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.VANILLA) { -+ // 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) { -+ org.bukkit.event.block.BlockRedstoneEvent event = new org.bukkit.event.block.BlockRedstoneEvent(worldIn.getWorld().getBlockAt(pos1.getX(), pos1.getY(), pos1.getZ()), i, j); -+ worldIn.getCraftServer().getPluginManager().callEvent(event); -+ -+ j = event.getNewCurrent(); -+ state = state.setValue(POWER, j); -+ -+ if (worldIn.getBlockState(pos1) == iblockstate) { -+ // [Space Walker] suppress shape updates and emit those manually to -+ // bypass the new neighbor update stack. -+ if (worldIn.setBlock(pos1, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) -+ turbo.updateNeighborShapes(worldIn, pos1, state); -+ } -+ } -+ -+ return state; -+ } -+ // Paper end -+ - private void updatePowerStrength(Level world, BlockPos pos, BlockState state) { - int i = this.calculateTargetStrength(world, pos); - -@@ -322,6 +432,7 @@ public class RedStoneWireBlock extends Block { - return Math.max(i, j - 1); - } - -+ private int getPower(int min, BlockState iblockdata) { return Math.max(min, getWireSignal(iblockdata)); } // Paper - Optimize redstone - private int getWireSignal(BlockState state) { - return state.is((Block) this) ? (Integer) state.getValue(RedStoneWireBlock.POWER) : 0; - } -@@ -344,7 +455,7 @@ public class RedStoneWireBlock extends Block { - @Override - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (!oldState.is(state.getBlock()) && !world.isClientSide) { -- this.updatePowerStrength(world, pos, state); -+ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone - Iterator iterator = Direction.Plane.VERTICAL.iterator(); - - while (iterator.hasNext()) { -@@ -371,7 +482,7 @@ public class RedStoneWireBlock extends Block { - world.updateNeighborsAt(pos.relative(enumdirection), this); - } - -- this.updatePowerStrength(world, pos, state); -+ this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone - this.updateNeighborsOfNeighboringWires(world, pos); - } - } -@@ -406,7 +517,7 @@ public class RedStoneWireBlock extends Block { - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - if (!world.isClientSide) { - if (state.canSurvive(world, pos)) { -- this.updatePowerStrength(world, pos, state); -+ this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone - } else { - dropResources(state, world, pos); - world.removeBlock(pos, false); diff --git a/patches/server/0488-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0488-Fix-hex-colors-not-working-in-some-kick-messages.patch new file mode 100644 index 0000000000..c4f76a0ad7 --- /dev/null +++ b/patches/server/0488-Fix-hex-colors-not-working-in-some-kick-messages.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: JRoy +Date: Thu, 27 Aug 2020 16:57:25 -0400 +Subject: [PATCH] Fix hex colors not working in some kick messages + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 43759cdf3da0796d7969c6504ac9a6986c0f0518..750fef0f5b908b776a7306e54653eba497b7c50b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -75,12 +75,12 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + // CraftBukkit end + if (packet.getProtocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- MutableComponent ichatmutablecomponent; ++ Component ichatmutablecomponent; // Paper - Fix hex colors not working in some kick messages + + if (packet.getProtocolVersion() < 754) { +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } else { +- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot ++ ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } + + this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index d477e9fbe6ffb600d08f8ba49741067d14348968..2f0a70bc9cc8cda9e9beef00421078c036d6287c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -108,7 +108,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + // CraftBukkit start + @Deprecated + public void disconnect(String s) { +- this.disconnect(Component.literal(s)); ++ this.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/0489-Fix-hex-colors-not-working-in-some-kick-messages.patch b/patches/server/0489-Fix-hex-colors-not-working-in-some-kick-messages.patch deleted file mode 100644 index c4f76a0ad7..0000000000 --- a/patches/server/0489-Fix-hex-colors-not-working-in-some-kick-messages.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: JRoy -Date: Thu, 27 Aug 2020 16:57:25 -0400 -Subject: [PATCH] Fix hex colors not working in some kick messages - - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 43759cdf3da0796d7969c6504ac9a6986c0f0518..750fef0f5b908b776a7306e54653eba497b7c50b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -75,12 +75,12 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - // CraftBukkit end - if (packet.getProtocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { -- MutableComponent ichatmutablecomponent; -+ Component ichatmutablecomponent; // Paper - Fix hex colors not working in some kick messages - - if (packet.getProtocolVersion() < 754) { -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages - } else { -- ichatmutablecomponent = Component.literal( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) ); // Spigot -+ ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages - } - - this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index d477e9fbe6ffb600d08f8ba49741067d14348968..2f0a70bc9cc8cda9e9beef00421078c036d6287c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -108,7 +108,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - // CraftBukkit start - @Deprecated - public void disconnect(String s) { -- this.disconnect(Component.literal(s)); -+ this.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/0489-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server/0489-PortalCreateEvent-needs-to-know-its-entity.patch new file mode 100644 index 0000000000..3c2cb7d994 --- /dev/null +++ b/patches/server/0489-PortalCreateEvent-needs-to-know-its-entity.patch @@ -0,0 +1,138 @@ +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 cbcc90cffe38ea249cd0de4b0a90adc2a3ddeb0b..b4ad1610d30396be344a04f5f3a565ae2b8f2265 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -431,7 +431,7 @@ public final class ItemStack { + net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition); + + if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically +- block.getBlock().onPlace(block, world, newblockposition, oldBlock, true); ++ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext + } + + world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index 378d4316a51d48cb84e2d8a553f7b28bae55630e..922b5b22a4ccfeead9d6d2b9a2a2b3cc8a1e6c55 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -139,20 +139,23 @@ public abstract class BaseFireBlock extends Block { + super.entityInside(state, world, pos, entity); + } + ++ // Paper start - ItemActionContext param ++ @Override public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { this.onPlace(state, world, pos, oldState, notify, null); } + @Override +- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- if (!oldState.is(state.getBlock())) { ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, net.minecraft.world.item.context.UseOnContext itemActionContext) { ++ // Paper end ++ if (!iblockdata1.is(iblockdata.getBlock())) { + if (BaseFireBlock.inPortalDimension(world)) { +- Optional optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X); ++ Optional optional = PortalShape.findEmptyPortalShape(world, blockposition, Direction.Axis.X); + + if (optional.isPresent()) { +- ((PortalShape) optional.get()).createPortalBlocks(); ++ ((PortalShape) optional.get()).createPortalBlocks(itemActionContext); // Paper - pass ItemActionContext param + return; + } + } + +- if (!state.canSurvive(world, pos)) { +- this.fireExtinguished(world, pos); // CraftBukkit - fuel block broke ++ if (!iblockdata.canSurvive(world, blockposition)) { ++ fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke + } + + } +diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java +index 5ce5902b13ebb9438433d189f2c03677e4cb54b3..e34b8cff424ad58eee65a65fa510fa9908dbdf39 100644 +--- a/src/main/java/net/minecraft/world/level/block/FireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java +@@ -12,6 +12,7 @@ import net.minecraft.core.Direction; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.util.RandomSource; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.GameRules; + import net.minecraft.world.level.Level; +@@ -356,9 +357,11 @@ public class FireBlock extends BaseFireBlock { + } + + @Override +- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +- super.onPlace(state, world, pos, oldState, notify); +- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); ++ // Paper start - ItemActionContext param ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { ++ super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, itemActionContext); ++ // Paper end ++ world.scheduleTick(blockposition, this, getFireTickDelay(world.random)); + } + + private static int getFireTickDelay(RandomSource random) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index ad38a7ced7f3dc05fb3d133e9da39f0a5eb0915b..7117dd220c393163fd5be77e332e56a34c667670 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -35,6 +35,7 @@ import net.minecraft.world.item.DyeColor; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.context.BlockPlaceContext; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.EmptyBlockGetter; + import net.minecraft.world.level.Level; +@@ -136,6 +137,12 @@ public abstract class BlockBehaviour { + DebugPackets.sendNeighborsUpdatePacket(world, pos); + } + ++ // Paper start - add ItemActionContext param ++ @Deprecated ++ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { ++ this.onPlace(iblockdata, world, blockposition, iblockdata1, flag); ++ } ++ // Paper end + /** @deprecated */ + @Deprecated + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +index 768c39b265437641721d669d6aa85b3db49e5422..3414f3190e1a760c602613e82e551e797c3aa575 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java +@@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.tags.BlockTags; + import net.minecraft.util.Mth; + import net.minecraft.world.entity.EntityDimensions; ++import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.NetherPortalBlock; +@@ -184,7 +185,10 @@ public class PortalShape { + } + + // CraftBukkit start - return boolean +- public boolean createPortalBlocks() { ++ // Paper start - ItemActionContext param ++ @Deprecated public boolean createPortalBlocks() { return this.createPortalBlocks(null); } ++ public boolean createPortalBlocks(UseOnContext itemActionContext) { ++ // Paper end + org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld(); + + // Copy below for loop +@@ -194,7 +198,7 @@ public class PortalShape { + this.blocks.setBlock(blockposition, iblockdata, 18); + }); + +- PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) this.blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE); ++ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, itemActionContext == null || itemActionContext.getPlayer() == null ? null : itemActionContext.getPlayer().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param + this.level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event); + + if (event.isCancelled()) { diff --git a/patches/server/0490-Fix-CraftTeam-null-check.patch b/patches/server/0490-Fix-CraftTeam-null-check.patch new file mode 100644 index 0000000000..fc8ca37076 --- /dev/null +++ b/patches/server/0490-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 18d5a26c34c848241c306241b3ad9825b5a0b9a9..60b1cffdccde4715832546d6edbf206fbab4e82f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -261,7 +261,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 = this.checkState(); + diff --git a/patches/server/0490-PortalCreateEvent-needs-to-know-its-entity.patch b/patches/server/0490-PortalCreateEvent-needs-to-know-its-entity.patch deleted file mode 100644 index 3c2cb7d994..0000000000 --- a/patches/server/0490-PortalCreateEvent-needs-to-know-its-entity.patch +++ /dev/null @@ -1,138 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Fri, 21 Aug 2020 20:57:54 +0200 -Subject: [PATCH] PortalCreateEvent needs to know its entity - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index cbcc90cffe38ea249cd0de4b0a90adc2a3ddeb0b..b4ad1610d30396be344a04f5f3a565ae2b8f2265 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -431,7 +431,7 @@ public final class ItemStack { - net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition); - - if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically -- block.getBlock().onPlace(block, world, newblockposition, oldBlock, true); -+ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext - } - - world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 378d4316a51d48cb84e2d8a553f7b28bae55630e..922b5b22a4ccfeead9d6d2b9a2a2b3cc8a1e6c55 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -139,20 +139,23 @@ public abstract class BaseFireBlock extends Block { - super.entityInside(state, world, pos, entity); - } - -+ // Paper start - ItemActionContext param -+ @Override public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { this.onPlace(state, world, pos, oldState, notify, null); } - @Override -- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -- if (!oldState.is(state.getBlock())) { -+ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, net.minecraft.world.item.context.UseOnContext itemActionContext) { -+ // Paper end -+ if (!iblockdata1.is(iblockdata.getBlock())) { - if (BaseFireBlock.inPortalDimension(world)) { -- Optional optional = PortalShape.findEmptyPortalShape(world, pos, Direction.Axis.X); -+ Optional optional = PortalShape.findEmptyPortalShape(world, blockposition, Direction.Axis.X); - - if (optional.isPresent()) { -- ((PortalShape) optional.get()).createPortalBlocks(); -+ ((PortalShape) optional.get()).createPortalBlocks(itemActionContext); // Paper - pass ItemActionContext param - return; - } - } - -- if (!state.canSurvive(world, pos)) { -- this.fireExtinguished(world, pos); // CraftBukkit - fuel block broke -+ if (!iblockdata.canSurvive(world, blockposition)) { -+ fireExtinguished(world, blockposition); // CraftBukkit - fuel block broke - } - - } -diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index 5ce5902b13ebb9438433d189f2c03677e4cb54b3..e34b8cff424ad58eee65a65fa510fa9908dbdf39 100644 ---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java -@@ -12,6 +12,7 @@ import net.minecraft.core.Direction; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.util.RandomSource; - import net.minecraft.world.item.context.BlockPlaceContext; -+import net.minecraft.world.item.context.UseOnContext; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.GameRules; - import net.minecraft.world.level.Level; -@@ -356,9 +357,11 @@ public class FireBlock extends BaseFireBlock { - } - - @Override -- public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -- super.onPlace(state, world, pos, oldState, notify); -- world.scheduleTick(pos, (Block) this, FireBlock.getFireTickDelay(world.random)); -+ // Paper start - ItemActionContext param -+ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { -+ super.onPlace(iblockdata, world, blockposition, iblockdata1, flag, itemActionContext); -+ // Paper end -+ world.scheduleTick(blockposition, this, getFireTickDelay(world.random)); - } - - private static int getFireTickDelay(RandomSource random) { -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index ad38a7ced7f3dc05fb3d133e9da39f0a5eb0915b..7117dd220c393163fd5be77e332e56a34c667670 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -35,6 +35,7 @@ import net.minecraft.world.item.DyeColor; - import net.minecraft.world.item.Item; - import net.minecraft.world.item.ItemStack; - import net.minecraft.world.item.context.BlockPlaceContext; -+import net.minecraft.world.item.context.UseOnContext; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.EmptyBlockGetter; - import net.minecraft.world.level.Level; -@@ -136,6 +137,12 @@ public abstract class BlockBehaviour { - DebugPackets.sendNeighborsUpdatePacket(world, pos); - } - -+ // Paper start - add ItemActionContext param -+ @Deprecated -+ public void onPlace(BlockState iblockdata, Level world, BlockPos blockposition, BlockState iblockdata1, boolean flag, UseOnContext itemActionContext) { -+ this.onPlace(iblockdata, world, blockposition, iblockdata1, flag); -+ } -+ // Paper end - /** @deprecated */ - @Deprecated - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -index 768c39b265437641721d669d6aa85b3db49e5422..3414f3190e1a760c602613e82e551e797c3aa575 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -@@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; - import net.minecraft.tags.BlockTags; - import net.minecraft.util.Mth; - import net.minecraft.world.entity.EntityDimensions; -+import net.minecraft.world.item.context.UseOnContext; - import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.NetherPortalBlock; -@@ -184,7 +185,10 @@ public class PortalShape { - } - - // CraftBukkit start - return boolean -- public boolean createPortalBlocks() { -+ // Paper start - ItemActionContext param -+ @Deprecated public boolean createPortalBlocks() { return this.createPortalBlocks(null); } -+ public boolean createPortalBlocks(UseOnContext itemActionContext) { -+ // Paper end - org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld(); - - // Copy below for loop -@@ -194,7 +198,7 @@ public class PortalShape { - this.blocks.setBlock(blockposition, iblockdata, 18); - }); - -- PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) this.blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE); -+ PortalCreateEvent event = new PortalCreateEvent((java.util.List) (java.util.List) blocks.getList(), bworld, itemActionContext == null || itemActionContext.getPlayer() == null ? null : itemActionContext.getPlayer().getBukkitEntity(), PortalCreateEvent.CreateReason.FIRE); // Paper - pass entity param - this.level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event); - - if (event.isCancelled()) { diff --git a/patches/server/0491-Add-more-Evoker-API.patch b/patches/server/0491-Add-more-Evoker-API.patch new file mode 100644 index 0000000000..367e900e25 --- /dev/null +++ b/patches/server/0491-Add-more-Evoker-API.patch @@ -0,0 +1,35 @@ +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/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java +index 91d07e6996e315734689ea25336992b0ed21cf25..7e861636710aa44ed36e7f20c6320dabb809c35d 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.Sheep; + import net.minecraft.world.entity.monster.SpellcasterIllager; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.EntityType; +@@ -35,4 +36,17 @@ public class CraftEvoker extends CraftSpellcaster implements Evoker { + public void setCurrentSpell(Evoker.Spell spell) { + this.getHandle().setIsCastingSpell(spell == null ? SpellcasterIllager.IllagerSpell.NONE : SpellcasterIllager.IllagerSpell.byId(spell.ordinal())); + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.entity.Sheep getWololoTarget() { ++ Sheep 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/0491-Fix-CraftTeam-null-check.patch b/patches/server/0491-Fix-CraftTeam-null-check.patch deleted file mode 100644 index fc8ca37076..0000000000 --- a/patches/server/0491-Fix-CraftTeam-null-check.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 18d5a26c34c848241c306241b3ad9825b5a0b9a9..60b1cffdccde4715832546d6edbf206fbab4e82f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -@@ -261,7 +261,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 = this.checkState(); - diff --git a/patches/server/0492-Add-methods-to-get-translation-keys.patch b/patches/server/0492-Add-methods-to-get-translation-keys.patch new file mode 100644 index 0000000000..5a5b0a4cd6 --- /dev/null +++ b/patches/server/0492-Add-methods-to-get-translation-keys.patch @@ -0,0 +1,174 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 11 Aug 2020 19:16:09 +0200 +Subject: [PATCH] Add methods to get translation keys + +Co-authored-by: MeFisto94 + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index ea3358905dcd39a1a855d98570457c65dcd7513d..396fb96abed1e77893d8ee8a15cff18cad804475 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -668,5 +668,15 @@ public class CraftBlock implements Block { + public org.bukkit.SoundGroup getBlockSoundGroup() { + return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); + } ++ ++ @Override ++ public String getTranslationKey() { ++ return this.translationKey(); ++ } ++ ++ @Override ++ public String translationKey() { ++ return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); ++ } + // 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 a859a675b4bc543e139358223cc92ad5eee3ddb5..31a22f26070059e5379730c1940ff1c5fb109be1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -194,6 +194,11 @@ public class CraftEnchantment extends Enchantment { + public net.kyori.adventure.text.Component displayName(int level) { + return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getFullname(level)); + } ++ ++ @Override ++ public String translationKey() { ++ return this.target.getDescriptionId(); ++ } + // Paper end + + public net.minecraft.world.item.enchantment.Enchantment getHandle() { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 6fefc65c6f9364d71e4e410972dfd79d97fdae3e..c65cc24fbd8b6cb9828fbc978c21c3f7ef9171a3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -478,6 +478,30 @@ public final class CraftMagicNumbers implements UnsafeValues { + Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); + return compound; + } ++ ++ @Override ++ public String getTranslationKey(Material mat) { ++ if (mat.isBlock()) { ++ return getBlock(mat).getDescriptionId(); ++ } ++ return getItem(mat).getDescriptionId(); ++ } ++ ++ @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.EntityType.byString(type.getName()).map(net.minecraft.world.entity.EntityType::getDescriptionId).orElse(null); ++ } ++ ++ @Override ++ public String getTranslationKey(org.bukkit.inventory.ItemStack itemStack) { ++ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); ++ return nmsItemStack.getItem().getDescriptionId(nmsItemStack); ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +index fab3063fffa959ac7f0eb5937f2fae94d11b6591..9292a0119499d14c9ed170999ac3b8dfdd1f839a 100644 +--- a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java ++++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java +@@ -1,12 +1,29 @@ + package io.papermc.paper.world; + + import com.destroystokyo.paper.ClientOption; ++import net.minecraft.core.Registry; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.network.chat.contents.TranslatableContents; ++import net.minecraft.resources.ResourceKey; + import net.minecraft.world.entity.player.ChatVisiblity; ++import net.minecraft.world.item.CreativeModeTab; ++import net.minecraft.world.level.GameType; ++import net.minecraft.world.level.biome.Biome; + import org.bukkit.Difficulty; ++import org.bukkit.FireworkEffect; ++import org.bukkit.GameMode; ++import org.bukkit.GameRule; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.craftbukkit.inventory.CraftCreativeCategory; ++import org.bukkit.inventory.CreativeCategory; ++import org.bukkit.support.AbstractTestingBase; + import org.junit.Assert; + import org.junit.Test; + +-public class TranslationKeyTest { ++import java.util.Map; ++import java.util.Objects; ++ ++public class TranslationKeyTest extends AbstractTestingBase { + + @Test + public void testChatVisibilityKeys() { +@@ -15,4 +32,60 @@ public class TranslationKeyTest { + Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey()); + } + } ++ ++ @Test ++ public void testDifficultyKeys() { ++ for (Difficulty bukkitDifficulty : Difficulty.values()) { ++ Assert.assertEquals(bukkitDifficulty + "'s translation key doesn't match", ((TranslatableContents) net.minecraft.world.Difficulty.byId(bukkitDifficulty.ordinal()).getDisplayName().getContents()).getKey(), bukkitDifficulty.translationKey()); ++ } ++ } ++ ++ @Test ++ public void testGameruleKeys() { ++ for (GameRule rule : GameRule.values()) { ++ Assert.assertEquals(rule.getName() + "'s translation doesn't match", org.bukkit.craftbukkit.CraftWorld.getGameRulesNMS().get(rule.getName()).getDescriptionId(), rule.translationKey()); ++ } ++ } ++ ++ @Test ++ public void testAttributeKeys() { ++ for (Attribute attribute : Attribute.values()) { ++ Assert.assertEquals("translation key mismatch for " + attribute, org.bukkit.craftbukkit.attribute.CraftAttributeMap.toMinecraft(attribute).getDescriptionId(), attribute.translationKey()); ++ } ++ } ++ ++ @Test ++ public void testFireworkEffectType() { ++ for (FireworkEffect.Type type : FireworkEffect.Type.values()) { ++ Assert.assertEquals("translation key mismatch for " + type, net.minecraft.world.item.FireworkRocketItem.Shape.byId(org.bukkit.craftbukkit.inventory.CraftMetaFirework.getNBT(type)).getName(), org.bukkit.FireworkEffect.Type.NAMES.key(type)); ++ } ++ } ++ ++ @Test ++ public void testCreativeCategory() { ++ for (CreativeModeTab tab : CreativeModeTab.TABS) { ++ if (tab == CreativeModeTab.TAB_SEARCH || tab == CreativeModeTab.TAB_HOTBAR || tab == CreativeModeTab.TAB_INVENTORY) { // not implemented in the api ++ continue; ++ } ++ CreativeCategory category = Objects.requireNonNull(CraftCreativeCategory.fromNMS(tab)); ++ Assert.assertEquals("translation key mismatch for " + category, ((TranslatableContents) tab.getDisplayName().getContents()).getKey(), category.translationKey()); ++ } ++ } ++ ++ @Test ++ public void testGameMode() { ++ for (GameType nms : GameType.values()) { ++ GameMode bukkit = GameMode.getByValue(nms.getId()); ++ Assert.assertNotNull(bukkit); ++ Assert.assertEquals("translation key mismatch for " + bukkit, ((TranslatableContents) nms.getLongDisplayName().getContents()).getKey(), bukkit.translationKey()); ++ } ++ } ++ ++ @Test ++ public void testBiome() { ++ for (Map.Entry, Biome> nms : RegistryAccess.builtinCopy().registry(Registry.BIOME_REGISTRY).get().entrySet()) { ++ org.bukkit.block.Biome bukkit = org.bukkit.block.Biome.valueOf(nms.getKey().location().getPath().toUpperCase()); ++ Assert.assertEquals("translation key mismatch for " + bukkit, nms.getKey().location().toLanguageKey("biome"), bukkit.translationKey()); ++ } ++ } + } diff --git a/patches/server/0492-Add-more-Evoker-API.patch b/patches/server/0492-Add-more-Evoker-API.patch deleted file mode 100644 index 367e900e25..0000000000 --- a/patches/server/0492-Add-more-Evoker-API.patch +++ /dev/null @@ -1,35 +0,0 @@ -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/org/bukkit/craftbukkit/entity/CraftEvoker.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEvoker.java -index 91d07e6996e315734689ea25336992b0ed21cf25..7e861636710aa44ed36e7f20c6320dabb809c35d 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.Sheep; - import net.minecraft.world.entity.monster.SpellcasterIllager; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.EntityType; -@@ -35,4 +36,17 @@ public class CraftEvoker extends CraftSpellcaster implements Evoker { - public void setCurrentSpell(Evoker.Spell spell) { - this.getHandle().setIsCastingSpell(spell == null ? SpellcasterIllager.IllagerSpell.NONE : SpellcasterIllager.IllagerSpell.byId(spell.ordinal())); - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.entity.Sheep getWololoTarget() { -+ Sheep 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/0493-Add-methods-to-get-translation-keys.patch b/patches/server/0493-Add-methods-to-get-translation-keys.patch deleted file mode 100644 index 5a5b0a4cd6..0000000000 --- a/patches/server/0493-Add-methods-to-get-translation-keys.patch +++ /dev/null @@ -1,174 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 11 Aug 2020 19:16:09 +0200 -Subject: [PATCH] Add methods to get translation keys - -Co-authored-by: MeFisto94 - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index ea3358905dcd39a1a855d98570457c65dcd7513d..396fb96abed1e77893d8ee8a15cff18cad804475 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -668,5 +668,15 @@ public class CraftBlock implements Block { - public org.bukkit.SoundGroup getBlockSoundGroup() { - return org.bukkit.craftbukkit.CraftSoundGroup.getSoundGroup(this.getNMS().getSoundType()); - } -+ -+ @Override -+ public String getTranslationKey() { -+ return this.translationKey(); -+ } -+ -+ @Override -+ public String translationKey() { -+ return org.bukkit.Bukkit.getUnsafe().getTranslationKey(this); -+ } - // 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 a859a675b4bc543e139358223cc92ad5eee3ddb5..31a22f26070059e5379730c1940ff1c5fb109be1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -@@ -194,6 +194,11 @@ public class CraftEnchantment extends Enchantment { - public net.kyori.adventure.text.Component displayName(int level) { - return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getFullname(level)); - } -+ -+ @Override -+ public String translationKey() { -+ return this.target.getDescriptionId(); -+ } - // Paper end - - public net.minecraft.world.item.enchantment.Enchantment getHandle() { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 6fefc65c6f9364d71e4e410972dfd79d97fdae3e..c65cc24fbd8b6cb9828fbc978c21c3f7ef9171a3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -478,6 +478,30 @@ public final class CraftMagicNumbers implements UnsafeValues { - Preconditions.checkArgument(dataVersion <= getDataVersion(), "Newer version! Server downgrades are not supported!"); - return compound; - } -+ -+ @Override -+ public String getTranslationKey(Material mat) { -+ if (mat.isBlock()) { -+ return getBlock(mat).getDescriptionId(); -+ } -+ return getItem(mat).getDescriptionId(); -+ } -+ -+ @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.EntityType.byString(type.getName()).map(net.minecraft.world.entity.EntityType::getDescriptionId).orElse(null); -+ } -+ -+ @Override -+ public String getTranslationKey(org.bukkit.inventory.ItemStack itemStack) { -+ net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); -+ return nmsItemStack.getItem().getDescriptionId(nmsItemStack); -+ } - // Paper end - - /** -diff --git a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -index fab3063fffa959ac7f0eb5937f2fae94d11b6591..9292a0119499d14c9ed170999ac3b8dfdd1f839a 100644 ---- a/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -+++ b/src/test/java/io/papermc/paper/world/TranslationKeyTest.java -@@ -1,12 +1,29 @@ - package io.papermc.paper.world; - - import com.destroystokyo.paper.ClientOption; -+import net.minecraft.core.Registry; -+import net.minecraft.core.RegistryAccess; -+import net.minecraft.network.chat.contents.TranslatableContents; -+import net.minecraft.resources.ResourceKey; - import net.minecraft.world.entity.player.ChatVisiblity; -+import net.minecraft.world.item.CreativeModeTab; -+import net.minecraft.world.level.GameType; -+import net.minecraft.world.level.biome.Biome; - import org.bukkit.Difficulty; -+import org.bukkit.FireworkEffect; -+import org.bukkit.GameMode; -+import org.bukkit.GameRule; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.craftbukkit.inventory.CraftCreativeCategory; -+import org.bukkit.inventory.CreativeCategory; -+import org.bukkit.support.AbstractTestingBase; - import org.junit.Assert; - import org.junit.Test; - --public class TranslationKeyTest { -+import java.util.Map; -+import java.util.Objects; -+ -+public class TranslationKeyTest extends AbstractTestingBase { - - @Test - public void testChatVisibilityKeys() { -@@ -15,4 +32,60 @@ public class TranslationKeyTest { - Assert.assertEquals(chatVisibility + "'s translation key doesn't match", ChatVisiblity.valueOf(chatVisibility.name()).getKey(), chatVisibility.translationKey()); - } - } -+ -+ @Test -+ public void testDifficultyKeys() { -+ for (Difficulty bukkitDifficulty : Difficulty.values()) { -+ Assert.assertEquals(bukkitDifficulty + "'s translation key doesn't match", ((TranslatableContents) net.minecraft.world.Difficulty.byId(bukkitDifficulty.ordinal()).getDisplayName().getContents()).getKey(), bukkitDifficulty.translationKey()); -+ } -+ } -+ -+ @Test -+ public void testGameruleKeys() { -+ for (GameRule rule : GameRule.values()) { -+ Assert.assertEquals(rule.getName() + "'s translation doesn't match", org.bukkit.craftbukkit.CraftWorld.getGameRulesNMS().get(rule.getName()).getDescriptionId(), rule.translationKey()); -+ } -+ } -+ -+ @Test -+ public void testAttributeKeys() { -+ for (Attribute attribute : Attribute.values()) { -+ Assert.assertEquals("translation key mismatch for " + attribute, org.bukkit.craftbukkit.attribute.CraftAttributeMap.toMinecraft(attribute).getDescriptionId(), attribute.translationKey()); -+ } -+ } -+ -+ @Test -+ public void testFireworkEffectType() { -+ for (FireworkEffect.Type type : FireworkEffect.Type.values()) { -+ Assert.assertEquals("translation key mismatch for " + type, net.minecraft.world.item.FireworkRocketItem.Shape.byId(org.bukkit.craftbukkit.inventory.CraftMetaFirework.getNBT(type)).getName(), org.bukkit.FireworkEffect.Type.NAMES.key(type)); -+ } -+ } -+ -+ @Test -+ public void testCreativeCategory() { -+ for (CreativeModeTab tab : CreativeModeTab.TABS) { -+ if (tab == CreativeModeTab.TAB_SEARCH || tab == CreativeModeTab.TAB_HOTBAR || tab == CreativeModeTab.TAB_INVENTORY) { // not implemented in the api -+ continue; -+ } -+ CreativeCategory category = Objects.requireNonNull(CraftCreativeCategory.fromNMS(tab)); -+ Assert.assertEquals("translation key mismatch for " + category, ((TranslatableContents) tab.getDisplayName().getContents()).getKey(), category.translationKey()); -+ } -+ } -+ -+ @Test -+ public void testGameMode() { -+ for (GameType nms : GameType.values()) { -+ GameMode bukkit = GameMode.getByValue(nms.getId()); -+ Assert.assertNotNull(bukkit); -+ Assert.assertEquals("translation key mismatch for " + bukkit, ((TranslatableContents) nms.getLongDisplayName().getContents()).getKey(), bukkit.translationKey()); -+ } -+ } -+ -+ @Test -+ public void testBiome() { -+ for (Map.Entry, Biome> nms : RegistryAccess.builtinCopy().registry(Registry.BIOME_REGISTRY).get().entrySet()) { -+ org.bukkit.block.Biome bukkit = org.bukkit.block.Biome.valueOf(nms.getKey().location().getPath().toUpperCase()); -+ Assert.assertEquals("translation key mismatch for " + bukkit, nms.getKey().location().toLanguageKey("biome"), bukkit.translationKey()); -+ } -+ } - } diff --git a/patches/server/0493-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server/0493-Create-HoverEvent-from-ItemStack-Entity.patch new file mode 100644 index 0000000000..238466b430 --- /dev/null +++ b/patches/server/0493-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 f05923a6292f02a01791627abce2770daff24b6a..f3a6a4d97b5be2e75c438a6f7010a8f588afe86c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -405,5 +405,40 @@ public final class CraftItemFactory implements ItemFactory { + + return nms != null ? net.minecraft.locale.Language.getInstance().getOrDefault(nms.getItem().getDescriptionId(nms)) : 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/0494-Cache-block-data-strings.patch b/patches/server/0494-Cache-block-data-strings.patch new file mode 100644 index 0000000000..6258b9db9a --- /dev/null +++ b/patches/server/0494-Cache-block-data-strings.patch @@ -0,0 +1,62 @@ +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 48650bc1c09b18f1b57d9828dfe27f51c74c4a75..dbe70160ac703e74613ac65bd62950a5cc951d4c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2032,6 +2032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop%s", nms, bukkit); + } + ++ // Paper start - cache block data strings ++ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ static { ++ // cache all of the default states at startup, will not cache ones with the custom states inside of the ++ // brackets in a different order, though ++ reloadCache(); ++ } ++ ++ public static void reloadCache() { ++ stringDataCache.clear(); ++ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); ++ } ++ // Paper end ++ + 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) { ++ net.minecraft.resources.ResourceLocation key = Registry.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 + BlockState blockData; + Block block = CraftMagicNumbers.getBlock(material); + Map, Comparable> parsed = null; diff --git a/patches/server/0494-Create-HoverEvent-from-ItemStack-Entity.patch b/patches/server/0494-Create-HoverEvent-from-ItemStack-Entity.patch deleted file mode 100644 index 238466b430..0000000000 --- a/patches/server/0494-Create-HoverEvent-from-ItemStack-Entity.patch +++ /dev/null @@ -1,51 +0,0 @@ -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 f05923a6292f02a01791627abce2770daff24b6a..f3a6a4d97b5be2e75c438a6f7010a8f588afe86c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -405,5 +405,40 @@ public final class CraftItemFactory implements ItemFactory { - - return nms != null ? net.minecraft.locale.Language.getInstance().getOrDefault(nms.getItem().getDescriptionId(nms)) : 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/0495-Cache-block-data-strings.patch b/patches/server/0495-Cache-block-data-strings.patch deleted file mode 100644 index 6258b9db9a..0000000000 --- a/patches/server/0495-Cache-block-data-strings.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: miclebrick -Date: Thu, 6 Dec 2018 19:52:50 -0500 -Subject: [PATCH] Cache block data strings - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 48650bc1c09b18f1b57d9828dfe27f51c74c4a75..dbe70160ac703e74613ac65bd62950a5cc951d4c 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2032,6 +2032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop%s", nms, bukkit); - } - -+ // Paper start - cache block data strings -+ private static Map stringDataCache = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ static { -+ // cache all of the default states at startup, will not cache ones with the custom states inside of the -+ // brackets in a different order, though -+ reloadCache(); -+ } -+ -+ public static void reloadCache() { -+ stringDataCache.clear(); -+ Block.BLOCK_STATE_REGISTRY.forEach(blockData -> stringDataCache.put(blockData.toString(), blockData.createCraftBlockData())); -+ } -+ // Paper end -+ - 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) { -+ net.minecraft.resources.ResourceLocation key = Registry.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 - BlockState blockData; - Block block = CraftMagicNumbers.getBlock(material); - Map, Comparable> parsed = null; diff --git a/patches/server/0495-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0495-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch new file mode 100644 index 0000000000..ec03ac9e56 --- /dev/null +++ b/patches/server/0495-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Tue, 25 Aug 2020 20:45:36 -0400 +Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported + +Uses correct setPositionRotation for Entity teleporting instead of setLocation +as this is how Vanilla teleports entities. + +Cancel any pending motion when teleported. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index da6a0171bd63ac68635de1c23fc9eafa732503bd..214771e661ca3303af167fda3b623d83f0f63055 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -760,7 +760,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + return; + } + +- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); ++ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - use proper moveTo for teleportation + this.lastGoodX = this.awaitingPositionFromClient.x; + this.lastGoodY = this.awaitingPositionFromClient.y; + this.lastGoodZ = this.awaitingPositionFromClient.z; +@@ -1676,7 +1676,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // CraftBukkit end + + this.awaitingTeleportTime = this.tickCount; +- this.player.absMoveTo(d0, d1, d2, f, f1); ++ this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper moveTo for teleportation + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 3ebe5297aa2fde1cb347f738738f5d74f6a61b9a..332bce00d355981c657993b504767814dfdd7f05 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 Nameable, EntityAccess, CommandSource { + + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; ++ public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation + static boolean isLevelAtLeast(CompoundTag tag, int level) { + return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } +@@ -1665,6 +1666,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void moveTo(double x, double y, double z, float yaw, float pitch) { ++ // Paper - cancel entity velocity if teleported ++ if (!preserveMotion) { ++ this.deltaMovement = Vec3.ZERO; ++ } else { ++ this.preserveMotion = false; ++ } ++ // Paper end + this.setPosRaw(x, y, z); + this.setYRot(yaw); + this.setXRot(pitch); +diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java +index e3d8814cbad30da795632afddf8ebc87eff72106..ee619590aa49323059947fbaee9e88d61df99789 100644 +--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java ++++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java +@@ -163,6 +163,7 @@ public abstract class BaseSpawner { + return; + } + ++ entity.preserveMotion = true; // Paper - preserve entity motion from tag + entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); + if (entity instanceof Mob) { + Mob entityinsentient = (Mob) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index c2282592a3e5c8e08acb30a8fe6f3a83dfe6d93d..40e6cd06f6b0cab2718c165cb38e772e90795917 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -561,7 +561,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + // entity.setLocation() throws no event, and so cannot be cancelled +- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); ++ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting + // SPIGOT-619: Force sync head rotation also + this.entity.setYHeadRot(location.getYaw()); + diff --git a/patches/server/0496-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server/0496-Add-additional-open-container-api-to-HumanEntity.patch new file mode 100644 index 0000000000..1c28c3f9cd --- /dev/null +++ b/patches/server/0496-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 15f6e1c04fefa1334301534646b8ed2535d16fa9..b8ba4e278de5c9a591789928c21bff26dcec2fb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -462,6 +462,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return this.getHandle().containerMenu.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().openMenu(block.getMenuProvider(null, getHandle().level, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); ++ getHandle().containerMenu.checkReachable = !force; ++ return getHandle().containerMenu.getBukkitView(); ++ } ++ // Paper end ++ + @Override + public void closeInventory() { + // Paper start diff --git a/patches/server/0496-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0496-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch deleted file mode 100644 index 66b1d24e2d..0000000000 --- a/patches/server/0496-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Tue, 25 Aug 2020 20:45:36 -0400 -Subject: [PATCH] Fix Entity Teleportation and cancel velocity if teleported - -Uses correct setPositionRotation for Entity teleporting instead of setLocation -as this is how Vanilla teleports entities. - -Cancel any pending motion when teleported. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ecd3d2cb0a4da4360c420f8c733a5898c54ba72e..70ef7e4a86cc080ff47dcc648088bf007d6ddee6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -760,7 +760,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - return; - } - -- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); -+ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - use proper moveTo for teleportation - this.lastGoodX = this.awaitingPositionFromClient.x; - this.lastGoodY = this.awaitingPositionFromClient.y; - this.lastGoodZ = this.awaitingPositionFromClient.z; -@@ -1676,7 +1676,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // CraftBukkit end - - this.awaitingTeleportTime = this.tickCount; -- this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper moveTo for teleportation - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7e36e53d44b5efbd6498caecb717bec1dcbec96d..1ed2670ab0687793b9298c2c1deb92da93e7bfd2 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 Nameable, EntityAccess, CommandSource { - - // CraftBukkit start - private static final int CURRENT_LEVEL = 2; -+ public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation - static boolean isLevelAtLeast(CompoundTag tag, int level) { - return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; - } -@@ -1651,6 +1652,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public void moveTo(double x, double y, double z, float yaw, float pitch) { -+ // Paper - cancel entity velocity if teleported -+ if (!preserveMotion) { -+ this.deltaMovement = Vec3.ZERO; -+ } else { -+ this.preserveMotion = false; -+ } -+ // Paper end - this.setPosRaw(x, y, z); - this.setYRot(yaw); - this.setXRot(pitch); -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index e3d8814cbad30da795632afddf8ebc87eff72106..ee619590aa49323059947fbaee9e88d61df99789 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -163,6 +163,7 @@ public abstract class BaseSpawner { - return; - } - -+ entity.preserveMotion = true; // Paper - preserve entity motion from tag - entity.moveTo(entity.getX(), entity.getY(), entity.getZ(), randomsource.nextFloat() * 360.0F, 0.0F); - if (entity instanceof Mob) { - Mob entityinsentient = (Mob) entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 863cd2c84dd207f984ddad977e9fd23860247c68..ab270a14263d6a264bb74de3b924584ac41ed523 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -559,7 +559,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - - // entity.setLocation() throws no event, and so cannot be cancelled -- this.entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); -+ entity.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // Paper - use proper moveTo, as per vanilla teleporting - // SPIGOT-619: Force sync head rotation also - this.entity.setYHeadRot(location.getYaw()); - diff --git a/patches/server/0497-Add-additional-open-container-api-to-HumanEntity.patch b/patches/server/0497-Add-additional-open-container-api-to-HumanEntity.patch deleted file mode 100644 index 53f2348da6..0000000000 --- a/patches/server/0497-Add-additional-open-container-api-to-HumanEntity.patch +++ /dev/null @@ -1,81 +0,0 @@ -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 3a14cc3d0d692c8bbc90de1b1c5731158b1323e5..acf609e8d880156ba980b701816c475166c27bdb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -459,6 +459,70 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return this.getHandle().containerMenu.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().openMenu(block.getMenuProvider(null, getHandle().level, new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()))); -+ getHandle().containerMenu.checkReachable = !force; -+ return getHandle().containerMenu.getBukkitView(); -+ } -+ // Paper end -+ - @Override - public void closeInventory() { - // Paper start diff --git a/patches/server/0497-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server/0497-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch new file mode 100644 index 0000000000..602cc3a878 --- /dev/null +++ b/patches/server/0497-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 d25f106ab64c90438f521a2c6fa944bdedc1969a..3d5e52d997a8e7d7f3b000e3737d30762aae2ca1 100644 +--- a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java ++++ b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java +@@ -28,8 +28,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; + } + +@@ -74,6 +76,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()) { + final CompletableFuture future = CompletableFuture.runAsync(() -> { diff --git a/patches/server/0498-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch b/patches/server/0498-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch deleted file mode 100644 index 602cc3a878..0000000000 --- a/patches/server/0498-Cache-DataFixerUpper-Rewrite-Rules-on-demand.patch +++ /dev/null @@ -1,54 +0,0 @@ -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 d25f106ab64c90438f521a2c6fa944bdedc1969a..3d5e52d997a8e7d7f3b000e3737d30762aae2ca1 100644 ---- a/src/main/java/com/mojang/datafixers/DataFixerBuilder.java -+++ b/src/main/java/com/mojang/datafixers/DataFixerBuilder.java -@@ -28,8 +28,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; - } - -@@ -74,6 +76,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()) { - final CompletableFuture future = CompletableFuture.runAsync(() -> { diff --git a/patches/server/0498-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0498-Extend-block-drop-capture-to-capture-all-items-added.patch new file mode 100644 index 0000000000..9009d631f8 --- /dev/null +++ b/patches/server/0498-Extend-block-drop-capture-to-capture-all-items-added.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 17 Sep 2020 00:36:05 +0100 +Subject: [PATCH] Extend block drop capture to capture all items added to the + world + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 2ba432f7be370ce1a5861e90de9541d76acdcfd2..85c0a80f00f5968b684b3609fbe56197e4aeb202 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1292,6 +1292,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ return true; ++ } ++ // Paper end + // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. + if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { + return false; +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 431ff490760f54be76847c7b370dbbb4b65de102..1b45c1483a7ebad47162483b51036f9dfcdf62f6 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -428,10 +428,12 @@ public class ServerPlayerGameMode { + // return true; // CraftBukkit + } + // CraftBukkit start ++ java.util.List itemsToDrop = level.captureDrops; // Paper - store current list ++ level.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, level.captureDrops); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - use stored ref + } +- level.captureDrops = null; ++ //world.captureDrops = null; // Paper - move up + + // Drop event experience + if (flag && event != null) { diff --git a/patches/server/0499-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/patches/server/0499-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch new file mode 100644 index 0000000000..89aadfda9c --- /dev/null +++ b/patches/server/0499-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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index a699107b1afea1c52e5a7e93af8f39ae9e1955b3..c4046b364d1896b781e23c92b241ec73c239d3a0 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -250,6 +250,7 @@ public class ChunkHolder { + } + + public void blockChanged(BlockPos pos) { ++ if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks + LevelChunk chunk = this.getTickingChunk(); + + if (chunk != null) { diff --git a/patches/server/0499-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0499-Extend-block-drop-capture-to-capture-all-items-added.patch deleted file mode 100644 index 9009d631f8..0000000000 --- a/patches/server/0499-Extend-block-drop-capture-to-capture-all-items-added.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 17 Sep 2020 00:36:05 +0100 -Subject: [PATCH] Extend block drop capture to capture all items added to the - world - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 2ba432f7be370ce1a5861e90de9541d76acdcfd2..85c0a80f00f5968b684b3609fbe56197e4aeb202 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1292,6 +1292,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ // Paper start - capture all item additions to the world -+ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ return true; -+ } -+ // Paper end - // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. - if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { - return false; -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 431ff490760f54be76847c7b370dbbb4b65de102..1b45c1483a7ebad47162483b51036f9dfcdf62f6 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -428,10 +428,12 @@ public class ServerPlayerGameMode { - // return true; // CraftBukkit - } - // CraftBukkit start -+ java.util.List itemsToDrop = level.captureDrops; // Paper - store current list -+ level.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, level.captureDrops); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - use stored ref - } -- level.captureDrops = null; -+ //world.captureDrops = null; // Paper - move up - - // Drop event experience - if (flag && event != null) { diff --git a/patches/server/0500-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/patches/server/0500-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch deleted file mode 100644 index 89aadfda9c..0000000000 --- a/patches/server/0500-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index a699107b1afea1c52e5a7e93af8f39ae9e1955b3..c4046b364d1896b781e23c92b241ec73c239d3a0 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -250,6 +250,7 @@ public class ChunkHolder { - } - - public void blockChanged(BlockPos pos) { -+ if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks - LevelChunk chunk = this.getTickingChunk(); - - if (chunk != null) { diff --git a/patches/server/0500-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0500-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch new file mode 100644 index 0000000000..7c7dcd77b6 --- /dev/null +++ b/patches/server/0500-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 332bce00d355981c657993b504767814dfdd7f05..71c674d7b8dfdd401f163cc707f2c9746da30c65 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4088,4 +4088,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + void accept(Entity entity, double x, double y, double z); + } ++ ++ // Paper start ++ public static int nextEntityId() { ++ return ENTITY_COUNTER.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 c65cc24fbd8b6cb9828fbc978c21c3f7ef9171a3..346f5f4b2afec3127c5d1b8e054eaacb1cb756e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -502,6 +502,10 @@ public final class CraftMagicNumbers implements UnsafeValues { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + return nmsItemStack.getItem().getDescriptionId(nmsItemStack); + } ++ ++ public int nextEntityId() { ++ return net.minecraft.world.entity.Entity.nextEntityId(); ++ } + // Paper end + + /** diff --git a/patches/server/0501-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch b/patches/server/0501-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch deleted file mode 100644 index be65d53303..0000000000 --- a/patches/server/0501-Expose-the-Entity-Counter-to-allow-plugins-to-use-va.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MeFisto94 -Date: Fri, 28 Aug 2020 01:41:26 +0200 -Subject: [PATCH] Expose the Entity Counter to allow plugins to use valid and - non-conflicting Entity Ids - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1ed2670ab0687793b9298c2c1deb92da93e7bfd2..511255467688d4c9397037753d2d4821af29bb79 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4074,4 +4074,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - - void accept(Entity entity, double x, double y, double z); - } -+ -+ // Paper start -+ public static int nextEntityId() { -+ return ENTITY_COUNTER.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 c65cc24fbd8b6cb9828fbc978c21c3f7ef9171a3..346f5f4b2afec3127c5d1b8e054eaacb1cb756e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -502,6 +502,10 @@ public final class CraftMagicNumbers implements UnsafeValues { - net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); - return nmsItemStack.getItem().getDescriptionId(nmsItemStack); - } -+ -+ public int nextEntityId() { -+ return net.minecraft.world.entity.Entity.nextEntityId(); -+ } - // Paper end - - /** diff --git a/patches/server/0501-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server/0501-Lazily-track-plugin-scoreboards-by-default.patch new file mode 100644 index 0000000000..bb0fe1c990 --- /dev/null +++ b/patches/server/0501-Lazily-track-plugin-scoreboards-by-default.patch @@ -0,0 +1,76 @@ +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/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index f367261b119ab48c1d17b2b6552cce481c6effbb..a038ede38ebb507d6a0182a4a34f2b0722ef024e 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; +@@ -52,6 +53,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + Validate.notNull(renderType, "RenderType cannot be null"); + Validate.isTrue(name.length() <= Short.MAX_VALUE, "The name '" + name + "' is longer than the limit of 32767 characters"); + Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); ++ // 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).criteria != net.minecraft.world.scores.criteria.ObjectiveCriteria.DUMMY && !registeredGlobally) { ++ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); ++ registeredGlobally = true; ++ } ++ // Paper end + net.minecraft.world.scores.Objective objective = board.addObjective(name, ((CraftCriteria) criteria).criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); + return new CraftObjective(this, objective); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index 71f73db28ca124a49434e9da9eb39d182b7421e2..39e19bea16419b9cbe53016084b8bbf014dcb056 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) { + this.mainScoreboard = new CraftScoreboard(scoreboardServer); ++ mainScoreboard.registeredGlobally = true; // Paper + this.server = minecraftserver; + this.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 ServerScoreboard(this.server)); +- this.scoreboards.add(scoreboard); ++ // Paper start ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().scoreboards.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 = this.playerBoards.get(player); diff --git a/patches/server/0502-Entity-isTicking.patch b/patches/server/0502-Entity-isTicking.patch new file mode 100644 index 0000000000..12f888c73e --- /dev/null +++ b/patches/server/0502-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 71c674d7b8dfdd401f163cc707f2c9746da30c65..902531525c213bd39aaac138f30ee1fc3e7c24a7 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -59,6 +59,7 @@ import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerChunkCache; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.TicketType; +@@ -4093,5 +4094,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public static int nextEntityId() { + return ENTITY_COUNTER.incrementAndGet(); + } ++ ++ public boolean isTicking() { ++ return ((ServerChunkCache) level.getChunkSource()).isPositionTicking(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 40e6cd06f6b0cab2718c165cb38e772e90795917..142e6be6a41500b6e3b49173b7432e63de7ad3da 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1295,5 +1295,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/0502-Lazily-track-plugin-scoreboards-by-default.patch b/patches/server/0502-Lazily-track-plugin-scoreboards-by-default.patch deleted file mode 100644 index bb0fe1c990..0000000000 --- a/patches/server/0502-Lazily-track-plugin-scoreboards-by-default.patch +++ /dev/null @@ -1,76 +0,0 @@ -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/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -index f367261b119ab48c1d17b2b6552cce481c6effbb..a038ede38ebb507d6a0182a4a34f2b0722ef024e 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; -@@ -52,6 +53,12 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { - Validate.notNull(renderType, "RenderType cannot be null"); - Validate.isTrue(name.length() <= Short.MAX_VALUE, "The name '" + name + "' is longer than the limit of 32767 characters"); - Validate.isTrue(board.getObjective(name) == null, "An objective of name '" + name + "' already exists"); -+ // 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).criteria != net.minecraft.world.scores.criteria.ObjectiveCriteria.DUMMY && !registeredGlobally) { -+ net.minecraft.server.MinecraftServer.getServer().server.getScoreboardManager().registerScoreboardForVanilla(this); -+ registeredGlobally = true; -+ } -+ // Paper end - net.minecraft.world.scores.Objective objective = board.addObjective(name, ((CraftCriteria) criteria).criteria, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName), CraftScoreboardTranslations.fromBukkitRender(renderType)); - return new CraftObjective(this, objective); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -index 71f73db28ca124a49434e9da9eb39d182b7421e2..39e19bea16419b9cbe53016084b8bbf014dcb056 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) { - this.mainScoreboard = new CraftScoreboard(scoreboardServer); -+ mainScoreboard.registeredGlobally = true; // Paper - this.server = minecraftserver; - this.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 ServerScoreboard(this.server)); -- this.scoreboards.add(scoreboard); -+ // Paper start -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().scoreboards.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 = this.playerBoards.get(player); diff --git a/patches/server/0503-Entity-isTicking.patch b/patches/server/0503-Entity-isTicking.patch deleted file mode 100644 index 86502ef7ae..0000000000 --- a/patches/server/0503-Entity-isTicking.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 21:39:16 -0500 -Subject: [PATCH] Entity#isTicking - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 511255467688d4c9397037753d2d4821af29bb79..532678194c2724e31a19f0e4d73d79d84ef6699c 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -59,6 +59,7 @@ import net.minecraft.resources.ResourceKey; - import net.minecraft.resources.ResourceLocation; - import net.minecraft.server.MCUtil; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerChunkCache; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.level.TicketType; -@@ -4079,5 +4080,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - public static int nextEntityId() { - return ENTITY_COUNTER.incrementAndGet(); - } -+ -+ public boolean isTicking() { -+ return ((ServerChunkCache) level.getChunkSource()).isPositionTicking(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 ab270a14263d6a264bb74de3b924584ac41ed523..fa1e996157fb3470c08669801e7482af70714b11 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1278,5 +1278,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/0503-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0503-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch new file mode 100644 index 0000000000..2af120114e --- /dev/null +++ b/patches/server/0503-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sat, 3 Oct 2020 22:00:27 -0500 +Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is + not enabled + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index dbe70160ac703e74613ac65bd62950a5cc951d4c..f02fc7d6d8d0aba84be5e85c8de0ccc0e5a01c78 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2098,13 +2098,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); + +- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { ++ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) + entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted")); + } + } diff --git a/patches/server/0504-Fix-Concurrency-issue-in-ShufflingList.patch b/patches/server/0504-Fix-Concurrency-issue-in-ShufflingList.patch new file mode 100644 index 0000000000..5b3dba224c --- /dev/null +++ b/patches/server/0504-Fix-Concurrency-issue-in-ShufflingList.patch @@ -0,0 +1,69 @@ +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 ShufflingList + +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/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +index c1f22c5e17418f91736237af1495a8a9910a61d5..5a5d454b5987bb01d03f91c15b7a6bff46f1de71 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -16,7 +16,7 @@ public class GateBehavior extends Behavior { + private final Set> exitErasedMemories; + private final GateBehavior.OrderPolicy orderPolicy; + private final GateBehavior.RunningPolicy runningPolicy; +- private final ShufflingList> behaviors = new ShufflingList<>(); ++ private final ShufflingList> behaviors = new ShufflingList<>(false); // Paper - don't use a clone + + public GateBehavior(Map, MemoryStatus> requiredMemoryState, Set> memoriesToForgetWhenStopped, GateBehavior.OrderPolicy order, GateBehavior.RunningPolicy runMode, List, Integer>> tasks) { + super(requiredMemoryState); +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +index 2af08836c60ddf0e1df7c53316eae6e329b5747f..090bba46b6ecd7d0e1415feb678b9b23264fe5e9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +@@ -14,12 +14,25 @@ import net.minecraft.util.RandomSource; + public class ShufflingList { + protected final List> entries; + private final RandomSource random = RandomSource.create(); ++ private final boolean isUnsafe; // Paper + + public ShufflingList() { ++ // Paper start ++ this(true); ++ } ++ public ShufflingList(boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end + this.entries = Lists.newArrayList(); + } + + private ShufflingList(List> list) { ++ // Paper start ++ this(list, true); ++ } ++ private ShufflingList(List> list, boolean isUnsafe) { ++ this.isUnsafe = isUnsafe; ++ // Paper end + this.entries = Lists.newArrayList(list); + } + +@@ -35,11 +48,12 @@ public class ShufflingList { + } + + public ShufflingList shuffle() { +- this.entries.forEach((entry) -> { +- entry.setRandom(this.random.nextFloat()); +- }); +- this.entries.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); +- return this; ++ // Paper start - make concurrent safe, work off a clone of the list ++ List> list = this.isUnsafe ? Lists.newArrayList(this.entries) : this.entries; ++ list.forEach(entry -> entry.setRandom(this.random.nextFloat())); ++ list.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); ++ return this.isUnsafe ? new ShufflingList<>(list, this.isUnsafe) : this; ++ // Paper end + } + + public Stream stream() { diff --git a/patches/server/0504-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch b/patches/server/0504-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch deleted file mode 100644 index 2af120114e..0000000000 --- a/patches/server/0504-Fix-deop-kicking-non-whitelisted-player-when-white-l.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sat, 3 Oct 2020 22:00:27 -0500 -Subject: [PATCH] Fix deop kicking non-whitelisted player when white list is - not enabled - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index dbe70160ac703e74613ac65bd62950a5cc951d4c..f02fc7d6d8d0aba84be5e85c8de0ccc0e5a01c78 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2098,13 +2098,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = Lists.newArrayList(playerlist.getPlayers()); - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- if (!whitelist.isWhiteListed(entityplayer.getGameProfile())) { -+ if (!whitelist.isWhiteListed(entityplayer.getGameProfile()) && !this.getPlayerList().isOp(entityplayer.getGameProfile())) { // Paper - Fix kicking ops when whitelist is reloaded (MC-171420) - entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.not_whitelisted")); - } - } diff --git a/patches/server/0505-Fix-Concurrency-issue-in-ShufflingList.patch b/patches/server/0505-Fix-Concurrency-issue-in-ShufflingList.patch deleted file mode 100644 index 5b3dba224c..0000000000 --- a/patches/server/0505-Fix-Concurrency-issue-in-ShufflingList.patch +++ /dev/null @@ -1,69 +0,0 @@ -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 ShufflingList - -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/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -index c1f22c5e17418f91736237af1495a8a9910a61d5..5a5d454b5987bb01d03f91c15b7a6bff46f1de71 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -@@ -16,7 +16,7 @@ public class GateBehavior extends Behavior { - private final Set> exitErasedMemories; - private final GateBehavior.OrderPolicy orderPolicy; - private final GateBehavior.RunningPolicy runningPolicy; -- private final ShufflingList> behaviors = new ShufflingList<>(); -+ private final ShufflingList> behaviors = new ShufflingList<>(false); // Paper - don't use a clone - - public GateBehavior(Map, MemoryStatus> requiredMemoryState, Set> memoriesToForgetWhenStopped, GateBehavior.OrderPolicy order, GateBehavior.RunningPolicy runMode, List, Integer>> tasks) { - super(requiredMemoryState); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -index 2af08836c60ddf0e1df7c53316eae6e329b5747f..090bba46b6ecd7d0e1415feb678b9b23264fe5e9 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -@@ -14,12 +14,25 @@ import net.minecraft.util.RandomSource; - public class ShufflingList { - protected final List> entries; - private final RandomSource random = RandomSource.create(); -+ private final boolean isUnsafe; // Paper - - public ShufflingList() { -+ // Paper start -+ this(true); -+ } -+ public ShufflingList(boolean isUnsafe) { -+ this.isUnsafe = isUnsafe; -+ // Paper end - this.entries = Lists.newArrayList(); - } - - private ShufflingList(List> list) { -+ // Paper start -+ this(list, true); -+ } -+ private ShufflingList(List> list, boolean isUnsafe) { -+ this.isUnsafe = isUnsafe; -+ // Paper end - this.entries = Lists.newArrayList(list); - } - -@@ -35,11 +48,12 @@ public class ShufflingList { - } - - public ShufflingList shuffle() { -- this.entries.forEach((entry) -> { -- entry.setRandom(this.random.nextFloat()); -- }); -- this.entries.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); -- return this; -+ // Paper start - make concurrent safe, work off a clone of the list -+ List> list = this.isUnsafe ? Lists.newArrayList(this.entries) : this.entries; -+ list.forEach(entry -> entry.setRandom(this.random.nextFloat())); -+ list.sort(Comparator.comparingDouble(ShufflingList.WeightedEntry::getRandWeight)); -+ return this.isUnsafe ? new ShufflingList<>(list, this.isUnsafe) : this; -+ // Paper end - } - - public Stream stream() { diff --git a/patches/server/0505-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0505-Reset-Ender-Crystals-on-Dragon-Spawn.patch new file mode 100644 index 0000000000..31cde5ee93 --- /dev/null +++ b/patches/server/0505-Reset-Ender-Crystals-on-Dragon-Spawn.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 1 Jun 2016 23:29:17 -0400 +Subject: [PATCH] Reset Ender Crystals on Dragon Spawn + +Crystals can end up in a bad state in certain conditions which causes +an exception on the expected number of crystals going negative. + +This ensures the crystals/pillars are in expected state when the dragon spawns. + +See #3522 + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 908cc3e2fc2cf4894a10081192a8b0d3c7e99932..f32906729baf83d8b53d64ab750cddcc413a2927 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -409,6 +409,7 @@ public class EndDragonFight { + enderDragon.moveTo(0.0D, 128.0D, 0.0D, this.level.random.nextFloat() * 360.0F, 0.0F); + this.level.addFreshEntity(enderDragon); + this.dragonUUID = enderDragon.getUUID(); ++ this.resetSpikeCrystals(); // Paper + return enderDragon; + } + diff --git a/patches/server/0506-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0506-Fix-for-large-move-vectors-crashing-server.patch new file mode 100644 index 0000000000..0b19e69619 --- /dev/null +++ b/patches/server/0506-Fix-for-large-move-vectors-crashing-server.patch @@ -0,0 +1,92 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 17 May 2020 23:47:33 -0700 +Subject: [PATCH] Fix for large move vectors crashing server + +Check movement distance also based on current position. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 214771e661ca3303af167fda3b623d83f0f63055..3d7d23a02e4aceb95ec36fbca9d02294f08c5780 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -575,9 +575,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.getLevel(); +- double d0 = entity.getX(); +- double d1 = entity.getY(); +- double d2 = entity.getZ(); ++ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER ++ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER ++ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER + double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER + double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER + double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER +@@ -587,8 +587,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + double d7 = d4 - this.vehicleFirstGoodY; + double d8 = d5 - this.vehicleFirstGoodZ; + double d9 = entity.getDeltaMovement().lengthSqr(); +- double d10 = d6 * d6 + d7 * d7 + d8 * d8; +- ++ // Paper start - fix large move vectors killing the server ++ double currDeltaX = toX - fromX; ++ double currDeltaY = toY - fromY; ++ double currDeltaZ = toZ - fromZ; ++ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ // Paper end - fix large move vectors killing the server ++ ++ // Paper start - fix large move vectors killing the server ++ double otherFieldX = d3 - this.vehicleLastGoodX; ++ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; ++ double otherFieldZ = d5 - this.vehicleLastGoodZ; ++ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + // CraftBukkit start - handle custom speeds and skipped ticks + this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; +@@ -634,9 +645,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- d6 = d3 - this.vehicleLastGoodX; +- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; +- d8 = d5 - this.vehicleLastGoodZ; ++ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above ++ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = entity.verticalCollisionBelow; + + entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); +@@ -1356,7 +1367,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + double d8 = d1 - this.firstGoodY; + double d9 = d2 - this.firstGoodZ; + double d10 = this.player.getDeltaMovement().lengthSqr(); +- 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 ++ // Paper start - fix large move vectors killing the server ++ double otherFieldX = d0 - this.lastGoodX; ++ double otherFieldY = d1 - this.lastGoodY; ++ double otherFieldZ = d2 - this.lastGoodZ; ++ d11 = Math.max(d11, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ // Paper end - fix large move vectors killing the server + + if (this.player.isSleeping()) { + if (d11 > 1.0D) { +@@ -1408,9 +1430,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + AABB axisalignedbb = this.player.getBoundingBox(); + +- d7 = d0 - this.lastGoodX; +- d8 = d1 - this.lastGoodY; +- d9 = d2 - this.lastGoodZ; ++ d7 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d8 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d9 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag = d8 > 0.0D; + + if (this.player.isOnGround() && !packet.isOnGround() && flag) { diff --git a/patches/server/0506-Reset-Ender-Crystals-on-Dragon-Spawn.patch b/patches/server/0506-Reset-Ender-Crystals-on-Dragon-Spawn.patch deleted file mode 100644 index 31cde5ee93..0000000000 --- a/patches/server/0506-Reset-Ender-Crystals-on-Dragon-Spawn.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Wed, 1 Jun 2016 23:29:17 -0400 -Subject: [PATCH] Reset Ender Crystals on Dragon Spawn - -Crystals can end up in a bad state in certain conditions which causes -an exception on the expected number of crystals going negative. - -This ensures the crystals/pillars are in expected state when the dragon spawns. - -See #3522 - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 908cc3e2fc2cf4894a10081192a8b0d3c7e99932..f32906729baf83d8b53d64ab750cddcc413a2927 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -409,6 +409,7 @@ public class EndDragonFight { - enderDragon.moveTo(0.0D, 128.0D, 0.0D, this.level.random.nextFloat() * 360.0F, 0.0F); - this.level.addFreshEntity(enderDragon); - this.dragonUUID = enderDragon.getUUID(); -+ this.resetSpikeCrystals(); // Paper - return enderDragon; - } - diff --git a/patches/server/0507-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0507-Fix-for-large-move-vectors-crashing-server.patch deleted file mode 100644 index 415947895c..0000000000 --- a/patches/server/0507-Fix-for-large-move-vectors-crashing-server.patch +++ /dev/null @@ -1,92 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 17 May 2020 23:47:33 -0700 -Subject: [PATCH] Fix for large move vectors crashing server - -Check movement distance also based on current position. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 70ef7e4a86cc080ff47dcc648088bf007d6ddee6..3b422cd28b3fb8a172a734e3d59636b293d4a5cb 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -575,9 +575,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { - ServerLevel worldserver = this.player.getLevel(); -- double d0 = entity.getX(); -- double d1 = entity.getY(); -- double d2 = entity.getZ(); -+ double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER -+ double d1 = entity.getY();final double fromY = d1; // Paper - OBFHELPER -+ double d2 = entity.getZ();final double fromZ = d2; // Paper - OBFHELPER - double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX()); final double toX = d3; // Paper - OBFHELPER - double d4 = ServerGamePacketListenerImpl.clampVertical(packet.getY()); final double toY = d4; // Paper - OBFHELPER - double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ()); final double toZ = d5; // Paper - OBFHELPER -@@ -587,8 +587,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - double d7 = d4 - this.vehicleFirstGoodY; - double d8 = d5 - this.vehicleFirstGoodZ; - double d9 = entity.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; -- -+ // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - fromX; -+ double currDeltaY = toY - fromY; -+ double currDeltaZ = toZ - fromZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ // Paper end - fix large move vectors killing the server -+ -+ // Paper start - fix large move vectors killing the server -+ double otherFieldX = d3 - this.vehicleLastGoodX; -+ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; -+ double otherFieldZ = d5 - this.vehicleLastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - // CraftBukkit start - handle custom speeds and skipped ticks - this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -@@ -634,9 +645,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- d6 = d3 - this.vehicleLastGoodX; -- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; -- d8 = d5 - this.vehicleLastGoodZ; -+ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above -+ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = entity.verticalCollisionBelow; - - entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); -@@ -1356,7 +1367,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - double d8 = d1 - this.firstGoodY; - double d9 = d2 - this.firstGoodZ; - double d10 = this.player.getDeltaMovement().lengthSqr(); -- 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 -+ // Paper start - fix large move vectors killing the server -+ double otherFieldX = d0 - this.lastGoodX; -+ double otherFieldY = d1 - this.lastGoodY; -+ double otherFieldZ = d2 - this.lastGoodZ; -+ d11 = Math.max(d11, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Paper end - fix large move vectors killing the server - - if (this.player.isSleeping()) { - if (d11 > 1.0D) { -@@ -1408,9 +1430,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - AABB axisalignedbb = this.player.getBoundingBox(); - -- d7 = d0 - this.lastGoodX; -- d8 = d1 - this.lastGoodY; -- d9 = d2 - this.lastGoodZ; -+ d7 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d8 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -+ d9 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag = d8 > 0.0D; - - if (this.player.isOnGround() && !packet.isOnGround() && flag) { diff --git a/patches/server/0507-Optimise-getType-calls.patch b/patches/server/0507-Optimise-getType-calls.patch new file mode 100644 index 0000000000..5d53107ff2 --- /dev/null +++ b/patches/server/0507-Optimise-getType-calls.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 3 Jun 2020 11:37:13 -0700 +Subject: [PATCH] Optimise getType calls + +Remove the map lookup for converting from Block->Bukkit Material + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +index 76133c77e8ebce7c9e5402e3e7cd50b30aa1c2e0..348a91a760bd728f8e732e1a35c86ab75d8fc0f1 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java +@@ -10,6 +10,17 @@ import net.minecraft.world.level.block.state.properties.Property; + public class BlockState extends BlockBehaviour.BlockStateBase { + public static final Codec CODEC = codec(Registry.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); + ++ // Paper start - optimise getType calls ++ org.bukkit.Material cachedMaterial; ++ ++ public final org.bukkit.Material getBukkitMaterial() { ++ if (this.cachedMaterial == null) { ++ this.cachedMaterial = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getBlock()); ++ } ++ ++ return this.cachedMaterial; ++ } ++ // Paper end - optimise getType calls + public BlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec codec) { + super(block, propertyMap, codec); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +index 0511ac55c4e6d9736ec12e94c9899eb04d5cd2e3..75193684a71d694736087d1a368b8fb6a8c8363b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java +@@ -84,7 +84,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { + public Material getBlockType(int x, int y, int z) { + this.validateChunkCoordinates(x, y, z); + +- return CraftMagicNumbers.getMaterial(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); ++ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 396fb96abed1e77893d8ee8a15cff18cad804475..2c1efaecf220588d8b74946194bf340871e57004 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -224,7 +224,7 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(this.world.getBlockState(position).getBlock()); ++ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 0a755f38fae9dc84440f43113920c5b4c6d8218b..7b9e943b391c061782fccd2b8d705ceec8db50fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -166,7 +166,7 @@ public class CraftBlockState implements BlockState { + + @Override + public Material getType() { +- return CraftMagicNumbers.getMaterial(this.data.getBlock()); ++ return this.data.getBukkitMaterial(); // Paper - optimise getType calls + } + + public void setFlag(int flag) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index d02a5e383190fc4713141d953efc111bb2f7f89c..aae7f7ab4931db8f955c3055157fe01f99931ec7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -50,7 +50,7 @@ public class CraftBlockData implements BlockData { + + @Override + public Material getMaterial() { +- return CraftMagicNumbers.getMaterial(this.state.getBlock()); ++ return this.state.getBukkitMaterial(); // Paper - optimise getType calls + } + + public BlockState getState() { +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +index ee88ff2ee86bbd3ccf3e4a0b2310f020f137ef4f..5e15feb408b8a05ec5ee393a604c8d39a91ff106 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java +@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { + + @Override + public Material getType(int x, int y, int z) { +- return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock()); ++ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls + } + + @Override diff --git a/patches/server/0508-Optimise-getType-calls.patch b/patches/server/0508-Optimise-getType-calls.patch deleted file mode 100644 index 5d53107ff2..0000000000 --- a/patches/server/0508-Optimise-getType-calls.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 3 Jun 2020 11:37:13 -0700 -Subject: [PATCH] Optimise getType calls - -Remove the map lookup for converting from Block->Bukkit Material - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockState.java b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -index 76133c77e8ebce7c9e5402e3e7cd50b30aa1c2e0..348a91a760bd728f8e732e1a35c86ab75d8fc0f1 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockState.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockState.java -@@ -10,6 +10,17 @@ import net.minecraft.world.level.block.state.properties.Property; - public class BlockState extends BlockBehaviour.BlockStateBase { - public static final Codec CODEC = codec(Registry.BLOCK.byNameCodec(), Block::defaultBlockState).stable(); - -+ // Paper start - optimise getType calls -+ org.bukkit.Material cachedMaterial; -+ -+ public final org.bukkit.Material getBukkitMaterial() { -+ if (this.cachedMaterial == null) { -+ this.cachedMaterial = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getBlock()); -+ } -+ -+ return this.cachedMaterial; -+ } -+ // Paper end - optimise getType calls - public BlockState(Block block, ImmutableMap, Comparable> propertyMap, MapCodec codec) { - super(block, propertyMap, codec); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -index 0511ac55c4e6d9736ec12e94c9899eb04d5cd2e3..75193684a71d694736087d1a368b8fb6a8c8363b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunkSnapshot.java -@@ -84,7 +84,7 @@ public class CraftChunkSnapshot implements ChunkSnapshot { - public Material getBlockType(int x, int y, int z) { - this.validateChunkCoordinates(x, y, z); - -- return CraftMagicNumbers.getMaterial(this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBlock()); -+ return this.blockids[this.getSectionIndex(y)].get(x, y & 0xF, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 396fb96abed1e77893d8ee8a15cff18cad804475..2c1efaecf220588d8b74946194bf340871e57004 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -224,7 +224,7 @@ public class CraftBlock implements Block { - - @Override - public Material getType() { -- return CraftMagicNumbers.getMaterial(this.world.getBlockState(position).getBlock()); -+ return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 0a755f38fae9dc84440f43113920c5b4c6d8218b..7b9e943b391c061782fccd2b8d705ceec8db50fe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -166,7 +166,7 @@ public class CraftBlockState implements BlockState { - - @Override - public Material getType() { -- return CraftMagicNumbers.getMaterial(this.data.getBlock()); -+ return this.data.getBukkitMaterial(); // Paper - optimise getType calls - } - - public void setFlag(int flag) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -index d02a5e383190fc4713141d953efc111bb2f7f89c..aae7f7ab4931db8f955c3055157fe01f99931ec7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -50,7 +50,7 @@ public class CraftBlockData implements BlockData { - - @Override - public Material getMaterial() { -- return CraftMagicNumbers.getMaterial(this.state.getBlock()); -+ return this.state.getBukkitMaterial(); // Paper - optimise getType calls - } - - public BlockState getState() { -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -index ee88ff2ee86bbd3ccf3e4a0b2310f020f137ef4f..5e15feb408b8a05ec5ee393a604c8d39a91ff106 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftChunkData.java -@@ -96,7 +96,7 @@ public final class CraftChunkData implements ChunkGenerator.ChunkData { - - @Override - public Material getType(int x, int y, int z) { -- return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock()); -+ return this.getTypeId(x, y, z).getBukkitMaterial(); // Paper - optimise getType calls - } - - @Override diff --git a/patches/server/0508-Villager-resetOffers.patch b/patches/server/0508-Villager-resetOffers.patch new file mode 100644 index 0000000000..7448270230 --- /dev/null +++ b/patches/server/0508-Villager-resetOffers.patch @@ -0,0 +1,40 @@ +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/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 9960862cb5302aa44776ac294e3b9e30105adb07..6717e3d116b7bff8ab4d7b45f8c4ec00939c9c73 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -115,6 +115,13 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + return this.tradingPlayer != null; + } + ++ // Paper start ++ public void resetOffers() { ++ this.offers = new MerchantOffers(); ++ this.updateTrades(); ++ } ++ // Paper end ++ + @Override + public MerchantOffers getOffers() { + if (this.offers == null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +index 1467232779541a9e38420caabf273662f380794c..762354681315e4c74e414bf7d677b5422385161e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java +@@ -70,4 +70,11 @@ public class CraftAbstractVillager extends CraftAgeable implements AbstractVilla + public HumanEntity getTrader() { + return this.getMerchant().getTrader(); + } ++ ++ // Paper start ++ @Override ++ public void resetOffers() { ++ getHandle().resetOffers(); ++ } ++ // Paper end + } diff --git a/patches/server/0509-Improve-inlinig-for-some-hot-IBlockData-methods.patch b/patches/server/0509-Improve-inlinig-for-some-hot-IBlockData-methods.patch new file mode 100644 index 0000000000..3639390300 --- /dev/null +++ b/patches/server/0509-Improve-inlinig-for-some-hot-IBlockData-methods.patch @@ -0,0 +1,96 @@ +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/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 7117dd220c393163fd5be77e332e56a34c667670..178d9ad7525b6743038ed45c6f85686a860ffd26 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -717,8 +717,14 @@ public abstract class BlockBehaviour { + return this.shapeExceedsCube; + } + // Paper end ++ // Paper start ++ protected boolean isTicking; ++ protected FluidState fluid; ++ // Paper end + + public void initCache() { ++ this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() ++ this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking() + if (!this.getBlock().hasDynamicShape()) { + this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); + } +@@ -768,15 +774,15 @@ public abstract class BlockBehaviour { + return this.shapeExceedsCube; // Paper - moved into shape cache init + } + +- public boolean useShapeForLightOcclusion() { ++ public final boolean useShapeForLightOcclusion() { // Paper + return this.useShapeForLightOcclusion; + } + +- public int getLightEmission() { ++ public final int getLightEmission() { // Paper + return this.lightEmission; + } + +- public boolean isAir() { ++ public final boolean isAir() { // Paper + return this.isAir; + } + +@@ -850,7 +856,7 @@ public abstract class BlockBehaviour { + } + } + +- public boolean canOcclude() { ++ public final boolean canOcclude() { // Paper + return this.canOcclude; + } + +@@ -1048,12 +1054,12 @@ public abstract class BlockBehaviour { + return this.getBlock() == block; + } + +- public FluidState getFluidState() { +- return this.getBlock().getFluidState(this.asState()); ++ public final FluidState getFluidState() { // Paper ++ return this.fluid; // Paper - moved into init + } + +- public boolean isRandomlyTicking() { +- return this.getBlock().isRandomlyTicking(this.asState()); ++ public final boolean isRandomlyTicking() { // Paper ++ return this.isTicking; // Paper - moved into init + } + + public long getSeed(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java +index d035eaccc09dfeaedf25579b850ccdaecba3f974..66f712657ae0c4166ecd198f41081d96843296c4 100644 +--- a/src/main/java/net/minecraft/world/level/material/FluidState.java ++++ b/src/main/java/net/minecraft/world/level/material/FluidState.java +@@ -26,8 +26,12 @@ public final class FluidState extends StateHolder { + public static final int AMOUNT_MAX = 9; + public static final int AMOUNT_FULL = 8; + ++ // Paper start ++ protected final boolean isEmpty; ++ // Paper end + public FluidState(Fluid fluid, ImmutableMap, Comparable> propertiesMap, MapCodec codec) { + super(fluid, propertiesMap, codec); ++ this.isEmpty = fluid.isEmpty(); // Paper - moved from isEmpty() + } + + public Fluid getType() { +@@ -43,7 +47,7 @@ public final class FluidState extends StateHolder { + } + + public boolean isEmpty() { +- return this.getType().isEmpty(); ++ return this.isEmpty; // Paper - moved into constructor + } + + public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/patches/server/0509-Villager-resetOffers.patch b/patches/server/0509-Villager-resetOffers.patch deleted file mode 100644 index 7448270230..0000000000 --- a/patches/server/0509-Villager-resetOffers.patch +++ /dev/null @@ -1,40 +0,0 @@ -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/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 9960862cb5302aa44776ac294e3b9e30105adb07..6717e3d116b7bff8ab4d7b45f8c4ec00939c9c73 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -115,6 +115,13 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - return this.tradingPlayer != null; - } - -+ // Paper start -+ public void resetOffers() { -+ this.offers = new MerchantOffers(); -+ this.updateTrades(); -+ } -+ // Paper end -+ - @Override - public MerchantOffers getOffers() { - if (this.offers == null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -index 1467232779541a9e38420caabf273662f380794c..762354681315e4c74e414bf7d677b5422385161e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractVillager.java -@@ -70,4 +70,11 @@ public class CraftAbstractVillager extends CraftAgeable implements AbstractVilla - public HumanEntity getTrader() { - return this.getMerchant().getTrader(); - } -+ -+ // Paper start -+ @Override -+ public void resetOffers() { -+ getHandle().resetOffers(); -+ } -+ // Paper end - } diff --git a/patches/server/0510-Improve-inlinig-for-some-hot-IBlockData-methods.patch b/patches/server/0510-Improve-inlinig-for-some-hot-IBlockData-methods.patch deleted file mode 100644 index 3639390300..0000000000 --- a/patches/server/0510-Improve-inlinig-for-some-hot-IBlockData-methods.patch +++ /dev/null @@ -1,96 +0,0 @@ -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/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 7117dd220c393163fd5be77e332e56a34c667670..178d9ad7525b6743038ed45c6f85686a860ffd26 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -717,8 +717,14 @@ public abstract class BlockBehaviour { - return this.shapeExceedsCube; - } - // Paper end -+ // Paper start -+ protected boolean isTicking; -+ protected FluidState fluid; -+ // Paper end - - public void initCache() { -+ this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() -+ this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking() - if (!this.getBlock().hasDynamicShape()) { - this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); - } -@@ -768,15 +774,15 @@ public abstract class BlockBehaviour { - return this.shapeExceedsCube; // Paper - moved into shape cache init - } - -- public boolean useShapeForLightOcclusion() { -+ public final boolean useShapeForLightOcclusion() { // Paper - return this.useShapeForLightOcclusion; - } - -- public int getLightEmission() { -+ public final int getLightEmission() { // Paper - return this.lightEmission; - } - -- public boolean isAir() { -+ public final boolean isAir() { // Paper - return this.isAir; - } - -@@ -850,7 +856,7 @@ public abstract class BlockBehaviour { - } - } - -- public boolean canOcclude() { -+ public final boolean canOcclude() { // Paper - return this.canOcclude; - } - -@@ -1048,12 +1054,12 @@ public abstract class BlockBehaviour { - return this.getBlock() == block; - } - -- public FluidState getFluidState() { -- return this.getBlock().getFluidState(this.asState()); -+ public final FluidState getFluidState() { // Paper -+ return this.fluid; // Paper - moved into init - } - -- public boolean isRandomlyTicking() { -- return this.getBlock().isRandomlyTicking(this.asState()); -+ public final boolean isRandomlyTicking() { // Paper -+ return this.isTicking; // Paper - moved into init - } - - public long getSeed(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/material/FluidState.java b/src/main/java/net/minecraft/world/level/material/FluidState.java -index d035eaccc09dfeaedf25579b850ccdaecba3f974..66f712657ae0c4166ecd198f41081d96843296c4 100644 ---- a/src/main/java/net/minecraft/world/level/material/FluidState.java -+++ b/src/main/java/net/minecraft/world/level/material/FluidState.java -@@ -26,8 +26,12 @@ public final class FluidState extends StateHolder { - public static final int AMOUNT_MAX = 9; - public static final int AMOUNT_FULL = 8; - -+ // Paper start -+ protected final boolean isEmpty; -+ // Paper end - public FluidState(Fluid fluid, ImmutableMap, Comparable> propertiesMap, MapCodec codec) { - super(fluid, propertiesMap, codec); -+ this.isEmpty = fluid.isEmpty(); // Paper - moved from isEmpty() - } - - public Fluid getType() { -@@ -43,7 +47,7 @@ public final class FluidState extends StateHolder { - } - - public boolean isEmpty() { -- return this.getType().isEmpty(); -+ return this.isEmpty; // Paper - moved into constructor - } - - public float getHeight(BlockGetter world, BlockPos pos) { diff --git a/patches/server/0510-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/0510-Retain-block-place-order-when-capturing-blockstates.patch new file mode 100644 index 0000000000..d600fe08e6 --- /dev/null +++ b/patches/server/0510-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/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 160a3cb1d70f765d277169bb4928238b6a575f26..525df1e5515fff204f790edcd0a051e851c03d33 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -155,7 +155,7 @@ public abstract class Level implements LevelAccessor, 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 final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); + // Paper start diff --git a/patches/server/0511-Reduce-blockpos-allocation-from-pathfinding.patch b/patches/server/0511-Reduce-blockpos-allocation-from-pathfinding.patch new file mode 100644 index 0000000000..3f620bf4e9 --- /dev/null +++ b/patches/server/0511-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/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index c48e097464977dbec22d1370c3393ba1fe105137..ede91a2fbe67480d2b6bcdeb776f87da0b69bdae 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -495,7 +495,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + return BlockPathTypes.DANGER_FIRE; + } + +- if (world.getFluidState(pos).is(FluidTags.WATER)) { ++ if (blockState.getFluidState().is(FluidTags.WATER)) { + return BlockPathTypes.WATER_BORDER; + } + } // Paper +@@ -526,7 +526,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { + } else if (blockState.is(Blocks.COCOA)) { + return BlockPathTypes.COCOA; + } else { +- FluidState fluidState = world.getFluidState(pos); ++ FluidState fluidState = blockState.getFluidState(); // Paper - remove another get type call + if (fluidState.is(FluidTags.LAVA)) { + return BlockPathTypes.LAVA; + } else if (isBurningBlock(blockState)) { diff --git a/patches/server/0511-Retain-block-place-order-when-capturing-blockstates.patch b/patches/server/0511-Retain-block-place-order-when-capturing-blockstates.patch deleted file mode 100644 index ae8ed41c93..0000000000 --- a/patches/server/0511-Retain-block-place-order-when-capturing-blockstates.patch +++ /dev/null @@ -1,24 +0,0 @@ -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/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d5e80a0d953e7792669f21011bc685adaec78464..d9a88b29cfefcdbce7bfc477b6c1af0e51079102 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -155,7 +155,7 @@ public abstract class Level implements LevelAccessor, 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 final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); - // Paper start diff --git a/patches/server/0512-Fix-item-locations-dropped-from-campfires.patch b/patches/server/0512-Fix-item-locations-dropped-from-campfires.patch new file mode 100644 index 0000000000..df944d4a66 --- /dev/null +++ b/patches/server/0512-Fix-item-locations-dropped-from-campfires.patch @@ -0,0 +1,24 @@ +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/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 0cebba557f0ba312e7911e61cb21485c81436720..003a66064c666db974c2b36f6579a87e1ad28685 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -78,7 +78,11 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + result = blockCookEvent.getResult(); + itemstack1 = CraftItemStack.asNMSCopy(result); + // CraftBukkit end +- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); ++ // Paper start ++ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10)); ++ droppedItem.setDeltaMovement(world.random.nextGaussian() * 0.05D, world.random.nextGaussian() * 0.05D + 0.2D, world.random.nextGaussian() * 0.05D); ++ world.addFreshEntity(droppedItem); ++ // Paper end + campfire.items.set(i, ItemStack.EMPTY); + world.sendBlockUpdated(pos, state, state, 3); + world.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); diff --git a/patches/server/0512-Reduce-blockpos-allocation-from-pathfinding.patch b/patches/server/0512-Reduce-blockpos-allocation-from-pathfinding.patch deleted file mode 100644 index 3f620bf4e9..0000000000 --- a/patches/server/0512-Reduce-blockpos-allocation-from-pathfinding.patch +++ /dev/null @@ -1,28 +0,0 @@ -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/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -index c48e097464977dbec22d1370c3393ba1fe105137..ede91a2fbe67480d2b6bcdeb776f87da0b69bdae 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -@@ -495,7 +495,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - return BlockPathTypes.DANGER_FIRE; - } - -- if (world.getFluidState(pos).is(FluidTags.WATER)) { -+ if (blockState.getFluidState().is(FluidTags.WATER)) { - return BlockPathTypes.WATER_BORDER; - } - } // Paper -@@ -526,7 +526,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - } else if (blockState.is(Blocks.COCOA)) { - return BlockPathTypes.COCOA; - } else { -- FluidState fluidState = world.getFluidState(pos); -+ FluidState fluidState = blockState.getFluidState(); // Paper - remove another get type call - if (fluidState.is(FluidTags.LAVA)) { - return BlockPathTypes.LAVA; - } else if (isBurningBlock(blockState)) { diff --git a/patches/server/0513-Fix-item-locations-dropped-from-campfires.patch b/patches/server/0513-Fix-item-locations-dropped-from-campfires.patch deleted file mode 100644 index df944d4a66..0000000000 --- a/patches/server/0513-Fix-item-locations-dropped-from-campfires.patch +++ /dev/null @@ -1,24 +0,0 @@ -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/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 0cebba557f0ba312e7911e61cb21485c81436720..003a66064c666db974c2b36f6579a87e1ad28685 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -78,7 +78,11 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - result = blockCookEvent.getResult(); - itemstack1 = CraftItemStack.asNMSCopy(result); - // CraftBukkit end -- Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1); -+ // Paper start -+ net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10)); -+ droppedItem.setDeltaMovement(world.random.nextGaussian() * 0.05D, world.random.nextGaussian() * 0.05D + 0.2D, world.random.nextGaussian() * 0.05D); -+ world.addFreshEntity(droppedItem); -+ // Paper end - campfire.items.set(i, ItemStack.EMPTY); - world.sendBlockUpdated(pos, state, state, 3); - world.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state)); diff --git a/patches/server/0513-Player-elytra-boost-API.patch b/patches/server/0513-Player-elytra-boost-API.patch new file mode 100644 index 0000000000..1e60420c04 --- /dev/null +++ b/patches/server/0513-Player-elytra-boost-API.patch @@ -0,0 +1,31 @@ +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 10e1be2349df779b911848f70e4b8f0e351b1de7..09f5a288cba1825fa0fff32f771f98675f473527 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -592,6 +592,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); ++ net.minecraft.world.level.Level world = ((CraftWorld) getWorld()).getHandle(); ++ net.minecraft.world.entity.projectile.FireworkRocketEntity entity = new net.minecraft.world.entity.projectile.FireworkRocketEntity(world, item, getHandle()); ++ return world.addFreshEntity(entity) ++ ? (org.bukkit.entity.Firework) entity.getBukkitEntity() ++ : null; ++ } + // Paper end + + @Override diff --git a/patches/server/0514-Fixed-TileEntityBell-memory-leak.patch b/patches/server/0514-Fixed-TileEntityBell-memory-leak.patch new file mode 100644 index 0000000000..09d0b77f4a --- /dev/null +++ b/patches/server/0514-Fixed-TileEntityBell-memory-leak.patch @@ -0,0 +1,39 @@ +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/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +index 4353ad2f67556feaa0fdd34e8e907b17ab697565..feaad48e9bbc1e658324ef9e1e7e73aca0b3bf48 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +@@ -61,6 +61,11 @@ public class BellBlockEntity extends BlockEntity { + + if (blockEntity.ticks >= 50) { + blockEntity.shaking = false; ++ // Paper start ++ if (!blockEntity.resonating) { ++ blockEntity.nearbyEntities.clear(); ++ } ++ // Paper end + blockEntity.ticks = 0; + } + +@@ -74,6 +79,7 @@ public class BellBlockEntity extends BlockEntity { + ++blockEntity.resonationTicks; + } else { + bellEffect.run(world, pos, blockEntity.nearbyEntities); ++ blockEntity.nearbyEntities.clear(); // Paper + blockEntity.resonating = false; + } + } +@@ -116,6 +122,7 @@ public class BellBlockEntity extends BlockEntity { + } + } + ++ this.nearbyEntities.removeIf(e -> !e.isAlive()); // Paper + } + + private static boolean areRaidersNearby(BlockPos pos, List hearingEntities) { diff --git a/patches/server/0514-Player-elytra-boost-API.patch b/patches/server/0514-Player-elytra-boost-API.patch deleted file mode 100644 index 1e60420c04..0000000000 --- a/patches/server/0514-Player-elytra-boost-API.patch +++ /dev/null @@ -1,31 +0,0 @@ -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 10e1be2349df779b911848f70e4b8f0e351b1de7..09f5a288cba1825fa0fff32f771f98675f473527 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -592,6 +592,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); -+ net.minecraft.world.level.Level world = ((CraftWorld) getWorld()).getHandle(); -+ net.minecraft.world.entity.projectile.FireworkRocketEntity entity = new net.minecraft.world.entity.projectile.FireworkRocketEntity(world, item, getHandle()); -+ return world.addFreshEntity(entity) -+ ? (org.bukkit.entity.Firework) entity.getBukkitEntity() -+ : null; -+ } - // Paper end - - @Override diff --git a/patches/server/0515-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server/0515-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch new file mode 100644 index 0000000000..f48528d0b5 --- /dev/null +++ b/patches/server/0515-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/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +index 11ac510ad4095438d4904d521bfb18aa5f743faf..f5886a88fd98ede5e85a91eccccb05ac33eb40e2 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +@@ -494,9 +494,15 @@ public class FishingHook extends Projectile { + + while (iterator.hasNext()) { + ItemStack itemstack1 = (ItemStack) iterator.next(); +- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), 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 ++ ItemEntity entityitem = null; ++ if (!itemstack1.isEmpty()) { ++ entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), 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.level.getCraftServer().getPluginManager().callEvent(playerFishEvent); + +@@ -509,8 +515,12 @@ public class FishingHook extends Projectile { + double d2 = entityhuman.getZ() - this.getZ(); + double d3 = 0.1D; + +- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); +- this.level.addFreshEntity(entityitem); ++ // Paper start, entity item can be null, so we need to check against this ++ if (entityitem != null) { ++ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); ++ this.level.addFreshEntity(entityitem); ++ } ++ // Paper end + // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() + if (playerFishEvent.getExpToDrop() > 0) { + entityhuman.level.addFreshEntity(new ExperienceOrb(entityhuman.level, entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper diff --git a/patches/server/0515-Fixed-TileEntityBell-memory-leak.patch b/patches/server/0515-Fixed-TileEntityBell-memory-leak.patch deleted file mode 100644 index 09d0b77f4a..0000000000 --- a/patches/server/0515-Fixed-TileEntityBell-memory-leak.patch +++ /dev/null @@ -1,39 +0,0 @@ -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/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -index 4353ad2f67556feaa0fdd34e8e907b17ab697565..feaad48e9bbc1e658324ef9e1e7e73aca0b3bf48 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -@@ -61,6 +61,11 @@ public class BellBlockEntity extends BlockEntity { - - if (blockEntity.ticks >= 50) { - blockEntity.shaking = false; -+ // Paper start -+ if (!blockEntity.resonating) { -+ blockEntity.nearbyEntities.clear(); -+ } -+ // Paper end - blockEntity.ticks = 0; - } - -@@ -74,6 +79,7 @@ public class BellBlockEntity extends BlockEntity { - ++blockEntity.resonationTicks; - } else { - bellEffect.run(world, pos, blockEntity.nearbyEntities); -+ blockEntity.nearbyEntities.clear(); // Paper - blockEntity.resonating = false; - } - } -@@ -116,6 +122,7 @@ public class BellBlockEntity extends BlockEntity { - } - } - -+ this.nearbyEntities.removeIf(e -> !e.isAlive()); // Paper - } - - private static boolean areRaidersNearby(BlockPos pos, List hearingEntities) { diff --git a/patches/server/0516-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0516-Add-getOfflinePlayerIfCached-String.patch new file mode 100644 index 0000000000..b453ab0746 --- /dev/null +++ b/patches/server/0516-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 f156620c14e28513fd0f825d5f34de5c5f831b75..f5783a0df00c7c88c5d5d1c3b55ba671350cd0c6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1803,6 +1803,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.getProfileCache().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/0516-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch b/patches/server/0516-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch deleted file mode 100644 index f48528d0b5..0000000000 --- a/patches/server/0516-Avoid-error-bubbling-up-when-item-stack-is-empty-in-.patch +++ /dev/null @@ -1,46 +0,0 @@ -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/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index 11ac510ad4095438d4904d521bfb18aa5f743faf..f5886a88fd98ede5e85a91eccccb05ac33eb40e2 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -@@ -494,9 +494,15 @@ public class FishingHook extends Projectile { - - while (iterator.hasNext()) { - ItemStack itemstack1 = (ItemStack) iterator.next(); -- ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), 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 -+ ItemEntity entityitem = null; -+ if (!itemstack1.isEmpty()) { -+ entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), 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.level.getCraftServer().getPluginManager().callEvent(playerFishEvent); - -@@ -509,8 +515,12 @@ public class FishingHook extends Projectile { - double d2 = entityhuman.getZ() - this.getZ(); - double d3 = 0.1D; - -- entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); -- this.level.addFreshEntity(entityitem); -+ // Paper start, entity item can be null, so we need to check against this -+ if (entityitem != null) { -+ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D); -+ this.level.addFreshEntity(entityitem); -+ } -+ // Paper end - // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop() - if (playerFishEvent.getExpToDrop() > 0) { - entityhuman.level.addFreshEntity(new ExperienceOrb(entityhuman.level, entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.FISHING, this.getPlayerOwner(), this)); // Paper diff --git a/patches/server/0517-Add-getOfflinePlayerIfCached-String.patch b/patches/server/0517-Add-getOfflinePlayerIfCached-String.patch deleted file mode 100644 index b453ab0746..0000000000 --- a/patches/server/0517-Add-getOfflinePlayerIfCached-String.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: oxygencraft <21054297+oxygencraft@users.noreply.github.com> -Date: Sun, 25 Oct 2020 18:34:50 +1100 -Subject: [PATCH] Add getOfflinePlayerIfCached(String) - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f156620c14e28513fd0f825d5f34de5c5f831b75..f5783a0df00c7c88c5d5d1c3b55ba671350cd0c6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1803,6 +1803,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.getProfileCache().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/0517-Add-ignore-discounts-API.patch b/patches/server/0517-Add-ignore-discounts-API.patch new file mode 100644 index 0000000000..e77eb448c9 --- /dev/null +++ b/patches/server/0517-Add-ignore-discounts-API.patch @@ -0,0 +1,154 @@ +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/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 39fc94b1e1555fd6706391223dd2783139b16016..1d2b7950e3c498945a2ff85fda0e3bb30acd22cb 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -489,6 +489,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator.hasNext()) { + MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); ++ if (merchantrecipe.ignoreDiscounts) continue; // Paper + + merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); + } +@@ -501,6 +502,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + while (iterator1.hasNext()) { + MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); ++ if (merchantrecipe1.ignoreDiscounts) continue; // Paper + double d0 = 0.3D + 0.0625D * (double) j; + int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); + +diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +index 8b46e494ecd0cce5ab0b2bf8e50cf50dc7e2a7e5..8a9a701baabdaf066cd9b28c05430f673fcafb4e 100644 +--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java ++++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +@@ -19,6 +19,7 @@ public class MerchantOffer { + public int demand; + public float priceMultiplier; + public int xp; ++ public boolean ignoreDiscounts; // Paper + // CraftBukkit start + private CraftMerchantRecipe bukkitHandle; + +@@ -31,7 +32,15 @@ public class MerchantOffer { + } + + public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) { +- this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand); ++ // Paper start - add ignoreDiscounts param ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, false, bukkit); ++ } ++ public MerchantOffer(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, 0, ignoreDiscounts, bukkit); ++ } ++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { ++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, ignoreDiscounts); ++ // Paper end + this.bukkitHandle = bukkit; + } + // CraftBukkit end +@@ -63,6 +72,7 @@ public class MerchantOffer { + + this.specialPriceDiff = nbt.getInt("specialPrice"); + this.demand = nbt.getInt("demand"); ++ this.ignoreDiscounts = nbt.getBoolean("Paper.IgnoreDiscounts"); // Paper + } + + public MerchantOffer(ItemStack buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) { +@@ -74,10 +84,19 @@ public class MerchantOffer { + } + + public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier) { +- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0); ++ // Paper start - add ignoreDiscounts param ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, false); ++ } ++ public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, boolean ignoreDiscounts) { ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0, ignoreDiscounts); + } + + public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) { ++ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, demandBonus, false); ++ } ++ public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus, boolean ignoreDiscounts) { ++ this.ignoreDiscounts = ignoreDiscounts; ++ // Paper end + this.rewardExp = true; + this.xp = 1; + this.baseCostA = firstBuyItem; +@@ -193,6 +212,7 @@ public class MerchantOffer { + nbttagcompound.putFloat("priceMultiplier", this.priceMultiplier); + nbttagcompound.putInt("specialPrice", this.specialPriceDiff); + nbttagcompound.putInt("demand", this.demand); ++ nbttagcompound.putBoolean("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 07462b77107541aed2e29d04da33831ac113b450..0f038f6152c90e707cb633dffcab0a1c5b99d260 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java +@@ -18,11 +18,19 @@ public class CraftMerchantRecipe extends MerchantRecipe { + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { +- this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0); ++ // Paper start - add ignoreDiscounts param ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, ignoreDiscounts); + } + + public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice) { +- super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice); ++ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, false); ++ } ++ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { ++ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, ignoreDiscounts); ++ // Paper end + this.handle = new net.minecraft.world.item.trading.MerchantOffer( + net.minecraft.world.item.ItemStack.EMPTY, + net.minecraft.world.item.ItemStack.EMPTY, +@@ -32,6 +40,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { + experience, + priceMultiplier, + demand, ++ ignoreDiscounts, // Paper - add ignoreDiscounts param + this + ); + this.setSpecialPrice(specialPrice); +@@ -108,6 +117,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.MerchantOffer toMinecraft() { + List ingredients = getIngredients(); + Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); +@@ -122,7 +143,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/0518-Add-ignore-discounts-API.patch b/patches/server/0518-Add-ignore-discounts-API.patch deleted file mode 100644 index e77eb448c9..0000000000 --- a/patches/server/0518-Add-ignore-discounts-API.patch +++ /dev/null @@ -1,154 +0,0 @@ -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/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 39fc94b1e1555fd6706391223dd2783139b16016..1d2b7950e3c498945a2ff85fda0e3bb30acd22cb 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -489,6 +489,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); -+ if (merchantrecipe.ignoreDiscounts) continue; // Paper - - merchantrecipe.addToSpecialPriceDiff(-Mth.floor((float) i * merchantrecipe.getPriceMultiplier())); - } -@@ -501,6 +502,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - while (iterator1.hasNext()) { - MerchantOffer merchantrecipe1 = (MerchantOffer) iterator1.next(); -+ if (merchantrecipe1.ignoreDiscounts) continue; // Paper - double d0 = 0.3D + 0.0625D * (double) j; - int k = (int) Math.floor(d0 * (double) merchantrecipe1.getBaseCostA().getCount()); - -diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -index 8b46e494ecd0cce5ab0b2bf8e50cf50dc7e2a7e5..8a9a701baabdaf066cd9b28c05430f673fcafb4e 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -19,6 +19,7 @@ public class MerchantOffer { - public int demand; - public float priceMultiplier; - public int xp; -+ public boolean ignoreDiscounts; // Paper - // CraftBukkit start - private CraftMerchantRecipe bukkitHandle; - -@@ -31,7 +32,15 @@ public class MerchantOffer { - } - - public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) { -- this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand); -+ // Paper start - add ignoreDiscounts param -+ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, false, bukkit); -+ } -+ public MerchantOffer(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, 0, ignoreDiscounts, bukkit); -+ } -+ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, boolean ignoreDiscounts, CraftMerchantRecipe bukkit) { -+ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand, ignoreDiscounts); -+ // Paper end - this.bukkitHandle = bukkit; - } - // CraftBukkit end -@@ -63,6 +72,7 @@ public class MerchantOffer { - - this.specialPriceDiff = nbt.getInt("specialPrice"); - this.demand = nbt.getInt("demand"); -+ this.ignoreDiscounts = nbt.getBoolean("Paper.IgnoreDiscounts"); // Paper - } - - public MerchantOffer(ItemStack buyItem, ItemStack sellItem, int maxUses, int merchantExperience, float priceMultiplier) { -@@ -74,10 +84,19 @@ public class MerchantOffer { - } - - public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier) { -- this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0); -+ // Paper start - add ignoreDiscounts param -+ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, false); -+ } -+ public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, boolean ignoreDiscounts) { -+ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, 0, ignoreDiscounts); - } - - public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus) { -+ this(firstBuyItem, secondBuyItem, sellItem, uses, maxUses, merchantExperience, priceMultiplier, demandBonus, false); -+ } -+ public MerchantOffer(ItemStack firstBuyItem, ItemStack secondBuyItem, ItemStack sellItem, int uses, int maxUses, int merchantExperience, float priceMultiplier, int demandBonus, boolean ignoreDiscounts) { -+ this.ignoreDiscounts = ignoreDiscounts; -+ // Paper end - this.rewardExp = true; - this.xp = 1; - this.baseCostA = firstBuyItem; -@@ -193,6 +212,7 @@ public class MerchantOffer { - nbttagcompound.putFloat("priceMultiplier", this.priceMultiplier); - nbttagcompound.putInt("specialPrice", this.specialPriceDiff); - nbttagcompound.putInt("demand", this.demand); -+ nbttagcompound.putBoolean("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 07462b77107541aed2e29d04da33831ac113b450..0f038f6152c90e707cb633dffcab0a1c5b99d260 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantRecipe.java -@@ -18,11 +18,19 @@ public class CraftMerchantRecipe extends MerchantRecipe { - } - - public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier) { -- this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0); -+ // Paper start - add ignoreDiscounts param -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, false); -+ } -+ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, boolean ignoreDiscounts) { -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, 0, 0, ignoreDiscounts); - } - - public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice) { -- super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice); -+ this(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, false); -+ } -+ public CraftMerchantRecipe(ItemStack result, int uses, int maxUses, boolean experienceReward, int experience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { -+ super(result, uses, maxUses, experienceReward, experience, priceMultiplier, demand, specialPrice, ignoreDiscounts); -+ // Paper end - this.handle = new net.minecraft.world.item.trading.MerchantOffer( - net.minecraft.world.item.ItemStack.EMPTY, - net.minecraft.world.item.ItemStack.EMPTY, -@@ -32,6 +40,7 @@ public class CraftMerchantRecipe extends MerchantRecipe { - experience, - priceMultiplier, - demand, -+ ignoreDiscounts, // Paper - add ignoreDiscounts param - this - ); - this.setSpecialPrice(specialPrice); -@@ -108,6 +117,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.MerchantOffer toMinecraft() { - List ingredients = getIngredients(); - Preconditions.checkState(!ingredients.isEmpty(), "No offered ingredients"); -@@ -122,7 +143,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/0518-Toggle-for-removing-existing-dragon.patch b/patches/server/0518-Toggle-for-removing-existing-dragon.patch new file mode 100644 index 0000000000..157073f779 --- /dev/null +++ b/patches/server/0518-Toggle-for-removing-existing-dragon.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Wed, 30 Sep 2020 22:49:14 +0200 +Subject: [PATCH] Toggle for removing existing dragon + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index f32906729baf83d8b53d64ab750cddcc413a2927..3e1e8b42f963fab17e416760b93e7c1c0a9a7f45 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -217,7 +217,7 @@ public class EndDragonFight { + this.dragonUUID = enderDragon.getUUID(); + LOGGER.info("Found that there's a dragon still alive ({})", (Object)enderDragon); + this.dragonKilled = false; +- if (!bl) { ++ if (!bl && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { + LOGGER.info("But we didn't have a portal, let's remove it."); + enderDragon.discard(); + this.dragonUUID = null; diff --git a/patches/server/0519-Fix-client-lag-on-advancement-loading.patch b/patches/server/0519-Fix-client-lag-on-advancement-loading.patch new file mode 100644 index 0000000000..3c62dff076 --- /dev/null +++ b/patches/server/0519-Fix-client-lag-on-advancement-loading.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 31 Oct 2020 11:49:01 -0700 +Subject: [PATCH] Fix client lag on advancement loading + +When new advancements are added via the UnsafeValues#loadAdvancement +API, it triggers a full datapack reload when this is not necessary. The +advancement is already loaded directly into the advancement registry, +and the point of saving the advancement to the Bukkit datapack seems to +be for persistence. By removing the call to reload datapacks when an +advancement is loaded, the client no longer completely freezes up when +adding a new advancement. +To ensure the client still receives the updated advancement data, we +manually reload the advancement data for all players, which +normally takes place as a part of the datapack reloading. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 346f5f4b2afec3127c5d1b8e054eaacb1cb756e4..3f45ebeb31264f5f9a99123894fe07bd8e4c65d8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -340,7 +340,13 @@ public final class CraftMagicNumbers implements UnsafeValues { + Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); + } + +- MinecraftServer.getServer().getPlayerList().reloadResources(); ++ // Paper start ++ //MinecraftServer.getServer().getPlayerList().reload(); ++ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { ++ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); ++ player.getAdvancements().flushDirty(player); ++ }); ++ // Paper end + + return bukkit; + } diff --git a/patches/server/0519-Toggle-for-removing-existing-dragon.patch b/patches/server/0519-Toggle-for-removing-existing-dragon.patch deleted file mode 100644 index 157073f779..0000000000 --- a/patches/server/0519-Toggle-for-removing-existing-dragon.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Wed, 30 Sep 2020 22:49:14 +0200 -Subject: [PATCH] Toggle for removing existing dragon - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index f32906729baf83d8b53d64ab750cddcc413a2927..3e1e8b42f963fab17e416760b93e7c1c0a9a7f45 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -217,7 +217,7 @@ public class EndDragonFight { - this.dragonUUID = enderDragon.getUUID(); - LOGGER.info("Found that there's a dragon still alive ({})", (Object)enderDragon); - this.dragonKilled = false; -- if (!bl) { -+ if (!bl && this.level.paperConfig().entities.behavior.shouldRemoveDragon) { - LOGGER.info("But we didn't have a portal, let's remove it."); - enderDragon.discard(); - this.dragonUUID = null; diff --git a/patches/server/0520-Fix-client-lag-on-advancement-loading.patch b/patches/server/0520-Fix-client-lag-on-advancement-loading.patch deleted file mode 100644 index 3c62dff076..0000000000 --- a/patches/server/0520-Fix-client-lag-on-advancement-loading.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 31 Oct 2020 11:49:01 -0700 -Subject: [PATCH] Fix client lag on advancement loading - -When new advancements are added via the UnsafeValues#loadAdvancement -API, it triggers a full datapack reload when this is not necessary. The -advancement is already loaded directly into the advancement registry, -and the point of saving the advancement to the Bukkit datapack seems to -be for persistence. By removing the call to reload datapacks when an -advancement is loaded, the client no longer completely freezes up when -adding a new advancement. -To ensure the client still receives the updated advancement data, we -manually reload the advancement data for all players, which -normally takes place as a part of the datapack reloading. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 346f5f4b2afec3127c5d1b8e054eaacb1cb756e4..3f45ebeb31264f5f9a99123894fe07bd8e4c65d8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -340,7 +340,13 @@ public final class CraftMagicNumbers implements UnsafeValues { - Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); - } - -- MinecraftServer.getServer().getPlayerList().reloadResources(); -+ // Paper start -+ //MinecraftServer.getServer().getPlayerList().reload(); -+ MinecraftServer.getServer().getPlayerList().getPlayers().forEach(player -> { -+ player.getAdvancements().reload(MinecraftServer.getServer().getAdvancements()); -+ player.getAdvancements().flushDirty(player); -+ }); -+ // Paper end - - return bukkit; - } diff --git a/patches/server/0520-Item-no-age-no-player-pickup.patch b/patches/server/0520-Item-no-age-no-player-pickup.patch new file mode 100644 index 0000000000..13b456555a --- /dev/null +++ b/patches/server/0520-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 30c954efba587d69ff55df509339f03e7d5a476e..1d90219c3a0e86786a9497d4c078c2d4077ab6cd 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 ItemEntity item; + + public CraftItem(CraftServer server, Entity entity, ItemEntity item) { +@@ -76,6 +82,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/0521-Item-no-age-no-player-pickup.patch b/patches/server/0521-Item-no-age-no-player-pickup.patch deleted file mode 100644 index 13b456555a..0000000000 --- a/patches/server/0521-Item-no-age-no-player-pickup.patch +++ /dev/null @@ -1,50 +0,0 @@ -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 30c954efba587d69ff55df509339f03e7d5a476e..1d90219c3a0e86786a9497d4c078c2d4077ab6cd 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 ItemEntity item; - - public CraftItem(CraftServer server, Entity entity, ItemEntity item) { -@@ -76,6 +82,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/0521-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/patches/server/0521-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch new file mode 100644 index 0000000000..91c966567f --- /dev/null +++ b/patches/server/0521-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch @@ -0,0 +1,135 @@ +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 + +1.17 Update: Please do this k thx bb +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 300c812d513e0b948a7e79c0ed241f514c7a09fc..d23481453717f715124156b5d83f6448f720d049 100644 +--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java ++++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +@@ -38,9 +38,12 @@ public class PathFinder { + if (node == null) { + return null; + } else { +- Map map = positions.stream().collect(Collectors.toMap((pos) -> { +- return this.nodeEvaluator.getGoal((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()); +- }, Function.identity())); ++ // Paper start - remove streams - and optimize collection ++ List> map = Lists.newArrayList(); ++ for (BlockPos pos : positions) { ++ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), pos)); ++ } ++ // Paper end + Path path = this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier); + this.nodeEvaluator.done(); + return path; +@@ -48,18 +51,19 @@ public class PathFinder { + } + + @Nullable +- private Path findPath(ProfilerFiller profiler, Node startNode, Map positions, float followRange, int distance, float rangeMultiplier) { ++ // Paper start - optimize collection ++ private Path findPath(ProfilerFiller profiler, Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { + profiler.push("find_path"); + profiler.markForCharting(MetricCategory.PATH_FINDING); +- Set set = positions.keySet(); ++ // Set set = positions.keySet(); + startNode.g = 0.0F; +- startNode.h = this.getBestH(startNode, set); ++ startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection + startNode.f = startNode.h; + this.openSet.clear(); + this.openSet.insert(startNode); +- Set set2 = ImmutableSet.of(); ++ // Set set2 = ImmutableSet.of(); // Paper - unused - diff on change + int i = 0; +- Set set3 = Sets.newHashSetWithExpectedSize(set.size()); ++ List> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection + int j = (int)((float)this.maxVisitedNodes * rangeMultiplier); + + while(!this.openSet.isEmpty()) { +@@ -71,14 +75,18 @@ public class PathFinder { + Node node = this.openSet.pop(); + node.closed = true; + +- for(Target target : set) { ++ // Paper start - optimize collection ++ for(int i1 = 0; i1 < positions.size(); i1++) { ++ final Map.Entry entry = positions.get(i1); ++ Target target = entry.getKey(); + if (node.distanceManhattan(target) <= (float)distance) { + target.setReached(); +- set3.add(target); ++ entryList.add(entry); ++ // Paper end + } + } + +- if (!set3.isEmpty()) { ++ if (!entryList.isEmpty()) { // Paper - rename variable + break; + } + +@@ -93,7 +101,7 @@ public class PathFinder { + if (node2.walkedDistance < followRange && (!node2.inOpenSet() || g < node2.g)) { + node2.cameFrom = node; + node2.g = g; +- node2.h = this.getBestH(node2, set) * 1.5F; ++ node2.h = this.getBestH(node2, positions) * 1.5F; // Paper - list instead of set + if (node2.inOpenSet()) { + this.openSet.changeCost(node2, node2.g + node2.h); + } else { +@@ -105,23 +113,31 @@ public class PathFinder { + } + } + +- Optional optional = !set3.isEmpty() ? set3.stream().map((node) -> { +- return this.reconstructPath(node.getBestNode(), positions.get(node), true); +- }).min(Comparator.comparingInt(Path::getNodeCount)) : set.stream().map((target) -> { +- return this.reconstructPath(target.getBestNode(), positions.get(target), false); +- }).min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount)); +- profiler.pop(); +- return !optional.isPresent() ? null : optional.get(); ++ // Paper start - remove streams - and optimize collection ++ Path best = null; ++ boolean entryListIsEmpty = entryList.isEmpty(); ++ Comparator comparator = entryListIsEmpty ? Comparator.comparingInt(Path::getNodeCount) ++ : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount); ++ for (Map.Entry entry : entryListIsEmpty ? positions : entryList) { ++ Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty); ++ if (best == null || comparator.compare(path, best) < 0) ++ best = path; ++ } ++ return best; ++ // Paper end + } + + protected float distance(Node a, Node b) { + return a.distanceTo(b); + } + +- private float getBestH(Node node, Set targets) { ++ private float getBestH(Node node, List> targets) { // Paper - optimize collection - Set -> List> + float f = Float.MAX_VALUE; + +- for(Target target : targets) { ++ // Paper start - optimize collection ++ for (int i = 0, targetsSize = targets.size(); i < targetsSize; i++) { ++ final Target target = targets.get(i).getKey(); ++ // Paper end + float g = node.distanceTo(target); + target.updateBest(g, node); + f = Math.min(g, f); diff --git a/patches/server/0522-Beacon-API-custom-effect-ranges.patch b/patches/server/0522-Beacon-API-custom-effect-ranges.patch new file mode 100644 index 0000000000..42125ab17d --- /dev/null +++ b/patches/server/0522-Beacon-API-custom-effect-ranges.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 24 Jun 2020 12:39:08 -0600 +Subject: [PATCH] Beacon API - custom effect ranges + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 349b89dd4d34b92d1077ddadb30c36642fd85622..812d21393ed2ed0623b7445585f1b0b3dcefb55d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -76,6 +76,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; + } + // CraftBukkit end ++ // Paper start - 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 BeaconBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.BEACON, pos, state); +@@ -186,7 +206,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + } + + if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { +- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); ++ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } +@@ -272,8 +292,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + } + + public static List getHumansInRange(Level world, BlockPos blockposition, int i) { ++ // Paper start ++ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); ++ } ++ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end + { +- double d0 = (double) (i * 10 + 10); ++ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10);// Paper - custom beacon ranges + + AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); + List list = world.getEntitiesOfClass(Player.class, axisalignedbb); +@@ -314,12 +339,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + } + + private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect) { ++ // Paper start ++ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); ++ } ++ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { ++ // Paper end + if (!world.isClientSide && primaryEffect != null) { + double d0 = (double) (beaconLevel * 10 + 10); + byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); + + int j = BeaconBlockEntity.getLevel(beaconLevel); +- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); ++ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper + + BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent + +@@ -369,6 +399,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + } + + this.lockKey = LockCode.fromTag(nbt); ++ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper + } + + @Override +@@ -382,6 +413,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + } + + this.lockKey.addToTag(nbt); ++ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper + } + + public void setCustomName(@Nullable Component customName) { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +index c186a44b927188ed222f8b2f8f76aaef35d9c654..c47e613cae3d9252a8364ccc4d717e410bb0fc0c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java +@@ -29,7 +29,7 @@ public class CraftBeacon extends CraftBlockEntityState implem + if (tileEntity instanceof BeaconBlockEntity) { + BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; + +- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); ++ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper + Collection bukkit = new ArrayList(nms.size()); + + for (Player human : nms) { +@@ -106,4 +106,21 @@ public class CraftBeacon extends CraftBlockEntityState implem + public void setLock(String key) { + this.getSnapshot().lockKey = (key == null) ? LockCode.NO_LOCK : new LockCode(key); + } ++ ++ // Paper start ++ @Override ++ public double getEffectRange() { ++ return this.getSnapshot().getEffectRange(); ++ } ++ ++ @Override ++ public void setEffectRange(double range) { ++ this.getSnapshot().setEffectRange(range); ++ } ++ ++ @Override ++ public void resetEffectRange() { ++ this.getSnapshot().resetEffectRange(); ++ } ++ // Paper end + } diff --git a/patches/server/0522-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch b/patches/server/0522-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch deleted file mode 100644 index 91c966567f..0000000000 --- a/patches/server/0522-Optimize-Pathfinder-Remove-Streams-Optimized-collect.patch +++ /dev/null @@ -1,135 +0,0 @@ -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 - -1.17 Update: Please do this k thx bb -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 300c812d513e0b948a7e79c0ed241f514c7a09fc..d23481453717f715124156b5d83f6448f720d049 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java -@@ -38,9 +38,12 @@ public class PathFinder { - if (node == null) { - return null; - } else { -- Map map = positions.stream().collect(Collectors.toMap((pos) -> { -- return this.nodeEvaluator.getGoal((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()); -- }, Function.identity())); -+ // Paper start - remove streams - and optimize collection -+ List> map = Lists.newArrayList(); -+ for (BlockPos pos : positions) { -+ map.add(new java.util.AbstractMap.SimpleEntry<>(this.nodeEvaluator.getGoal(pos.getX(), pos.getY(), pos.getZ()), pos)); -+ } -+ // Paper end - Path path = this.findPath(world.getProfiler(), node, map, followRange, distance, rangeMultiplier); - this.nodeEvaluator.done(); - return path; -@@ -48,18 +51,19 @@ public class PathFinder { - } - - @Nullable -- private Path findPath(ProfilerFiller profiler, Node startNode, Map positions, float followRange, int distance, float rangeMultiplier) { -+ // Paper start - optimize collection -+ private Path findPath(ProfilerFiller profiler, Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { - profiler.push("find_path"); - profiler.markForCharting(MetricCategory.PATH_FINDING); -- Set set = positions.keySet(); -+ // Set set = positions.keySet(); - startNode.g = 0.0F; -- startNode.h = this.getBestH(startNode, set); -+ startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection - startNode.f = startNode.h; - this.openSet.clear(); - this.openSet.insert(startNode); -- Set set2 = ImmutableSet.of(); -+ // Set set2 = ImmutableSet.of(); // Paper - unused - diff on change - int i = 0; -- Set set3 = Sets.newHashSetWithExpectedSize(set.size()); -+ List> entryList = Lists.newArrayListWithExpectedSize(positions.size()); // Paper - optimize collection - int j = (int)((float)this.maxVisitedNodes * rangeMultiplier); - - while(!this.openSet.isEmpty()) { -@@ -71,14 +75,18 @@ public class PathFinder { - Node node = this.openSet.pop(); - node.closed = true; - -- for(Target target : set) { -+ // Paper start - optimize collection -+ for(int i1 = 0; i1 < positions.size(); i1++) { -+ final Map.Entry entry = positions.get(i1); -+ Target target = entry.getKey(); - if (node.distanceManhattan(target) <= (float)distance) { - target.setReached(); -- set3.add(target); -+ entryList.add(entry); -+ // Paper end - } - } - -- if (!set3.isEmpty()) { -+ if (!entryList.isEmpty()) { // Paper - rename variable - break; - } - -@@ -93,7 +101,7 @@ public class PathFinder { - if (node2.walkedDistance < followRange && (!node2.inOpenSet() || g < node2.g)) { - node2.cameFrom = node; - node2.g = g; -- node2.h = this.getBestH(node2, set) * 1.5F; -+ node2.h = this.getBestH(node2, positions) * 1.5F; // Paper - list instead of set - if (node2.inOpenSet()) { - this.openSet.changeCost(node2, node2.g + node2.h); - } else { -@@ -105,23 +113,31 @@ public class PathFinder { - } - } - -- Optional optional = !set3.isEmpty() ? set3.stream().map((node) -> { -- return this.reconstructPath(node.getBestNode(), positions.get(node), true); -- }).min(Comparator.comparingInt(Path::getNodeCount)) : set.stream().map((target) -> { -- return this.reconstructPath(target.getBestNode(), positions.get(target), false); -- }).min(Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount)); -- profiler.pop(); -- return !optional.isPresent() ? null : optional.get(); -+ // Paper start - remove streams - and optimize collection -+ Path best = null; -+ boolean entryListIsEmpty = entryList.isEmpty(); -+ Comparator comparator = entryListIsEmpty ? Comparator.comparingInt(Path::getNodeCount) -+ : Comparator.comparingDouble(Path::getDistToTarget).thenComparingInt(Path::getNodeCount); -+ for (Map.Entry entry : entryListIsEmpty ? positions : entryList) { -+ Path path = this.reconstructPath(entry.getKey().getBestNode(), entry.getValue(), !entryListIsEmpty); -+ if (best == null || comparator.compare(path, best) < 0) -+ best = path; -+ } -+ return best; -+ // Paper end - } - - protected float distance(Node a, Node b) { - return a.distanceTo(b); - } - -- private float getBestH(Node node, Set targets) { -+ private float getBestH(Node node, List> targets) { // Paper - optimize collection - Set -> List> - float f = Float.MAX_VALUE; - -- for(Target target : targets) { -+ // Paper start - optimize collection -+ for (int i = 0, targetsSize = targets.size(); i < targetsSize; i++) { -+ final Target target = targets.get(i).getKey(); -+ // Paper end - float g = node.distanceTo(target); - target.updateBest(g, node); - f = Math.min(g, f); diff --git a/patches/server/0523-Add-API-for-quit-reason.patch b/patches/server/0523-Add-API-for-quit-reason.patch new file mode 100644 index 0000000000..67527963c4 --- /dev/null +++ b/patches/server/0523-Add-API-for-quit-reason.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:19:52 +0100 +Subject: [PATCH] Add API for quit reason + + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 4d2b26fb1ac5663b667ffd16eed379f4a3311677..dd81751f64695180331b82225ac878913afe4513 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -147,12 +147,15 @@ public class Connection extends SimpleChannelInboundHandler> { + + this.handlingFault = true; + if (this.channel.isOpen()) { ++ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper + if (throwable instanceof TimeoutException) { + Connection.LOGGER.debug("Timeout", throwable); ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper + this.disconnect(Component.translatable("disconnect.timeout")); + } else { + MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + throwable); + ++ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper + if (flag) { + Connection.LOGGER.debug("Failed to sent packet", throwable); + ConnectionProtocol enumprotocol = this.getCurrentProtocol(); +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 6f2b52165c1935511790a429792d3754251537c8..9b4436bdb697d8350eac57282f8fad8140029d8f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -266,6 +266,7 @@ public class ServerPlayer extends Player { + + public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper ++ 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 ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) { + super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile, publicKey); +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3d7d23a02e4aceb95ec36fbca9d02294f08c5780..8e12c4d4b54c2f0a265dc627d7981282fc6fda6e 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -515,6 +515,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end + ++ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { + this.connection.disconnect(ichatbasecomponent); + })); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index ceb5e9540e54275e97f08533d16c820061e76c8f..1d7a7a2d11f0524ae7a81fc0e3bdddd72f5ef53c 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -607,7 +607,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())), entityplayer.quitReason); // Paper - quit reason + if (entityplayer.didPlayerJoinEvent) this.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/0523-Beacon-API-custom-effect-ranges.patch b/patches/server/0523-Beacon-API-custom-effect-ranges.patch deleted file mode 100644 index 42125ab17d..0000000000 --- a/patches/server/0523-Beacon-API-custom-effect-ranges.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 24 Jun 2020 12:39:08 -0600 -Subject: [PATCH] Beacon API - custom effect ranges - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 349b89dd4d34b92d1077ddadb30c36642fd85622..812d21393ed2ed0623b7445585f1b0b3dcefb55d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -76,6 +76,26 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - return (BeaconBlockEntity.hasSecondaryEffect(this.levels, this.primaryPower, this.secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, BeaconBlockEntity.getLevel(this.levels), BeaconBlockEntity.getAmplification(this.levels, this.primaryPower, this.secondaryPower), true, true)) : null; - } - // CraftBukkit end -+ // Paper start - 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 BeaconBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BEACON, pos, state); -@@ -186,7 +206,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - } - - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { -- BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower); -+ BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -@@ -272,8 +292,13 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - } - - public static List getHumansInRange(Level world, BlockPos blockposition, int i) { -+ // Paper start -+ return BeaconBlockEntity.getHumansInRange(world, blockposition, i, null); -+ } -+ public static List getHumansInRange(Level world, BlockPos blockposition, int i, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - { -- double d0 = (double) (i * 10 + 10); -+ double d0 = blockEntity != null ? blockEntity.getEffectRange() : (i * 10 + 10);// Paper - custom beacon ranges - - AABB axisalignedbb = (new AABB(blockposition)).inflate(d0).expandTowards(0.0D, (double) world.getHeight(), 0.0D); - List list = world.getEntitiesOfClass(Player.class, axisalignedbb); -@@ -314,12 +339,17 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - } - - private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect) { -+ // Paper start -+ BeaconBlockEntity.applyEffects(world, pos, beaconLevel, primaryEffect, secondaryEffect, null); -+ } -+ private static void applyEffects(Level world, BlockPos pos, int beaconLevel, @Nullable MobEffect primaryEffect, @Nullable MobEffect secondaryEffect, @Nullable BeaconBlockEntity blockEntity) { -+ // Paper end - if (!world.isClientSide && primaryEffect != null) { - double d0 = (double) (beaconLevel * 10 + 10); - byte b0 = BeaconBlockEntity.getAmplification(beaconLevel, primaryEffect, secondaryEffect); - - int j = BeaconBlockEntity.getLevel(beaconLevel); -- List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel); -+ List list = BeaconBlockEntity.getHumansInRange(world, pos, beaconLevel, blockEntity); // Paper - - BeaconBlockEntity.applyEffect(list, primaryEffect, j, b0, true, pos); // Paper - BeaconEffectEvent - -@@ -369,6 +399,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - } - - this.lockKey = LockCode.fromTag(nbt); -+ this.effectRange = nbt.contains(PAPER_RANGE_TAG, 6) ? nbt.getDouble(PAPER_RANGE_TAG) : -1; // Paper - } - - @Override -@@ -382,6 +413,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - } - - this.lockKey.addToTag(nbt); -+ nbt.putDouble(PAPER_RANGE_TAG, this.effectRange); // Paper - } - - public void setCustomName(@Nullable Component customName) { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -index c186a44b927188ed222f8b2f8f76aaef35d9c654..c47e613cae3d9252a8364ccc4d717e410bb0fc0c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeacon.java -@@ -29,7 +29,7 @@ public class CraftBeacon extends CraftBlockEntityState implem - if (tileEntity instanceof BeaconBlockEntity) { - BeaconBlockEntity beacon = (BeaconBlockEntity) tileEntity; - -- Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels); -+ Collection nms = BeaconBlockEntity.getHumansInRange(beacon.getLevel(), beacon.getBlockPos(), beacon.levels, beacon); // Paper - Collection bukkit = new ArrayList(nms.size()); - - for (Player human : nms) { -@@ -106,4 +106,21 @@ public class CraftBeacon extends CraftBlockEntityState implem - public void setLock(String key) { - this.getSnapshot().lockKey = (key == null) ? LockCode.NO_LOCK : new LockCode(key); - } -+ -+ // Paper start -+ @Override -+ public double getEffectRange() { -+ return this.getSnapshot().getEffectRange(); -+ } -+ -+ @Override -+ public void setEffectRange(double range) { -+ this.getSnapshot().setEffectRange(range); -+ } -+ -+ @Override -+ public void resetEffectRange() { -+ this.getSnapshot().resetEffectRange(); -+ } -+ // Paper end - } diff --git a/patches/server/0524-Add-API-for-quit-reason.patch b/patches/server/0524-Add-API-for-quit-reason.patch deleted file mode 100644 index 20ba1338d0..0000000000 --- a/patches/server/0524-Add-API-for-quit-reason.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:19:52 +0100 -Subject: [PATCH] Add API for quit reason - - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 4d2b26fb1ac5663b667ffd16eed379f4a3311677..dd81751f64695180331b82225ac878913afe4513 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -147,12 +147,15 @@ public class Connection extends SimpleChannelInboundHandler> { - - this.handlingFault = true; - if (this.channel.isOpen()) { -+ net.minecraft.server.level.ServerPlayer player = this.getPlayer(); // Paper - if (throwable instanceof TimeoutException) { - Connection.LOGGER.debug("Timeout", throwable); -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.TIMED_OUT; // Paper - this.disconnect(Component.translatable("disconnect.timeout")); - } else { - MutableComponent ichatmutablecomponent = Component.translatable("disconnect.genericReason", "Internal Exception: " + throwable); - -+ if (player != null) player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.ERRONEOUS_STATE; // Paper - if (flag) { - Connection.LOGGER.debug("Failed to sent packet", throwable); - ConnectionProtocol enumprotocol = this.getCurrentProtocol(); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 6f2b52165c1935511790a429792d3754251537c8..9b4436bdb697d8350eac57282f8fad8140029d8f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -266,6 +266,7 @@ public class ServerPlayer extends Player { - - public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper -+ 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 ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile, publicKey); -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3b422cd28b3fb8a172a734e3d59636b293d4a5cb..a6820c2262dd2198b772eae491c40ee379ec2da7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -515,6 +515,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure - // CraftBukkit end - -+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { - this.connection.disconnect(ichatbasecomponent); - })); -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index ceb5e9540e54275e97f08533d16c820061e76c8f..1d7a7a2d11f0524ae7a81fc0e3bdddd72f5ef53c 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -607,7 +607,7 @@ public abstract class PlayerList { - entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - } - -- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); -+ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())), entityplayer.quitReason); // Paper - quit reason - if (entityplayer.didPlayerJoinEvent) this.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/0524-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server/0524-Add-Wandering-Trader-spawn-rate-config-options.patch new file mode 100644 index 0000000000..a5adffed4f --- /dev/null +++ b/patches/server/0524-Add-Wandering-Trader-spawn-rate-config-options.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +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/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index 0130c5e7e1b42024f88c6d7dadd246b7744d7f91..fcde09e155727fe0b8b6acc79700fe4122fd22f0 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -43,43 +43,53 @@ public class WanderingTraderSpawner implements CustomSpawner { + + public WanderingTraderSpawner(ServerLevelData properties) { + this.serverLevelData = properties; +- this.tickDelay = 1200; +- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); +- this.spawnChance = properties.getWanderingTraderSpawnChance(); +- if (this.spawnDelay == 0 && this.spawnChance == 0) { +- this.spawnDelay = 24000; +- properties.setWanderingTraderSpawnDelay(this.spawnDelay); +- this.spawnChance = 25; +- properties.setWanderingTraderSpawnChance(this.spawnChance); +- } ++ // Paper start ++ this.tickDelay = Integer.MIN_VALUE; ++ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value ++ //if (this.spawnDelay == 0 && this.spawnChance == 0) { ++ // this.spawnDelay = 24000; ++ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); ++ // this.spawnChance = 25; ++ // properties.setWanderingTraderSpawnChance(this.spawnChance); ++ //} ++ // Paper end + + } + + @Override + public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { ++ // Paper start ++ if (this.tickDelay == Integer.MIN_VALUE) { ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ } + if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { + return 0; +- } else if (--this.tickDelay > 0) { ++ } else if (this.tickDelay - 1 > 0) { ++ this.tickDelay = this.tickDelay - 1; + return 0; + } else { +- this.tickDelay = 1200; +- this.spawnDelay -= 1200; +- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); ++ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; ++ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.spawnDelay > 0) { + return 0; + } else { +- this.spawnDelay = 24000; ++ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; + if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + return 0; + } else { + int i = this.spawnChance; + +- this.spawnChance = Mth.clamp(this.spawnChance + 25, (int) 25, (int) 75); +- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); ++ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); ++ //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways + if (this.random.nextInt(100) > i) { + return 0; + } else if (this.spawn(world)) { +- this.spawnChance = 25; ++ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; ++ // Paper end + return 1; + } else { + return 0; diff --git a/patches/server/0525-Add-Wandering-Trader-spawn-rate-config-options.patch b/patches/server/0525-Add-Wandering-Trader-spawn-rate-config-options.patch deleted file mode 100644 index a5adffed4f..0000000000 --- a/patches/server/0525-Add-Wandering-Trader-spawn-rate-config-options.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -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/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -index 0130c5e7e1b42024f88c6d7dadd246b7744d7f91..fcde09e155727fe0b8b6acc79700fe4122fd22f0 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -43,43 +43,53 @@ public class WanderingTraderSpawner implements CustomSpawner { - - public WanderingTraderSpawner(ServerLevelData properties) { - this.serverLevelData = properties; -- this.tickDelay = 1200; -- this.spawnDelay = properties.getWanderingTraderSpawnDelay(); -- this.spawnChance = properties.getWanderingTraderSpawnChance(); -- if (this.spawnDelay == 0 && this.spawnChance == 0) { -- this.spawnDelay = 24000; -- properties.setWanderingTraderSpawnDelay(this.spawnDelay); -- this.spawnChance = 25; -- properties.setWanderingTraderSpawnChance(this.spawnChance); -- } -+ // Paper start -+ this.tickDelay = Integer.MIN_VALUE; -+ //this.spawnDelay = properties.getWanderingTraderSpawnDelay(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //this.spawnChance = properties.getWanderingTraderSpawnChance(); // Paper - This value is read from the world file only for the first spawn, after which vanilla uses a hardcoded value -+ //if (this.spawnDelay == 0 && this.spawnChance == 0) { -+ // this.spawnDelay = 24000; -+ // properties.setWanderingTraderSpawnDelay(this.spawnDelay); -+ // this.spawnChance = 25; -+ // properties.setWanderingTraderSpawnChance(this.spawnChance); -+ //} -+ // Paper end - - } - - @Override - public int tick(ServerLevel world, boolean spawnMonsters, boolean spawnAnimals) { -+ // Paper start -+ if (this.tickDelay == Integer.MIN_VALUE) { -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ } - if (!world.getGameRules().getBoolean(GameRules.RULE_DO_TRADER_SPAWNING)) { - return 0; -- } else if (--this.tickDelay > 0) { -+ } else if (this.tickDelay - 1 > 0) { -+ this.tickDelay = this.tickDelay - 1; - return 0; - } else { -- this.tickDelay = 1200; -- this.spawnDelay -= 1200; -- this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); -+ this.tickDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ this.spawnDelay = this.spawnDelay - world.paperConfig().entities.spawning.wanderingTrader.spawnMinuteLength; -+ //this.serverLevelData.setWanderingTraderSpawnDelay(this.spawnDelay); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways - if (this.spawnDelay > 0) { - return 0; - } else { -- this.spawnDelay = 24000; -+ this.spawnDelay = world.paperConfig().entities.spawning.wanderingTrader.spawnDayLength; - if (!world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { - return 0; - } else { - int i = this.spawnChance; - -- this.spawnChance = Mth.clamp(this.spawnChance + 25, (int) 25, (int) 75); -- this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); -+ this.spawnChance = Mth.clamp(i + world.paperConfig().entities.spawning.wanderingTrader.spawnChanceFailureIncrement, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin, world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMax); -+ //this.serverLevelData.setWanderingTraderSpawnChance(this.spawnChance); // Paper - We don't need to save this value to disk if it gets set back to a hardcoded value anyways - if (this.random.nextInt(100) > i) { - return 0; - } else if (this.spawn(world)) { -- this.spawnChance = 25; -+ this.spawnChance = world.paperConfig().entities.spawning.wanderingTrader.spawnChanceMin; -+ // Paper end - return 1; - } else { - return 0; diff --git a/patches/server/0525-Expose-world-spawn-angle.patch b/patches/server/0525-Expose-world-spawn-angle.patch new file mode 100644 index 0000000000..5bf390c16c --- /dev/null +++ b/patches/server/0525-Expose-world-spawn-angle.patch @@ -0,0 +1,19 @@ +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 1d7a7a2d11f0524ae7a81fc0e3bdddd72f5ef53c..195fcf93c46967e14a3247768a575c7a73bf9e31 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -885,7 +885,7 @@ public abstract class PlayerList { + if (location == null) { + worldserver1 = this.server.getLevel(Level.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.levelData.getSpawnAngle(), 0.0F); // Paper - use world spawn angle + } + + Player respawnPlayer = entityplayer1.getBukkitEntity(); diff --git a/patches/server/0526-Add-Destroy-Speed-API.patch b/patches/server/0526-Add-Destroy-Speed-API.patch new file mode 100644 index 0000000000..a2b7640fee --- /dev/null +++ b/patches/server/0526-Add-Destroy-Speed-API.patch @@ -0,0 +1,38 @@ +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 2c1efaecf220588d8b74946194bf340871e57004..653dcec33f83ab490af424faa6ede7df07d41742 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -678,5 +678,26 @@ public class CraftBlock implements Block { + public String translationKey() { + 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).handle; ++ if (nmsItemStack == null) { ++ nmsItemStack = net.minecraft.world.item.ItemStack.EMPTY; ++ } ++ } else { ++ nmsItemStack = CraftItemStack.asNMSCopy(itemStack); ++ } ++ float speed = nmsItemStack.getDestroySpeed(this.getNMS().getBlock().defaultBlockState()); ++ if (speed > 1.0F && considerEnchants) { ++ int enchantLevel = net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BLOCK_EFFICIENCY, nmsItemStack); ++ if (enchantLevel > 0) { ++ speed += enchantLevel * enchantLevel + 1; ++ } ++ } ++ return speed; ++ } + // Paper end + } diff --git a/patches/server/0526-Expose-world-spawn-angle.patch b/patches/server/0526-Expose-world-spawn-angle.patch deleted file mode 100644 index 5bf390c16c..0000000000 --- a/patches/server/0526-Expose-world-spawn-angle.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 1d7a7a2d11f0524ae7a81fc0e3bdddd72f5ef53c..195fcf93c46967e14a3247768a575c7a73bf9e31 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -885,7 +885,7 @@ public abstract class PlayerList { - if (location == null) { - worldserver1 = this.server.getLevel(Level.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.levelData.getSpawnAngle(), 0.0F); // Paper - use world spawn angle - } - - Player respawnPlayer = entityplayer1.getBukkitEntity(); diff --git a/patches/server/0527-Add-Destroy-Speed-API.patch b/patches/server/0527-Add-Destroy-Speed-API.patch deleted file mode 100644 index a2b7640fee..0000000000 --- a/patches/server/0527-Add-Destroy-Speed-API.patch +++ /dev/null @@ -1,38 +0,0 @@ -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 2c1efaecf220588d8b74946194bf340871e57004..653dcec33f83ab490af424faa6ede7df07d41742 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -678,5 +678,26 @@ public class CraftBlock implements Block { - public String translationKey() { - 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).handle; -+ if (nmsItemStack == null) { -+ nmsItemStack = net.minecraft.world.item.ItemStack.EMPTY; -+ } -+ } else { -+ nmsItemStack = CraftItemStack.asNMSCopy(itemStack); -+ } -+ float speed = nmsItemStack.getDestroySpeed(this.getNMS().getBlock().defaultBlockState()); -+ if (speed > 1.0F && considerEnchants) { -+ int enchantLevel = net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BLOCK_EFFICIENCY, nmsItemStack); -+ if (enchantLevel > 0) { -+ speed += enchantLevel * enchantLevel + 1; -+ } -+ } -+ return speed; -+ } - // Paper end - } diff --git a/patches/server/0527-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0527-Fix-Player-spawnParticle-x-y-z-precision-loss.patch new file mode 100644 index 0000000000..c8c87f141b --- /dev/null +++ b/patches/server/0527-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 09f5a288cba1825fa0fff32f771f98675f473527..21f413b1e75c15fae888a4e6dccefbe822ad1c40 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2263,7 +2263,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()); + } +- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); ++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(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 + this.getHandle().connection.send(packetplayoutworldparticles); + + } diff --git a/patches/server/0528-Add-LivingEntity-clearActiveItem.patch b/patches/server/0528-Add-LivingEntity-clearActiveItem.patch new file mode 100644 index 0000000000..2eb3c37c50 --- /dev/null +++ b/patches/server/0528-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 679e08544fc1e6973605dd2f3953c12a0d338ddd..4e7cb29909fd82d26029e4c78580ead500db5c2a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -851,6 +851,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return getHandle().getUseItem().asBukkitMirror(); + } + ++ // Paper start ++ @Override ++ public void clearActiveItem() { ++ getHandle().stopUsingItem(); ++ } ++ // Paper end ++ + @Override + public int getItemUseRemainingTime() { + return getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0528-Fix-Player-spawnParticle-x-y-z-precision-loss.patch b/patches/server/0528-Fix-Player-spawnParticle-x-y-z-precision-loss.patch deleted file mode 100644 index c8c87f141b..0000000000 --- a/patches/server/0528-Fix-Player-spawnParticle-x-y-z-precision-loss.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Esophose -Date: Sat, 3 Oct 2020 18:57:47 -0600 -Subject: [PATCH] Fix Player spawnParticle x/y/z precision loss - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 09f5a288cba1825fa0fff32f771f98675f473527..21f413b1e75c15fae888a4e6dccefbe822ad1c40 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2263,7 +2263,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()); - } -- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); -+ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(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 - this.getHandle().connection.send(packetplayoutworldparticles); - - } diff --git a/patches/server/0529-Add-LivingEntity-clearActiveItem.patch b/patches/server/0529-Add-LivingEntity-clearActiveItem.patch deleted file mode 100644 index bed1819376..0000000000 --- a/patches/server/0529-Add-LivingEntity-clearActiveItem.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anrza -Date: Wed, 15 Jul 2020 12:08:49 +0200 -Subject: [PATCH] Add LivingEntity#clearActiveItem - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 594db7ab7c78f4f7f17781f339431bf5f133b8bf..6e2e217d965ea6b8601e5ba0f8a44b817ee6654d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -810,6 +810,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return getHandle().getUseItem().asBukkitMirror(); - } - -+ // Paper start -+ @Override -+ public void clearActiveItem() { -+ getHandle().stopUsingItem(); -+ } -+ // Paper end -+ - @Override - public int getItemUseRemainingTime() { - return getHandle().getUseItemRemainingTicks(); diff --git a/patches/server/0529-Add-PlayerItemCooldownEvent.patch b/patches/server/0529-Add-PlayerItemCooldownEvent.patch new file mode 100644 index 0000000000..751841d117 --- /dev/null +++ b/patches/server/0529-Add-PlayerItemCooldownEvent.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 25 Aug 2020 13:48:33 +0200 +Subject: [PATCH] Add PlayerItemCooldownEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +index 47283d2a49209839002212e663a503a82ea86587..ce026600b3b5c846d991a0dfe599708caf2a2962 100644 +--- a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java ++++ b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java +@@ -10,6 +10,16 @@ public class ServerItemCooldowns extends ItemCooldowns { + this.player = player; + } + ++ // Paper start ++ @Override ++ public void addCooldown(Item item, int duration) { ++ io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent(this.player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(item), duration); ++ if (event.callEvent()) { ++ super.addCooldown(item, event.getCooldown()); ++ } ++ } ++ // Paper end ++ + @Override + protected void onCooldownStarted(Item item, int duration) { + super.onCooldownStarted(item, duration); diff --git a/patches/server/0530-Add-PlayerItemCooldownEvent.patch b/patches/server/0530-Add-PlayerItemCooldownEvent.patch deleted file mode 100644 index 751841d117..0000000000 --- a/patches/server/0530-Add-PlayerItemCooldownEvent.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 25 Aug 2020 13:48:33 +0200 -Subject: [PATCH] Add PlayerItemCooldownEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -index 47283d2a49209839002212e663a503a82ea86587..ce026600b3b5c846d991a0dfe599708caf2a2962 100644 ---- a/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -+++ b/src/main/java/net/minecraft/world/item/ServerItemCooldowns.java -@@ -10,6 +10,16 @@ public class ServerItemCooldowns extends ItemCooldowns { - this.player = player; - } - -+ // Paper start -+ @Override -+ public void addCooldown(Item item, int duration) { -+ io.papermc.paper.event.player.PlayerItemCooldownEvent event = new io.papermc.paper.event.player.PlayerItemCooldownEvent(this.player.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(item), duration); -+ if (event.callEvent()) { -+ super.addCooldown(item, event.getCooldown()); -+ } -+ } -+ // Paper end -+ - @Override - protected void onCooldownStarted(Item item, int duration) { - super.onCooldownStarted(item, duration); diff --git a/patches/server/0530-Significantly-improve-performance-of-the-end-generat.patch b/patches/server/0530-Significantly-improve-performance-of-the-end-generat.patch new file mode 100644 index 0000000000..c223e5c115 --- /dev/null +++ b/patches/server/0530-Significantly-improve-performance-of-the-end-generat.patch @@ -0,0 +1,63 @@ +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/levelgen/DensityFunctions.java b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java +index 683474cd96d3a0cdfb3b22d0111c8d3231f01d92..f09fd5027535d2fc4d4afae010f08e7accff45b3 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java +@@ -489,6 +489,16 @@ public final class DensityFunctions { + public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(new DensityFunctions.EndIslandDensityFunction(0L))); + private static final float ISLAND_THRESHOLD = -0.9F; + private final SimplexNoise islandNoise; ++ // 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 EndIslandDensityFunction(long seed) { + RandomSource randomSource = new LegacyRandomSource(seed); +@@ -504,12 +514,26 @@ public final class DensityFunctions { + float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow + f = Mth.clamp(f, -100.0F, 80.0F); + ++ NoiseCache cache = noiseCache.get().computeIfAbsent(sampler, noiseKey -> new NoiseCache()); // Paper + for(int m = -12; m <= 12; ++m) { + for(int n = -12; n <= 12; ++n) { + long o = (long)(i + m); + long p = (long)(j + n); +- if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) { +- float g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F; ++ // Paper start - Significantly improve end generation performance by using a noise cache ++ long key = net.minecraft.world.level.ChunkPos.asLong((int) o, (int) p); ++ int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191; ++ float g = Float.MIN_VALUE; ++ if (cache.keys[index] == key) { ++ g = cache.values[index]; ++ } else { ++ if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) { ++ g = (Mth.abs((float) o) * 3439.0F + Mth.abs((float) p) * 147.0F) % 13.0F + 9.0F; ++ } ++ cache.keys[index] = key; ++ cache.values[index] = g; ++ } ++ if (g != Float.MIN_VALUE) { ++ // Paper end + float h = (float)(k - m * 2); + float q = (float)(l - n * 2); + float r = 100.0F - Mth.sqrt(h * h + q * q) * g; diff --git a/patches/server/0531-More-lightning-API.patch b/patches/server/0531-More-lightning-API.patch new file mode 100644 index 0000000000..142479579e --- /dev/null +++ b/patches/server/0531-More-lightning-API.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sun, 26 Jul 2020 14:44:09 +0200 +Subject: [PATCH] More lightning API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +index f7991ff14ef9cda0327b8621bf615b49cffd7ac5..e515e819774bfb31ec03f05a5502921e66f2b0e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java +@@ -45,4 +45,38 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike + return this.spigot; + } + // Spigot end ++ ++ // Paper start ++ @Override ++ public int getFlashCount() { ++ return getHandle().flashes; ++ } ++ ++ @Override ++ public void setFlashCount(int flashes) { ++ com.google.common.base.Preconditions.checkArgument(flashes >= 0, "Flashes has to be a positive number!"); ++ getHandle().flashes = flashes; ++ } ++ ++ @Override ++ public int getLifeTicks() { ++ return getHandle().life; ++ } ++ ++ @Override ++ public void setLifeTicks(int lifeTicks) { ++ getHandle().life = lifeTicks; ++ } ++ ++ @Override ++ public @org.jetbrains.annotations.Nullable org.bukkit.entity.Entity getCausingEntity() { ++ final var cause = this.getHandle().getCause(); ++ return cause == null ? null : cause.getBukkitEntity(); ++ } ++ ++ @Override ++ public void setCausingPlayer(@org.jetbrains.annotations.Nullable org.bukkit.entity.Player causingPlayer) { ++ this.getHandle().setCause(causingPlayer == null ? null : ((CraftPlayer) causingPlayer).getHandle()); ++ } ++ // Paper end + } diff --git a/patches/server/0531-Significantly-improve-performance-of-the-end-generat.patch b/patches/server/0531-Significantly-improve-performance-of-the-end-generat.patch deleted file mode 100644 index c223e5c115..0000000000 --- a/patches/server/0531-Significantly-improve-performance-of-the-end-generat.patch +++ /dev/null @@ -1,63 +0,0 @@ -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/levelgen/DensityFunctions.java b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -index 683474cd96d3a0cdfb3b22d0111c8d3231f01d92..f09fd5027535d2fc4d4afae010f08e7accff45b3 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/DensityFunctions.java -@@ -489,6 +489,16 @@ public final class DensityFunctions { - public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(MapCodec.unit(new DensityFunctions.EndIslandDensityFunction(0L))); - private static final float ISLAND_THRESHOLD = -0.9F; - private final SimplexNoise islandNoise; -+ // 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 EndIslandDensityFunction(long seed) { - RandomSource randomSource = new LegacyRandomSource(seed); -@@ -504,12 +514,26 @@ public final class DensityFunctions { - float f = 100.0F - Mth.sqrt((long) x * (long) x + (long) z * (long) z) * 8.0F; // Paper - cast ints to long to avoid integer overflow - f = Mth.clamp(f, -100.0F, 80.0F); - -+ NoiseCache cache = noiseCache.get().computeIfAbsent(sampler, noiseKey -> new NoiseCache()); // Paper - for(int m = -12; m <= 12; ++m) { - for(int n = -12; n <= 12; ++n) { - long o = (long)(i + m); - long p = (long)(j + n); -- if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) { -- float g = (Mth.abs((float)o) * 3439.0F + Mth.abs((float)p) * 147.0F) % 13.0F + 9.0F; -+ // Paper start - Significantly improve end generation performance by using a noise cache -+ long key = net.minecraft.world.level.ChunkPos.asLong((int) o, (int) p); -+ int index = (int) it.unimi.dsi.fastutil.HashCommon.mix(key) & 8191; -+ float g = Float.MIN_VALUE; -+ if (cache.keys[index] == key) { -+ g = cache.values[index]; -+ } else { -+ if (o * o + p * p > 4096L && sampler.getValue((double)o, (double)p) < (double)-0.9F) { -+ g = (Mth.abs((float) o) * 3439.0F + Mth.abs((float) p) * 147.0F) % 13.0F + 9.0F; -+ } -+ cache.keys[index] = key; -+ cache.values[index] = g; -+ } -+ if (g != Float.MIN_VALUE) { -+ // Paper end - float h = (float)(k - m * 2); - float q = (float)(l - n * 2); - float r = 100.0F - Mth.sqrt(h * h + q * q) * g; diff --git a/patches/server/0532-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0532-Climbing-should-not-bypass-cramming-gamerule.patch new file mode 100644 index 0000000000..55702b598c --- /dev/null +++ b/patches/server/0532-Climbing-should-not-bypass-cramming-gamerule.patch @@ -0,0 +1,157 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Sun, 23 Aug 2020 20:59:00 +0200 +Subject: [PATCH] Climbing should not bypass cramming gamerule + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 902531525c213bd39aaac138f30ee1fc3e7c24a7..96bb4a32a04851bd3a83f2b214efd2297ff8b57e 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1846,6 +1846,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public boolean isPushable() { ++ // Paper start ++ return isCollidable(false); ++ } ++ ++ public boolean isCollidable(boolean ignoreClimbing) { ++ // Paper end + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java +index 22f36cd3df49160f1b6668befdd05c2268edaa49..e39965c2e50bc8ee424ea07819346e0611398e28 100644 +--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java ++++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java +@@ -45,11 +45,17 @@ public final class EntitySelector { + } + + public static Predicate pushableBy(Entity entity) { ++ // Paper start - ignoreClimbing param ++ return pushable(entity, false); ++ } ++ ++ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { ++ // Paper end + Team scoreboardteambase = entity.getTeam(); + Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteambase == null ? Team.CollisionRule.ALWAYS : scoreboardteambase.getCollisionRule(); + + return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { +- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API ++ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - isCollidable + return false; + } else if (entity.level.isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { + return false; +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 049d4422136a342556951dc0114435f6c2ede946..d6ad8cfb509920c448fc51ec02e867b1552730df 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3357,7 +3357,7 @@ public abstract class LivingEntity extends Entity { + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); ++ List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, level.paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule + + if (!list.isEmpty()) { + // Paper - move up +@@ -3520,9 +3520,16 @@ public abstract class LivingEntity extends Entity { + return !this.isRemoved() && this.collides; // CraftBukkit + } + ++ // Paper start + @Override + public boolean isPushable() { +- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit ++ return this.isCollidable(level.paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit ++ // Paper end + } + + // CraftBukkit start - collidable API +diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +index e9f6e31cd557410a4ad4ced7086c4a846f13a4f6..50d4595b81f24949011c7565c5e3fc8c26c86019 100644 +--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java ++++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java +@@ -82,7 +82,7 @@ public class Bat extends AmbientCreature { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +index 90b98b823855037ce6efab1f478d875f225f65ed..a2977596c672a5a435f56bb20fbfb7b59882dda6 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java +@@ -382,8 +382,8 @@ public class Parrot extends ShoulderRidingEntity implements FlyingAnimal { + } + + @Override +- public boolean isPushable() { +- return super.isPushable(); // 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/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 119ee27ceb873c67d1d0904da903401e216eb450..04a119e6641898454253e2478bc1b4dff181b5ee 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -243,7 +243,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return !this.isVehicle(); + } + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 808e564789d826c1778c053ab91038e3d4d81b7f..150afceb491cfd254c0f1b84800e6df14cf26676 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -342,7 +342,7 @@ public class ArmorStand extends LivingEntity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return false; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 7d60f56b2e8206e8e546abdd06ea74a2ead6e75d..4984b2b3294e425247b595bcf36812728fb4cd16 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -147,7 +147,7 @@ public abstract class AbstractMinecart extends Entity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 5d927b6331f3d5cb7dd34be3e7ea4443f52f9ead..8471c34d02ba5819580754f98ce8cc0b50a0b328 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -157,7 +157,7 @@ public class Boat extends Entity { + } + + @Override +- public boolean isPushable() { ++ public boolean isCollidable(boolean ignoreClimbing) { // Paper + return true; + } + diff --git a/patches/server/0532-More-lightning-API.patch b/patches/server/0532-More-lightning-API.patch deleted file mode 100644 index 142479579e..0000000000 --- a/patches/server/0532-More-lightning-API.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sun, 26 Jul 2020 14:44:09 +0200 -Subject: [PATCH] More lightning API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -index f7991ff14ef9cda0327b8621bf615b49cffd7ac5..e515e819774bfb31ec03f05a5502921e66f2b0e2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLightningStrike.java -@@ -45,4 +45,38 @@ public class CraftLightningStrike extends CraftEntity implements LightningStrike - return this.spigot; - } - // Spigot end -+ -+ // Paper start -+ @Override -+ public int getFlashCount() { -+ return getHandle().flashes; -+ } -+ -+ @Override -+ public void setFlashCount(int flashes) { -+ com.google.common.base.Preconditions.checkArgument(flashes >= 0, "Flashes has to be a positive number!"); -+ getHandle().flashes = flashes; -+ } -+ -+ @Override -+ public int getLifeTicks() { -+ return getHandle().life; -+ } -+ -+ @Override -+ public void setLifeTicks(int lifeTicks) { -+ getHandle().life = lifeTicks; -+ } -+ -+ @Override -+ public @org.jetbrains.annotations.Nullable org.bukkit.entity.Entity getCausingEntity() { -+ final var cause = this.getHandle().getCause(); -+ return cause == null ? null : cause.getBukkitEntity(); -+ } -+ -+ @Override -+ public void setCausingPlayer(@org.jetbrains.annotations.Nullable org.bukkit.entity.Player causingPlayer) { -+ this.getHandle().setCause(causingPlayer == null ? null : ((CraftPlayer) causingPlayer).getHandle()); -+ } -+ // Paper end - } diff --git a/patches/server/0533-Added-missing-default-perms-for-commands.patch b/patches/server/0533-Added-missing-default-perms-for-commands.patch new file mode 100644 index 0000000000..ddaf0c6aec --- /dev/null +++ b/patches/server/0533-Added-missing-default-perms-for-commands.patch @@ -0,0 +1,206 @@ +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 ca30f9c590f792caa8f1b76d7219e9121d932673..0467419fc8a06c241a46216c8f8c32abeb9fbc26 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +@@ -25,12 +25,67 @@ public final class CommandPermissions { + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "xp", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); +- DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "toggledownfall", "Allows the user to toggle rain on/off for a given world", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "effect", "Allows the user to add/remove effects on players", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "selector", "Allows the use of selectors", PermissionDefault.OP, commands); + DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "trigger", "Allows the use of the trigger command", PermissionDefault.TRUE, commands); ++ // Paper start ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "attribute", "Allows the user to query, add, remove or set an entity attribute", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "advancement", "Allows the user to give, remove, or check player advancements", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "ban", "Allows the user to add players to the ban list", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "ban-ip", "Allows the user to add ip address to the ban list", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "banlist", "Allows the user to display the ban list", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "bossbar", "Allows the user to create and modify bossbars", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "clear", "Allows the user to clear items from player inventory", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "clone", "Allows the user to copy blocks from one place to another", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "data", "Allows the user to get, merge, modify, and remove block entity and entity NBT data", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "datapack", "Allows the user to control loaded data packs", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "debug", "Allows the user to start or stop a debugging session", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "deop", "Allows the user to revoke operator status from a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "difficulty", "Allows the user to set the difficulty level", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "enchant", "Allows the user to enchant a player item", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "execute", "Allows the user to execute another command", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "fill", "Allows the user to fill a region with a specific block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "forceload", "Allows the user to force chunks to be constantly loaded or not", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "function", "Allows the user to run a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamerule", "Allows a user to set or query a game rule value", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "jfr", "Allows a user to use the vanilla Java FlightRecorder profiling system", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "locate", "Allows the user to locate the closest structure", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "loot", "Allows the user to drop items from an inventory slot onto the ground", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "op", "Allows the user to grant operator status to a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "pardon", "Allows the user to remove entries from the player ban list", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "pardon-ip", "Allows the user to remove entries from the ip address ban list", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "particle", "Allows the user to create particles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "perf", "Allows the user to start/stop the vanilla performance metrics capture", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "playsound", "Allows the user to play a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "recipe", "Allows the user to give or take recipes", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "reload", "Allows the user to reload loot tables, advancements, and functions from disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "item", "Allows the user to replace items in inventories", PermissionDefault.OP, commands); // Remove in 1.17 (replaced by /item) ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-all", "Allows the user to save the server to disk", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-off", "Allows the user disable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-on", "Allows the user enable automatic server saves", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "schedule", "Allows the user to delay the execution of a function", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "scoreboard", "Allows the user manage scoreboard objectives and players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setblock", "Allows the user to change a block to another block", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setidletimeout", "Allows the user to set the time before idle players are kicked", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setworldspawn", "Allows the user to set the world spawn", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spawnpoint", "Allows the user to set the spawn point for a player", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spectate", "Allows the user to make one player in spectator mode spectate an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spreadplayers", "Allows the user to teleport entities to random locations", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "stopsound", "Allows the user to stop a sound", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "summon", "Allows the user to summon an entity", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "tag", "Allows the user to control entity tags", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "team", "Allows the user to control teams", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "teammsg", "Allows the user to specify the message to send to team", PermissionDefault.TRUE, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "tellraw", "Allows the user to display a JSON message to players", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "time", "Allows the user to change or query the world's game time", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "title", "Allows the user to manage screen titles", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "weather", "Allows the user to set the weather", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "whitelist", "Allows the user to manage the server whitelist", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "worldborder", "Allows the user to manage the world border", PermissionDefault.OP, commands); ++ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "place", "Allows the user to place features and structures", PermissionDefault.OP, commands); ++ // Paper end + + DefaultPermissions.registerPermission("minecraft.admin.command_feedback", "Receive command broadcasts when sendCommandFeedback is true", PermissionDefault.OP, commands); + +diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..da2eb57ea64403657744ea00eff40243ef51df58 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +@@ -0,0 +1,86 @@ ++package io.papermc.paper.permissions; ++ ++import com.mojang.brigadier.tree.CommandNode; ++import com.mojang.brigadier.tree.RootCommandNode; ++import net.minecraft.commands.CommandBuildContext; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.commands.Commands; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.server.Bootstrap; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; ++import org.bukkit.permissions.Permission; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.AfterClass; ++import org.junit.BeforeClass; ++import org.junit.Test; ++ ++import java.io.PrintStream; ++import java.util.HashSet; ++import java.util.LinkedHashSet; ++import java.util.List; ++import java.util.Set; ++import java.util.TreeSet; ++ ++import static org.junit.Assert.assertTrue; ++ ++public class MinecraftCommandPermissionsTest extends AbstractTestingBase { ++ ++ private static PrintStream old; ++ @BeforeClass ++ public static void before() { ++ old = System.out; ++ System.setOut(Bootstrap.STDOUT); ++ } ++ ++ @Test ++ public void test() { ++ CraftDefaultPermissions.registerCorePermissions(); ++ Set perms = collectMinecraftCommandPerms(); ++ ++ Commands commands = new Commands(Commands.CommandSelection.DEDICATED, new CommandBuildContext(RegistryAccess.BUILTIN.get())); ++ RootCommandNode root = commands.getDispatcher().getRoot(); ++ Set missing = new LinkedHashSet<>(); ++ Set foundPerms = new HashSet<>(); ++ for (CommandNode child : root.getChildren()) { ++ final String vanillaPerm = VanillaCommandWrapper.getPermission(child); ++ if (!perms.contains(vanillaPerm)) { ++ missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); ++ } else { ++ foundPerms.add(vanillaPerm); ++ } ++ } ++ assertTrue("Commands missing permissions: \n" + String.join("\n", missing), missing.isEmpty()); ++ perms.removeAll(foundPerms); ++ assertTrue("Extra permissions not associated with a command: \n" + String.join("\n", perms), perms.isEmpty()); ++ } ++ ++ private static final List TO_SKIP = List.of( ++ "minecraft.command.selector" ++ ); ++ ++ private static Set collectMinecraftCommandPerms() { ++ Set perms = new TreeSet<>(); ++ for (Permission perm : Bukkit.getPluginManager().getPermissions()) { ++ if (perm.getName().startsWith("minecraft.command.")) { ++ if (TO_SKIP.contains(perm.getName())) { ++ continue; ++ } ++ if (perm.getName().endsWith(".xp")) { ++ perms.add("minecraft.command.experience"); // for the "experience" command, craftbukkit perm is "minecraft.command.xp" ++ continue; ++ } ++ perms.add(perm.getName()); ++ } ++ } ++ return perms; ++ } ++ ++ @AfterClass ++ public static void after() { ++ if (old != null) { ++ System.setOut(old); ++ } ++ } ++} +diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java +index 946497353a64421592d2bae012c9a3cb874dd5b8..2ddceb709291d3bd713621ffa4020c02ec26bb21 100644 +--- a/src/test/java/org/bukkit/support/DummyServer.java ++++ b/src/test/java/org/bukkit/support/DummyServer.java +@@ -106,7 +106,21 @@ public final class DummyServer implements InvocationHandler { + } + } + ); +- Bukkit.setServer(Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(new DummyServer())); ++ // Paper start - modeled off of TestServer in the API tests module ++ methods.put( ++ Server.class.getMethod("getPluginManager"), ++ new MethodHandler() { ++ @Override ++ public Object handle(DummyServer server, Object[] args) { ++ return server.pluginManager; ++ } ++ } ++ ); ++ DummyServer server = new DummyServer(); ++ Server instance = Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(server); ++ Bukkit.setServer(instance); ++ server.pluginManager = new org.bukkit.plugin.SimplePluginManager(instance, new org.bukkit.command.SimpleCommandMap(instance)); ++ // Paper end + } catch (Throwable t) { + throw new Error(t); + } +@@ -116,6 +130,7 @@ public final class DummyServer implements InvocationHandler { + + private DummyServer() {}; + ++ private org.bukkit.plugin.PluginManager pluginManager; // Paper + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + MethodHandler handler = DummyServer.methods.get(method); diff --git a/patches/server/0533-Climbing-should-not-bypass-cramming-gamerule.patch b/patches/server/0533-Climbing-should-not-bypass-cramming-gamerule.patch deleted file mode 100644 index 50b7fcd2d3..0000000000 --- a/patches/server/0533-Climbing-should-not-bypass-cramming-gamerule.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Sun, 23 Aug 2020 20:59:00 +0200 -Subject: [PATCH] Climbing should not bypass cramming gamerule - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 532678194c2724e31a19f0e4d73d79d84ef6699c..11a5a0b3002300b882511b4ebafe96af5e230cb8 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1832,6 +1832,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public boolean isPushable() { -+ // Paper start -+ return isCollidable(false); -+ } -+ -+ public boolean isCollidable(boolean ignoreClimbing) { -+ // Paper end - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 22f36cd3df49160f1b6668befdd05c2268edaa49..e39965c2e50bc8ee424ea07819346e0611398e28 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -45,11 +45,17 @@ public final class EntitySelector { - } - - public static Predicate pushableBy(Entity entity) { -+ // Paper start - ignoreClimbing param -+ return pushable(entity, false); -+ } -+ -+ public static Predicate pushable(Entity entity, boolean ignoreClimbing) { -+ // Paper end - Team scoreboardteambase = entity.getTeam(); - Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteambase == null ? Team.CollisionRule.ALWAYS : scoreboardteambase.getCollisionRule(); - - return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> { -- if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API -+ if (!entity1.isCollidable(ignoreClimbing) || !entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API // Paper - isCollidable - return false; - } else if (entity.level.isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) { - return false; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 09513bc7a5b78580da415d486369b9403e99c773..502d54845e1bcf538149a51be995320c6df10acb 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3335,7 +3335,7 @@ public abstract class LivingEntity extends Entity { - return; - } - // Paper end - don't run getEntities if we're not going to use its result -- List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushableBy(this)); -+ List list = this.level.getEntities((Entity) this, this.getBoundingBox(), EntitySelector.pushable(this, level.paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - fix climbing bypassing cramming rule - - if (!list.isEmpty()) { - // Paper - move up -@@ -3498,9 +3498,16 @@ public abstract class LivingEntity extends Entity { - return !this.isRemoved() && this.collides; // CraftBukkit - } - -+ // Paper start - @Override - public boolean isPushable() { -- return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit -+ return this.isCollidable(level.paperConfig().collisions.fixClimbingBypassingCrammingRule); -+ } -+ -+ @Override -+ public boolean isCollidable(boolean ignoreClimbing) { -+ return this.isAlive() && !this.isSpectator() && (ignoreClimbing || !this.onClimbable()) && this.collides; // CraftBukkit -+ // Paper end - } - - // CraftBukkit start - collidable API -diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -index e9f6e31cd557410a4ad4ced7086c4a846f13a4f6..50d4595b81f24949011c7565c5e3fc8c26c86019 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -82,7 +82,7 @@ public class Bat extends AmbientCreature { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -index 90b98b823855037ce6efab1f478d875f225f65ed..a2977596c672a5a435f56bb20fbfb7b59882dda6 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -382,8 +382,8 @@ public class Parrot extends ShoulderRidingEntity implements FlyingAnimal { - } - - @Override -- public boolean isPushable() { -- return super.isPushable(); // 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/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index 119ee27ceb873c67d1d0904da903401e216eb450..04a119e6641898454253e2478bc1b4dff181b5ee 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -243,7 +243,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - return !this.isVehicle(); - } - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -index 808e564789d826c1778c053ab91038e3d4d81b7f..150afceb491cfd254c0f1b84800e6df14cf26676 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -342,7 +342,7 @@ public class ArmorStand extends LivingEntity { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index 7d60f56b2e8206e8e546abdd06ea74a2ead6e75d..4984b2b3294e425247b595bcf36812728fb4cd16 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -147,7 +147,7 @@ public abstract class AbstractMinecart extends Entity { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - return true; - } - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 5d927b6331f3d5cb7dd34be3e7ea4443f52f9ead..8471c34d02ba5819580754f98ce8cc0b50a0b328 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -157,7 +157,7 @@ public class Boat extends Entity { - } - - @Override -- public boolean isPushable() { -+ public boolean isCollidable(boolean ignoreClimbing) { // Paper - return true; - } - diff --git a/patches/server/0534-Add-PlayerShearBlockEvent.patch b/patches/server/0534-Add-PlayerShearBlockEvent.patch new file mode 100644 index 0000000000..4004036b95 --- /dev/null +++ b/patches/server/0534-Add-PlayerShearBlockEvent.patch @@ -0,0 +1,70 @@ +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/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 8dff119fa83e44d71b10bb3ea6e5f4e886a48c9c..1aaab26c59bb9255955aff34ea1d057b88152768 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -113,7 +113,7 @@ public class BeehiveBlock extends BaseEntityBlock { + } + + public static void dropHoneycomb(Level world, BlockPos pos) { +- popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); ++ popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - conflict on change, item needs to be set below + } + + @Override +@@ -126,8 +126,19 @@ public class BeehiveBlock extends BaseEntityBlock { + Item item = itemstack.getItem(); + + if (itemstack.is(Items.SHEARS)) { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), net.minecraft.server.MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (hand == InteractionHand.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 InteractionResult.PASS; ++ } ++ // Paper end + world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.NEUTRAL, 1.0F, 1.0F); +- BeehiveBlock.dropHoneycomb(world, pos); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) { ++ popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop)); ++ } ++ // Paper end + itemstack.hurtAndBreak(1, player, (entityhuman1) -> { + entityhuman1.broadcastBreakEvent(hand); + }); +diff --git a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +index 0e8cbe7a465edc31b78b7e47a928435f9c2b6bd9..f998598a34315389dd74b82e4b9c8448f0aae253 100644 +--- a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java +@@ -27,13 +27,24 @@ public class PumpkinBlock extends StemGrownBlock { + ItemStack itemStack = player.getItemInHand(hand); + if (itemStack.is(Items.SHEARS)) { + if (!world.isClientSide) { ++ // Paper start - Add PlayerShearBlockEvent ++ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), net.minecraft.server.MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (hand == InteractionHand.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 InteractionResult.PASS; ++ } ++ // Paper end + Direction direction = hit.getDirection(); + Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction; + world.playSound((Player)null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11); +- ItemEntity itemEntity = new ItemEntity(world, (double)pos.getX() + 0.5D + (double)direction2.getStepX() * 0.65D, (double)pos.getY() + 0.1D, (double)pos.getZ() + 0.5D + (double)direction2.getStepZ() * 0.65D, new ItemStack(Items.PUMPKIN_SEEDS, 4)); ++ // Paper start - Add PlayerShearBlockEvent ++ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { ++ ItemEntity itemEntity = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) direction2.getStepX() * 0.65D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D + (double) direction2.getStepZ() * 0.65D, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); ++ // Paper end + itemEntity.setDeltaMovement(0.05D * (double)direction2.getStepX() + world.random.nextDouble() * 0.02D, 0.05D, 0.05D * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02D); + world.addFreshEntity(itemEntity); ++ } // Paper - Add PlayerShearBlockEvent + itemStack.hurtAndBreak(1, player, (playerx) -> { + playerx.broadcastBreakEvent(hand); + }); diff --git a/patches/server/0534-Added-missing-default-perms-for-commands.patch b/patches/server/0534-Added-missing-default-perms-for-commands.patch deleted file mode 100644 index ddaf0c6aec..0000000000 --- a/patches/server/0534-Added-missing-default-perms-for-commands.patch +++ /dev/null @@ -1,206 +0,0 @@ -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 ca30f9c590f792caa8f1b76d7219e9121d932673..0467419fc8a06c241a46216c8f8c32abeb9fbc26 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java -@@ -25,12 +25,67 @@ public final class CommandPermissions { - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "xp", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); -- DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "toggledownfall", "Allows the user to toggle rain on/off for a given world", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "effect", "Allows the user to add/remove effects on players", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "selector", "Allows the use of selectors", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "trigger", "Allows the use of the trigger command", PermissionDefault.TRUE, commands); -+ // Paper start -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "attribute", "Allows the user to query, add, remove or set an entity attribute", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "advancement", "Allows the user to give, remove, or check player advancements", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "ban", "Allows the user to add players to the ban list", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "ban-ip", "Allows the user to add ip address to the ban list", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "banlist", "Allows the user to display the ban list", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "bossbar", "Allows the user to create and modify bossbars", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "clear", "Allows the user to clear items from player inventory", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "clone", "Allows the user to copy blocks from one place to another", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "data", "Allows the user to get, merge, modify, and remove block entity and entity NBT data", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "datapack", "Allows the user to control loaded data packs", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "debug", "Allows the user to start or stop a debugging session", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "deop", "Allows the user to revoke operator status from a player", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "difficulty", "Allows the user to set the difficulty level", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "enchant", "Allows the user to enchant a player item", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "execute", "Allows the user to execute another command", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "fill", "Allows the user to fill a region with a specific block", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "forceload", "Allows the user to force chunks to be constantly loaded or not", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "function", "Allows the user to run a function", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamerule", "Allows a user to set or query a game rule value", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "jfr", "Allows a user to use the vanilla Java FlightRecorder profiling system", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "locate", "Allows the user to locate the closest structure", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "loot", "Allows the user to drop items from an inventory slot onto the ground", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "op", "Allows the user to grant operator status to a player", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "pardon", "Allows the user to remove entries from the player ban list", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "pardon-ip", "Allows the user to remove entries from the ip address ban list", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "particle", "Allows the user to create particles", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "perf", "Allows the user to start/stop the vanilla performance metrics capture", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "playsound", "Allows the user to play a sound", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "recipe", "Allows the user to give or take recipes", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "reload", "Allows the user to reload loot tables, advancements, and functions from disk", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "item", "Allows the user to replace items in inventories", PermissionDefault.OP, commands); // Remove in 1.17 (replaced by /item) -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-all", "Allows the user to save the server to disk", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-off", "Allows the user disable automatic server saves", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "save-on", "Allows the user enable automatic server saves", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "schedule", "Allows the user to delay the execution of a function", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "scoreboard", "Allows the user manage scoreboard objectives and players", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setblock", "Allows the user to change a block to another block", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setidletimeout", "Allows the user to set the time before idle players are kicked", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "setworldspawn", "Allows the user to set the world spawn", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spawnpoint", "Allows the user to set the spawn point for a player", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spectate", "Allows the user to make one player in spectator mode spectate an entity", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "spreadplayers", "Allows the user to teleport entities to random locations", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "stopsound", "Allows the user to stop a sound", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "summon", "Allows the user to summon an entity", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "tag", "Allows the user to control entity tags", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "team", "Allows the user to control teams", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "teammsg", "Allows the user to specify the message to send to team", PermissionDefault.TRUE, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "tellraw", "Allows the user to display a JSON message to players", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "time", "Allows the user to change or query the world's game time", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "title", "Allows the user to manage screen titles", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "weather", "Allows the user to set the weather", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "whitelist", "Allows the user to manage the server whitelist", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "worldborder", "Allows the user to manage the world border", PermissionDefault.OP, commands); -+ DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "place", "Allows the user to place features and structures", PermissionDefault.OP, commands); -+ // Paper end - - DefaultPermissions.registerPermission("minecraft.admin.command_feedback", "Receive command broadcasts when sendCommandFeedback is true", PermissionDefault.OP, commands); - -diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..da2eb57ea64403657744ea00eff40243ef51df58 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -@@ -0,0 +1,86 @@ -+package io.papermc.paper.permissions; -+ -+import com.mojang.brigadier.tree.CommandNode; -+import com.mojang.brigadier.tree.RootCommandNode; -+import net.minecraft.commands.CommandBuildContext; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.core.RegistryAccess; -+import net.minecraft.server.Bootstrap; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.command.VanillaCommandWrapper; -+import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; -+import org.bukkit.permissions.Permission; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.AfterClass; -+import org.junit.BeforeClass; -+import org.junit.Test; -+ -+import java.io.PrintStream; -+import java.util.HashSet; -+import java.util.LinkedHashSet; -+import java.util.List; -+import java.util.Set; -+import java.util.TreeSet; -+ -+import static org.junit.Assert.assertTrue; -+ -+public class MinecraftCommandPermissionsTest extends AbstractTestingBase { -+ -+ private static PrintStream old; -+ @BeforeClass -+ public static void before() { -+ old = System.out; -+ System.setOut(Bootstrap.STDOUT); -+ } -+ -+ @Test -+ public void test() { -+ CraftDefaultPermissions.registerCorePermissions(); -+ Set perms = collectMinecraftCommandPerms(); -+ -+ Commands commands = new Commands(Commands.CommandSelection.DEDICATED, new CommandBuildContext(RegistryAccess.BUILTIN.get())); -+ RootCommandNode root = commands.getDispatcher().getRoot(); -+ Set missing = new LinkedHashSet<>(); -+ Set foundPerms = new HashSet<>(); -+ for (CommandNode child : root.getChildren()) { -+ final String vanillaPerm = VanillaCommandWrapper.getPermission(child); -+ if (!perms.contains(vanillaPerm)) { -+ missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); -+ } else { -+ foundPerms.add(vanillaPerm); -+ } -+ } -+ assertTrue("Commands missing permissions: \n" + String.join("\n", missing), missing.isEmpty()); -+ perms.removeAll(foundPerms); -+ assertTrue("Extra permissions not associated with a command: \n" + String.join("\n", perms), perms.isEmpty()); -+ } -+ -+ private static final List TO_SKIP = List.of( -+ "minecraft.command.selector" -+ ); -+ -+ private static Set collectMinecraftCommandPerms() { -+ Set perms = new TreeSet<>(); -+ for (Permission perm : Bukkit.getPluginManager().getPermissions()) { -+ if (perm.getName().startsWith("minecraft.command.")) { -+ if (TO_SKIP.contains(perm.getName())) { -+ continue; -+ } -+ if (perm.getName().endsWith(".xp")) { -+ perms.add("minecraft.command.experience"); // for the "experience" command, craftbukkit perm is "minecraft.command.xp" -+ continue; -+ } -+ perms.add(perm.getName()); -+ } -+ } -+ return perms; -+ } -+ -+ @AfterClass -+ public static void after() { -+ if (old != null) { -+ System.setOut(old); -+ } -+ } -+} -diff --git a/src/test/java/org/bukkit/support/DummyServer.java b/src/test/java/org/bukkit/support/DummyServer.java -index 946497353a64421592d2bae012c9a3cb874dd5b8..2ddceb709291d3bd713621ffa4020c02ec26bb21 100644 ---- a/src/test/java/org/bukkit/support/DummyServer.java -+++ b/src/test/java/org/bukkit/support/DummyServer.java -@@ -106,7 +106,21 @@ public final class DummyServer implements InvocationHandler { - } - } - ); -- Bukkit.setServer(Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(new DummyServer())); -+ // Paper start - modeled off of TestServer in the API tests module -+ methods.put( -+ Server.class.getMethod("getPluginManager"), -+ new MethodHandler() { -+ @Override -+ public Object handle(DummyServer server, Object[] args) { -+ return server.pluginManager; -+ } -+ } -+ ); -+ DummyServer server = new DummyServer(); -+ Server instance = Proxy.getProxyClass(Server.class.getClassLoader(), Server.class).asSubclass(Server.class).getConstructor(InvocationHandler.class).newInstance(server); -+ Bukkit.setServer(instance); -+ server.pluginManager = new org.bukkit.plugin.SimplePluginManager(instance, new org.bukkit.command.SimpleCommandMap(instance)); -+ // Paper end - } catch (Throwable t) { - throw new Error(t); - } -@@ -116,6 +130,7 @@ public final class DummyServer implements InvocationHandler { - - private DummyServer() {}; - -+ private org.bukkit.plugin.PluginManager pluginManager; // Paper - @Override - public Object invoke(Object proxy, Method method, Object[] args) { - MethodHandler handler = DummyServer.methods.get(method); diff --git a/patches/server/0535-Add-PlayerShearBlockEvent.patch b/patches/server/0535-Add-PlayerShearBlockEvent.patch deleted file mode 100644 index 4004036b95..0000000000 --- a/patches/server/0535-Add-PlayerShearBlockEvent.patch +++ /dev/null @@ -1,70 +0,0 @@ -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/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index 8dff119fa83e44d71b10bb3ea6e5f4e886a48c9c..1aaab26c59bb9255955aff34ea1d057b88152768 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -113,7 +113,7 @@ public class BeehiveBlock extends BaseEntityBlock { - } - - public static void dropHoneycomb(Level world, BlockPos pos) { -- popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); -+ popResource(world, pos, new ItemStack(Items.HONEYCOMB, 3)); // Paper - conflict on change, item needs to be set below - } - - @Override -@@ -126,8 +126,19 @@ public class BeehiveBlock extends BaseEntityBlock { - Item item = itemstack.getItem(); - - if (itemstack.is(Items.SHEARS)) { -+ // Paper start - Add PlayerShearBlockEvent -+ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), net.minecraft.server.MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (hand == InteractionHand.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 InteractionResult.PASS; -+ } -+ // Paper end - world.playSound(player, player.getX(), player.getY(), player.getZ(), SoundEvents.BEEHIVE_SHEAR, SoundSource.NEUTRAL, 1.0F, 1.0F); -- BeehiveBlock.dropHoneycomb(world, pos); -+ // Paper start - Add PlayerShearBlockEvent -+ for (org.bukkit.inventory.ItemStack itemDrop : event.getDrops()) { -+ popResource(world, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemDrop)); -+ } -+ // Paper end - itemstack.hurtAndBreak(1, player, (entityhuman1) -> { - entityhuman1.broadcastBreakEvent(hand); - }); -diff --git a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -index 0e8cbe7a465edc31b78b7e47a928435f9c2b6bd9..f998598a34315389dd74b82e4b9c8448f0aae253 100644 ---- a/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PumpkinBlock.java -@@ -27,13 +27,24 @@ public class PumpkinBlock extends StemGrownBlock { - ItemStack itemStack = player.getItemInHand(hand); - if (itemStack.is(Items.SHEARS)) { - if (!world.isClientSide) { -+ // Paper start - Add PlayerShearBlockEvent -+ io.papermc.paper.event.block.PlayerShearBlockEvent event = new io.papermc.paper.event.block.PlayerShearBlockEvent((org.bukkit.entity.Player) player.getBukkitEntity(), net.minecraft.server.MCUtil.toBukkitBlock(world, pos), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (hand == InteractionHand.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 InteractionResult.PASS; -+ } -+ // Paper end - Direction direction = hit.getDirection(); - Direction direction2 = direction.getAxis() == Direction.Axis.Y ? player.getDirection().getOpposite() : direction; - world.playSound((Player)null, pos, SoundEvents.PUMPKIN_CARVE, SoundSource.BLOCKS, 1.0F, 1.0F); - world.setBlock(pos, Blocks.CARVED_PUMPKIN.defaultBlockState().setValue(CarvedPumpkinBlock.FACING, direction2), 11); -- ItemEntity itemEntity = new ItemEntity(world, (double)pos.getX() + 0.5D + (double)direction2.getStepX() * 0.65D, (double)pos.getY() + 0.1D, (double)pos.getZ() + 0.5D + (double)direction2.getStepZ() * 0.65D, new ItemStack(Items.PUMPKIN_SEEDS, 4)); -+ // Paper start - Add PlayerShearBlockEvent -+ for (org.bukkit.inventory.ItemStack item : event.getDrops()) { -+ ItemEntity itemEntity = new ItemEntity(world, (double) pos.getX() + 0.5D + (double) direction2.getStepX() * 0.65D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D + (double) direction2.getStepZ() * 0.65D, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item)); -+ // Paper end - itemEntity.setDeltaMovement(0.05D * (double)direction2.getStepX() + world.random.nextDouble() * 0.02D, 0.05D, 0.05D * (double)direction2.getStepZ() + world.random.nextDouble() * 0.02D); - world.addFreshEntity(itemEntity); -+ } // Paper - Add PlayerShearBlockEvent - itemStack.hurtAndBreak(1, player, (playerx) -> { - playerx.broadcastBreakEvent(hand); - }); diff --git a/patches/server/0535-Fix-curing-zombie-villager-discount-exploit.patch b/patches/server/0535-Fix-curing-zombie-villager-discount-exploit.patch new file mode 100644 index 0000000000..c6cb27bebe --- /dev/null +++ b/patches/server/0535-Fix-curing-zombie-villager-discount-exploit.patch @@ -0,0 +1,29 @@ +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/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 1d2b7950e3c498945a2ff85fda0e3bb30acd22cb..10b45ec24a5a0867106d1694312385ad1e267f43 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -976,6 +976,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + @Override + public void onReputationEventFrom(ReputationEventType interaction, Entity entity) { + if (interaction == ReputationEventType.ZOMBIE_VILLAGER_CURED) { ++ // Paper start - fix MC-181190 ++ if (level.paperConfig().fixes.fixCuringZombieVillagerDiscountExploit) { ++ final GossipContainer.EntityGossips playerReputation = this.getGossips().getReputations().get(entity.getUUID()); ++ if (playerReputation != null) { ++ playerReputation.remove(GossipType.MAJOR_POSITIVE); ++ playerReputation.remove(GossipType.MINOR_POSITIVE); ++ } ++ } ++ // Paper end + this.gossips.add(entity.getUUID(), GossipType.MAJOR_POSITIVE, 20); + this.gossips.add(entity.getUUID(), GossipType.MINOR_POSITIVE, 25); + } else if (interaction == ReputationEventType.TRADE) { diff --git a/patches/server/0536-Fix-curing-zombie-villager-discount-exploit.patch b/patches/server/0536-Fix-curing-zombie-villager-discount-exploit.patch deleted file mode 100644 index c6cb27bebe..0000000000 --- a/patches/server/0536-Fix-curing-zombie-villager-discount-exploit.patch +++ /dev/null @@ -1,29 +0,0 @@ -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/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 1d2b7950e3c498945a2ff85fda0e3bb30acd22cb..10b45ec24a5a0867106d1694312385ad1e267f43 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -976,6 +976,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - @Override - public void onReputationEventFrom(ReputationEventType interaction, Entity entity) { - if (interaction == ReputationEventType.ZOMBIE_VILLAGER_CURED) { -+ // Paper start - fix MC-181190 -+ if (level.paperConfig().fixes.fixCuringZombieVillagerDiscountExploit) { -+ final GossipContainer.EntityGossips playerReputation = this.getGossips().getReputations().get(entity.getUUID()); -+ if (playerReputation != null) { -+ playerReputation.remove(GossipType.MAJOR_POSITIVE); -+ playerReputation.remove(GossipType.MINOR_POSITIVE); -+ } -+ } -+ // Paper end - this.gossips.add(entity.getUUID(), GossipType.MAJOR_POSITIVE, 20); - this.gossips.add(entity.getUUID(), GossipType.MINOR_POSITIVE, 25); - } else if (interaction == ReputationEventType.TRADE) { diff --git a/patches/server/0536-Limit-recipe-packets.patch b/patches/server/0536-Limit-recipe-packets.patch new file mode 100644 index 0000000000..f40301fce3 --- /dev/null +++ b/patches/server/0536-Limit-recipe-packets.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 12 Dec 2020 23:45:28 +0000 +Subject: [PATCH] Limit recipe packets + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 8e12c4d4b54c2f0a265dc627d7981282fc6fda6e..32227020d0cba1aba3ec0fcda5f4b4cd4b1ce394 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -263,6 +263,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // CraftBukkit start - multithreaded fields + private final AtomicInteger chatSpamTickCount = new AtomicInteger(); + private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits ++ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit + // CraftBukkit end + private int dropSpamTickCount; + private double firstGoodX; +@@ -425,6 +426,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // CraftBukkit start + for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(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.chatSpamTickCount > 0) { + --this.chatSpamTickCount; +@@ -3247,6 +3249,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + @Override + public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ // Paper start ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { ++ server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]))); // Paper ++ return; ++ } ++ } ++ // Paper end + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + this.player.resetLastActionTime(); + if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/patches/server/0537-Fix-CraftSound-backwards-compatibility.patch b/patches/server/0537-Fix-CraftSound-backwards-compatibility.patch new file mode 100644 index 0000000000..60e8ef1e6b --- /dev/null +++ b/patches/server/0537-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 266563e72b563fd9db85f17bca710bbe45e8a22d..b2667c5f0794d521766203fea3299f12e21f5c76 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftSound.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftSound.java +@@ -26,4 +26,10 @@ public class CraftSound { + public static Sound getBukkit(SoundEvent soundEffect) { + return Registry.SOUNDS.get(CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.SOUND_EVENT.getKey(soundEffect))); + } ++ ++ // Paper start ++ public static String getSound(Sound sound) { ++ return sound.getKey().getKey(); ++ } ++ // Paper end + } diff --git a/patches/server/0537-Limit-recipe-packets.patch b/patches/server/0537-Limit-recipe-packets.patch deleted file mode 100644 index c6f3933831..0000000000 --- a/patches/server/0537-Limit-recipe-packets.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 12 Dec 2020 23:45:28 +0000 -Subject: [PATCH] Limit recipe packets - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index bae8cb6dea250a389c02efe8667c5a898ff49909..bdab60032896ad24add7d2efb49db07a1793670a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -263,6 +263,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // CraftBukkit start - multithreaded fields - private final AtomicInteger chatSpamTickCount = new AtomicInteger(); - private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits -+ private final java.util.concurrent.atomic.AtomicInteger recipeSpamPackets = new java.util.concurrent.atomic.AtomicInteger(); // Paper - auto recipe limit - // CraftBukkit end - private int dropSpamTickCount; - private double firstGoodX; -@@ -425,6 +426,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // CraftBukkit start - for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(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.chatSpamTickCount > 0) { - --this.chatSpamTickCount; -@@ -3243,6 +3245,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - @Override - public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { -+ // Paper start -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -+ server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]))); // Paper -+ return; -+ } -+ } -+ // Paper end - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - this.player.resetLastActionTime(); - if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { diff --git a/patches/server/0538-Fix-CraftSound-backwards-compatibility.patch b/patches/server/0538-Fix-CraftSound-backwards-compatibility.patch deleted file mode 100644 index 60e8ef1e6b..0000000000 --- a/patches/server/0538-Fix-CraftSound-backwards-compatibility.patch +++ /dev/null @@ -1,21 +0,0 @@ -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 266563e72b563fd9db85f17bca710bbe45e8a22d..b2667c5f0794d521766203fea3299f12e21f5c76 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftSound.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftSound.java -@@ -26,4 +26,10 @@ public class CraftSound { - public static Sound getBukkit(SoundEvent soundEffect) { - return Registry.SOUNDS.get(CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.SOUND_EVENT.getKey(soundEffect))); - } -+ -+ // Paper start -+ public static String getSound(Sound sound) { -+ return sound.getKey().getKey(); -+ } -+ // Paper end - } diff --git a/patches/server/0538-Player-Chunk-Load-Unload-Events.patch b/patches/server/0538-Player-Chunk-Load-Unload-Events.patch new file mode 100644 index 0000000000..04505672a2 --- /dev/null +++ b/patches/server/0538-Player-Chunk-Load-Unload-Events.patch @@ -0,0 +1,32 @@ +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/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 9b4436bdb697d8350eac57282f8fad8140029d8f..a802529b6f1adfd358295811c4e329e6fe82009b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2134,11 +2134,21 @@ public class ServerPlayer extends Player { + + public void trackChunk(ChunkPos chunkPos, Packet chunkDataPacket) { + this.connection.send(chunkDataPacket); ++ // Paper start ++ if(io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + public void untrackChunk(ChunkPos chunkPos) { + if (this.isAlive()) { + this.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos.x, chunkPos.z)); ++ // Paper start ++ if(io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0){ ++ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), this.getBukkitEntity()).callEvent(); ++ } ++ // Paper end + } + + } diff --git a/patches/server/0539-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server/0539-Optimize-Dynamic-get-Missing-Keys.patch new file mode 100644 index 0000000000..5a4efe035c --- /dev/null +++ b/patches/server/0539-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/0539-Player-Chunk-Load-Unload-Events.patch b/patches/server/0539-Player-Chunk-Load-Unload-Events.patch deleted file mode 100644 index 04505672a2..0000000000 --- a/patches/server/0539-Player-Chunk-Load-Unload-Events.patch +++ /dev/null @@ -1,32 +0,0 @@ -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/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 9b4436bdb697d8350eac57282f8fad8140029d8f..a802529b6f1adfd358295811c4e329e6fe82009b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2134,11 +2134,21 @@ public class ServerPlayer extends Player { - - public void trackChunk(ChunkPos chunkPos, Packet chunkDataPacket) { - this.connection.send(chunkDataPacket); -+ // Paper start -+ if(io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0){ -+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), this.getBukkitEntity()).callEvent(); -+ } -+ // Paper end - } - - public void untrackChunk(ChunkPos chunkPos) { - if (this.isAlive()) { - this.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos.x, chunkPos.z)); -+ // Paper start -+ if(io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0){ -+ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(this.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), this.getBukkitEntity()).callEvent(); -+ } -+ // Paper end - } - - } diff --git a/patches/server/0540-Expose-LivingEntity-hurt-direction.patch b/patches/server/0540-Expose-LivingEntity-hurt-direction.patch new file mode 100644 index 0000000000..ff8defb04b --- /dev/null +++ b/patches/server/0540-Expose-LivingEntity-hurt-direction.patch @@ -0,0 +1,26 @@ +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/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 4e7cb29909fd82d26029e4c78580ead500db5c2a..d16ea002ca558502be2f04528f0346d9aacff30f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -896,5 +896,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { + getHandle().take(((CraftItem) item).getHandle(), quantity); + } ++ ++ @Override ++ public float getHurtDirection() { ++ return getHandle().hurtDir; ++ } ++ ++ @Override ++ public void setHurtDirection(float hurtDirection) { ++ getHandle().hurtDir = hurtDirection; ++ } + // Paper end + } diff --git a/patches/server/0540-Optimize-Dynamic-get-Missing-Keys.patch b/patches/server/0540-Optimize-Dynamic-get-Missing-Keys.patch deleted file mode 100644 index 5a4efe035c..0000000000 --- a/patches/server/0540-Optimize-Dynamic-get-Missing-Keys.patch +++ /dev/null @@ -1,34 +0,0 @@ -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/0541-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0541-Add-OBSTRUCTED-reason-to-BedEnterResult.patch new file mode 100644 index 0000000000..5c4a714385 --- /dev/null +++ b/patches/server/0541-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 c2eefe215f47e36cc2b8476750ae00ec88f826a6..7f8dbc94ad04ff58e0ee7591b42e268ee4b75576 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -263,6 +263,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/0541-Expose-LivingEntity-hurt-direction.patch b/patches/server/0541-Expose-LivingEntity-hurt-direction.patch deleted file mode 100644 index 4eb56db7d2..0000000000 --- a/patches/server/0541-Expose-LivingEntity-hurt-direction.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Sun, 13 Dec 2020 05:32:05 +0200 -Subject: [PATCH] Expose LivingEntity hurt direction - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 6e2e217d965ea6b8601e5ba0f8a44b817ee6654d..a0b46e05dc9c384b3dd70de00d89911c492ef493 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -855,5 +855,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void playPickupItemAnimation(org.bukkit.entity.Item item, int quantity) { - getHandle().take(((CraftItem) item).getHandle(), quantity); - } -+ -+ @Override -+ public float getHurtDirection() { -+ return getHandle().hurtDir; -+ } -+ -+ @Override -+ public void setHurtDirection(float hurtDirection) { -+ getHandle().hurtDir = hurtDirection; -+ } - // Paper end - } diff --git a/patches/server/0542-Add-OBSTRUCTED-reason-to-BedEnterResult.patch b/patches/server/0542-Add-OBSTRUCTED-reason-to-BedEnterResult.patch deleted file mode 100644 index 5c4a714385..0000000000 --- a/patches/server/0542-Add-OBSTRUCTED-reason-to-BedEnterResult.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 24 Dec 2020 12:43:39 -0800 -Subject: [PATCH] Add OBSTRUCTED reason to BedEnterResult - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index c2eefe215f47e36cc2b8476750ae00ec88f826a6..7f8dbc94ad04ff58e0ee7591b42e268ee4b75576 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -263,6 +263,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/0542-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch b/patches/server/0542-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch new file mode 100644 index 0000000000..a66e8fc14d --- /dev/null +++ b/patches/server/0542-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/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 6717e3d116b7bff8ab4d7b45f8c4ec00939c9c73..0c585354ba459ceb6badbf60dcf7b068bfa108b6 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -275,7 +275,11 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + Bukkit.getPluginManager().callEvent(event); + } + if (!event.isCancelled()) { +- recipeList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); ++ // Paper start ++ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); ++ if (craftMerchantRecipe.getIngredients().isEmpty()) return; ++ recipeList.add(craftMerchantRecipe.toMinecraft()); ++ // Paper end + } + // CraftBukkit end + } diff --git a/patches/server/0543-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/0543-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch new file mode 100644 index 0000000000..339a34391b --- /dev/null +++ b/patches/server/0543-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch @@ -0,0 +1,251 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Jul 2020 16:12:10 -0700 +Subject: [PATCH] Add PlayerTradeEvent and PlayerPurchaseEvent + +Co-authored-by: Alexander + +diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +index 0c585354ba459ceb6badbf60dcf7b068bfa108b6..5eab7d50734551d96128dfebee126a1da4c51375 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +@@ -138,11 +138,24 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa + @Override + public void overrideXp(int experience) {} + ++ // Paper start ++ @Override ++ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ if (event == null || event.willIncreaseTradeUses()) { ++ recipe.increaseUses(); ++ } ++ if (event == null || event.isRewardingExp()) { ++ this.rewardTradeXp(recipe); ++ } ++ this.notifyTrade(recipe); ++ } ++ // Paper end ++ + @Override + public void notifyTrade(MerchantOffer offer) { +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - handled in processTrade + this.ambientSoundTime = -this.getAmbientSoundInterval(); +- this.rewardTradeXp(offer); ++ // this.rewardTradeXp(offer); // Paper - handled in processTrade + if (this.tradingPlayer instanceof ServerPlayer) { + CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); + } +diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 66261330157cef50dfabb7f92e9ece6dcd280951..823ead0fd1942db0219968cd383439b324b16f6e 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -751,6 +751,14 @@ public abstract class AbstractContainerMenu { + public abstract boolean stillValid(Player player); + + protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { ++ // Paper start ++ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); ++ } ++ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { ++ if (isCheck) { ++ stack = stack.copy(); ++ } ++ // Paper end + boolean flag1 = false; + int k = startIndex; + +@@ -773,18 +781,27 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end + if (!itemstack1.isEmpty() && ItemStack.isSameItemSameTags(stack, itemstack1)) { + int l = itemstack1.getCount() + stack.getCount(); + + if (l <= stack.getMaxStackSize()) { + stack.setCount(0); + itemstack1.setCount(l); ++ if (!isCheck) { // Paper - dont update if only a check + slot.setChanged(); ++ } // Paper + flag1 = true; + } else if (itemstack1.getCount() < stack.getMaxStackSize()) { + stack.shrink(stack.getMaxStackSize() - itemstack1.getCount()); + itemstack1.setCount(stack.getMaxStackSize()); ++ if (!isCheck) { // Paper - dont update if only a check + slot.setChanged(); ++ } // Paper + flag1 = true; + } + } +@@ -815,14 +832,33 @@ public abstract class AbstractContainerMenu { + + slot = (Slot) this.slots.get(k); + itemstack1 = slot.getItem(); ++ // Paper start - clone if only a check ++ if (isCheck) { ++ itemstack1 = itemstack1.copy(); ++ } ++ // Paper end + if (itemstack1.isEmpty() && slot.mayPlace(stack)) { + if (stack.getCount() > slot.getMaxStackSize()) { ++ // Paper start - dont set slot if only check ++ if (isCheck) { ++ stack.shrink(slot.getMaxStackSize()); ++ } else { ++ // Paper end + slot.set(stack.split(slot.getMaxStackSize())); ++ } // Paper + } else { ++ // Paper start - dont set slot if only check ++ if (isCheck) { ++ stack.shrink(stack.getCount()); ++ } else { ++ // Paper end + slot.set(stack.split(stack.getCount())); ++ } // Paper + } + ++ if (!isCheck) { // Paper - dont update if only check + slot.setChanged(); ++ } // Paper + flag1 = true; + break; + } +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +index 0d3002c5b13cbac0e021e8cdcf7d0685b256268a..3e3dfbe30fc5357e144aa176bc5df1b8a7ae6d64 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java +@@ -134,12 +134,12 @@ public class MerchantMenu extends AbstractContainerMenu { + + itemstack = itemstack1.copy(); + if (index == 2) { +- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { ++ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper + return ItemStack.EMPTY; + } + +- slot.onQuickCraft(itemstack1, itemstack); +- this.playTradeSound(); ++ // slot.onQuickCraft(itemstack1, itemstack); // Paper - moved to after the non-check moveItemStackTo call ++ // this.playTradeSound(); + } else if (index != 0 && index != 1) { + if (index >= 3 && index < 30) { + if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { +@@ -152,6 +152,7 @@ public class MerchantMenu extends AbstractContainerMenu { + return ItemStack.EMPTY; + } + ++ if (index != 2) { // Paper - moved down for slot 2 + if (itemstack1.isEmpty()) { + slot.set(ItemStack.EMPTY); + } else { +@@ -163,6 +164,21 @@ public class MerchantMenu extends AbstractContainerMenu { + } + + slot.onTake(player, itemstack1); ++ } // Paper start - handle slot 2 ++ if (index == 2) { // is merchant result slot ++ slot.onTake(player, itemstack1); ++ if (itemstack1.isEmpty()) { ++ slot.set(ItemStack.EMPTY); ++ return ItemStack.EMPTY; ++ } ++ ++ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above ++ ++ slot.onQuickCraft(itemstack1, itemstack); ++ this.playTradeSound(); ++ slot.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty ++ } ++ // Paper end + } + + return itemstack; +diff --git a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +index 74b28315197b81f80334ae6023113904e4fac4c3..7acf5ab9339e0134819aab5f270e99e927cc7a62 100644 +--- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java ++++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java +@@ -47,13 +47,32 @@ public class MerchantResultSlot extends Slot { + + @Override + public void onTake(Player player, ItemStack stack) { +- this.checkTakeAchievements(stack); ++ // this.checkTakeAchievements(stack); // Paper - move to after event is called and not cancelled + MerchantOffer merchantOffer = this.slots.getActiveOffer(); ++ // Paper start ++ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; ++ if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { ++ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { ++ event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); ++ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { ++ event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true); ++ } ++ if (event != null) { ++ if (!event.callEvent()) { ++ stack.setCount(0); ++ event.getPlayer().updateInventory(); ++ return; ++ } ++ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); ++ } ++ } ++ this.checkTakeAchievements(stack); ++ // Paper end + if (merchantOffer != null) { + ItemStack itemStack = this.slots.getItem(0); + ItemStack itemStack2 = this.slots.getItem(1); + if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { +- this.merchant.notifyTrade(merchantOffer); ++ this.merchant.processTrade(merchantOffer, event); // Paper + player.awardStat(Stats.TRADED_WITH_VILLAGER); + this.slots.setItem(0, itemStack); + this.slots.setItem(1, itemStack2); +diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java +index d41f44ed2e497ba3373d170c08488b49e88334c4..d3a99ba6f3085ad12b67ddc94cc4ab393ec7ecbe 100644 +--- a/src/main/java/net/minecraft/world/item/trading/Merchant.java ++++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java +@@ -20,6 +20,7 @@ public interface Merchant { + + void overrideOffers(MerchantOffers offers); + ++ default void processTrade(MerchantOffer merchantRecipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper + void notifyTrade(MerchantOffer offer); + + void notifyTradeUpdated(ItemStack stack); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +index 257776a12ca26c1e75be22a67c94b0aa012fd687..5074e8b2259b3fb969bd0ff99c296b7537920273 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java +@@ -74,10 +74,25 @@ public class CraftMerchantCustom extends CraftMerchant { + return this.trades; + } + ++ // Paper start ++ @Override ++ public void processTrade(MerchantOffer merchantRecipe, @javax.annotation.Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent ++ /** Based on {@link net.minecraft.world.entity.npc.AbstractVillager#processTrade(MerchantOffer, io.papermc.paper.event.player.PlayerPurchaseEvent)} */ ++ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { ++ if (event == null || event.willIncreaseTradeUses()) { ++ merchantRecipe.increaseUses(); ++ } ++ if (event == null || event.isRewardingExp()) { ++ this.tradingPlayer.level.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(tradingPlayer.level, tradingPlayer.getX(), tradingPlayer.getY(), tradingPlayer.getZ(), merchantRecipe.getXp(), org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.tradingPlayer, null)); ++ } ++ } ++ this.notifyTrade(merchantRecipe); ++ } ++ // Paper end + @Override + public void notifyTrade(MerchantOffer offer) { + // increase recipe's uses +- offer.increaseUses(); ++ // offer.increaseUses(); // Paper - handled above in processTrade + } + + @Override diff --git a/patches/server/0543-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch b/patches/server/0543-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch deleted file mode 100644 index a66e8fc14d..0000000000 --- a/patches/server/0543-Do-not-crash-from-invalid-ingredient-lists-in-Villag.patch +++ /dev/null @@ -1,24 +0,0 @@ -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/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 6717e3d116b7bff8ab4d7b45f8c4ec00939c9c73..0c585354ba459ceb6badbf60dcf7b068bfa108b6 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -275,7 +275,11 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - Bukkit.getPluginManager().callEvent(event); - } - if (!event.isCancelled()) { -- recipeList.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft()); -+ // Paper start -+ final CraftMerchantRecipe craftMerchantRecipe = CraftMerchantRecipe.fromBukkit(event.getRecipe()); -+ if (craftMerchantRecipe.getIngredients().isEmpty()) return; -+ recipeList.add(craftMerchantRecipe.toMinecraft()); -+ // Paper end - } - // CraftBukkit end - } diff --git a/patches/server/0544-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch b/patches/server/0544-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch deleted file mode 100644 index 339a34391b..0000000000 --- a/patches/server/0544-Add-PlayerTradeEvent-and-PlayerPurchaseEvent.patch +++ /dev/null @@ -1,251 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Jul 2020 16:12:10 -0700 -Subject: [PATCH] Add PlayerTradeEvent and PlayerPurchaseEvent - -Co-authored-by: Alexander - -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 0c585354ba459ceb6badbf60dcf7b068bfa108b6..5eab7d50734551d96128dfebee126a1da4c51375 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -138,11 +138,24 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa - @Override - public void overrideXp(int experience) {} - -+ // Paper start -+ @Override -+ public void processTrade(MerchantOffer recipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent -+ if (event == null || event.willIncreaseTradeUses()) { -+ recipe.increaseUses(); -+ } -+ if (event == null || event.isRewardingExp()) { -+ this.rewardTradeXp(recipe); -+ } -+ this.notifyTrade(recipe); -+ } -+ // Paper end -+ - @Override - public void notifyTrade(MerchantOffer offer) { -- offer.increaseUses(); -+ // offer.increaseUses(); // Paper - handled in processTrade - this.ambientSoundTime = -this.getAmbientSoundInterval(); -- this.rewardTradeXp(offer); -+ // this.rewardTradeXp(offer); // Paper - handled in processTrade - if (this.tradingPlayer instanceof ServerPlayer) { - CriteriaTriggers.TRADE.trigger((ServerPlayer) this.tradingPlayer, this, offer.getResult()); - } -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 66261330157cef50dfabb7f92e9ece6dcd280951..823ead0fd1942db0219968cd383439b324b16f6e 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -751,6 +751,14 @@ public abstract class AbstractContainerMenu { - public abstract boolean stillValid(Player player); - - protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast) { -+ // Paper start -+ return this.moveItemStackTo(stack, startIndex, endIndex, fromLast, false); -+ } -+ protected boolean moveItemStackTo(ItemStack stack, int startIndex, int endIndex, boolean fromLast, boolean isCheck) { -+ if (isCheck) { -+ stack = stack.copy(); -+ } -+ // Paper end - boolean flag1 = false; - int k = startIndex; - -@@ -773,18 +781,27 @@ public abstract class AbstractContainerMenu { - - slot = (Slot) this.slots.get(k); - itemstack1 = slot.getItem(); -+ // Paper start - clone if only a check -+ if (isCheck) { -+ itemstack1 = itemstack1.copy(); -+ } -+ // Paper end - if (!itemstack1.isEmpty() && ItemStack.isSameItemSameTags(stack, itemstack1)) { - int l = itemstack1.getCount() + stack.getCount(); - - if (l <= stack.getMaxStackSize()) { - stack.setCount(0); - itemstack1.setCount(l); -+ if (!isCheck) { // Paper - dont update if only a check - slot.setChanged(); -+ } // Paper - flag1 = true; - } else if (itemstack1.getCount() < stack.getMaxStackSize()) { - stack.shrink(stack.getMaxStackSize() - itemstack1.getCount()); - itemstack1.setCount(stack.getMaxStackSize()); -+ if (!isCheck) { // Paper - dont update if only a check - slot.setChanged(); -+ } // Paper - flag1 = true; - } - } -@@ -815,14 +832,33 @@ public abstract class AbstractContainerMenu { - - slot = (Slot) this.slots.get(k); - itemstack1 = slot.getItem(); -+ // Paper start - clone if only a check -+ if (isCheck) { -+ itemstack1 = itemstack1.copy(); -+ } -+ // Paper end - if (itemstack1.isEmpty() && slot.mayPlace(stack)) { - if (stack.getCount() > slot.getMaxStackSize()) { -+ // Paper start - dont set slot if only check -+ if (isCheck) { -+ stack.shrink(slot.getMaxStackSize()); -+ } else { -+ // Paper end - slot.set(stack.split(slot.getMaxStackSize())); -+ } // Paper - } else { -+ // Paper start - dont set slot if only check -+ if (isCheck) { -+ stack.shrink(stack.getCount()); -+ } else { -+ // Paper end - slot.set(stack.split(stack.getCount())); -+ } // Paper - } - -+ if (!isCheck) { // Paper - dont update if only check - slot.setChanged(); -+ } // Paper - flag1 = true; - break; - } -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -index 0d3002c5b13cbac0e021e8cdcf7d0685b256268a..3e3dfbe30fc5357e144aa176bc5df1b8a7ae6d64 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantMenu.java -@@ -134,12 +134,12 @@ public class MerchantMenu extends AbstractContainerMenu { - - itemstack = itemstack1.copy(); - if (index == 2) { -- if (!this.moveItemStackTo(itemstack1, 3, 39, true)) { -+ if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - return ItemStack.EMPTY; - } - -- slot.onQuickCraft(itemstack1, itemstack); -- this.playTradeSound(); -+ // slot.onQuickCraft(itemstack1, itemstack); // Paper - moved to after the non-check moveItemStackTo call -+ // this.playTradeSound(); - } else if (index != 0 && index != 1) { - if (index >= 3 && index < 30) { - if (!this.moveItemStackTo(itemstack1, 30, 39, false)) { -@@ -152,6 +152,7 @@ public class MerchantMenu extends AbstractContainerMenu { - return ItemStack.EMPTY; - } - -+ if (index != 2) { // Paper - moved down for slot 2 - if (itemstack1.isEmpty()) { - slot.set(ItemStack.EMPTY); - } else { -@@ -163,6 +164,21 @@ public class MerchantMenu extends AbstractContainerMenu { - } - - slot.onTake(player, itemstack1); -+ } // Paper start - handle slot 2 -+ if (index == 2) { // is merchant result slot -+ slot.onTake(player, itemstack1); -+ if (itemstack1.isEmpty()) { -+ slot.set(ItemStack.EMPTY); -+ return ItemStack.EMPTY; -+ } -+ -+ this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above -+ -+ slot.onQuickCraft(itemstack1, itemstack); -+ this.playTradeSound(); -+ slot.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty -+ } -+ // Paper end - } - - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -index 74b28315197b81f80334ae6023113904e4fac4c3..7acf5ab9339e0134819aab5f270e99e927cc7a62 100644 ---- a/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -+++ b/src/main/java/net/minecraft/world/inventory/MerchantResultSlot.java -@@ -47,13 +47,32 @@ public class MerchantResultSlot extends Slot { - - @Override - public void onTake(Player player, ItemStack stack) { -- this.checkTakeAchievements(stack); -+ // this.checkTakeAchievements(stack); // Paper - move to after event is called and not cancelled - MerchantOffer merchantOffer = this.slots.getActiveOffer(); -+ // Paper start -+ io.papermc.paper.event.player.PlayerPurchaseEvent event = null; -+ if (merchantOffer != null && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ if (this.merchant instanceof net.minecraft.world.entity.npc.AbstractVillager abstractVillager) { -+ event = new io.papermc.paper.event.player.PlayerTradeEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.AbstractVillager) abstractVillager.getBukkitEntity(), merchantOffer.asBukkit(), true, true); -+ } else if (this.merchant instanceof org.bukkit.craftbukkit.inventory.CraftMerchantCustom.MinecraftMerchant) { -+ event = new io.papermc.paper.event.player.PlayerPurchaseEvent(serverPlayer.getBukkitEntity(), merchantOffer.asBukkit(), false, true); -+ } -+ if (event != null) { -+ if (!event.callEvent()) { -+ stack.setCount(0); -+ event.getPlayer().updateInventory(); -+ return; -+ } -+ merchantOffer = org.bukkit.craftbukkit.inventory.CraftMerchantRecipe.fromBukkit(event.getTrade()).toMinecraft(); -+ } -+ } -+ this.checkTakeAchievements(stack); -+ // Paper end - if (merchantOffer != null) { - ItemStack itemStack = this.slots.getItem(0); - ItemStack itemStack2 = this.slots.getItem(1); - if (merchantOffer.take(itemStack, itemStack2) || merchantOffer.take(itemStack2, itemStack)) { -- this.merchant.notifyTrade(merchantOffer); -+ this.merchant.processTrade(merchantOffer, event); // Paper - player.awardStat(Stats.TRADED_WITH_VILLAGER); - this.slots.setItem(0, itemStack); - this.slots.setItem(1, itemStack2); -diff --git a/src/main/java/net/minecraft/world/item/trading/Merchant.java b/src/main/java/net/minecraft/world/item/trading/Merchant.java -index d41f44ed2e497ba3373d170c08488b49e88334c4..d3a99ba6f3085ad12b67ddc94cc4ab393ec7ecbe 100644 ---- a/src/main/java/net/minecraft/world/item/trading/Merchant.java -+++ b/src/main/java/net/minecraft/world/item/trading/Merchant.java -@@ -20,6 +20,7 @@ public interface Merchant { - - void overrideOffers(MerchantOffers offers); - -+ default void processTrade(MerchantOffer merchantRecipe, @Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { this.notifyTrade(merchantRecipe); } // Paper - void notifyTrade(MerchantOffer offer); - - void notifyTradeUpdated(ItemStack stack); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -index 257776a12ca26c1e75be22a67c94b0aa012fd687..5074e8b2259b3fb969bd0ff99c296b7537920273 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchantCustom.java -@@ -74,10 +74,25 @@ public class CraftMerchantCustom extends CraftMerchant { - return this.trades; - } - -+ // Paper start -+ @Override -+ public void processTrade(MerchantOffer merchantRecipe, @javax.annotation.Nullable io.papermc.paper.event.player.PlayerPurchaseEvent event) { // The MerchantRecipe passed in here is the one set by the PlayerPurchaseEvent -+ /** Based on {@link net.minecraft.world.entity.npc.AbstractVillager#processTrade(MerchantOffer, io.papermc.paper.event.player.PlayerPurchaseEvent)} */ -+ if (getTradingPlayer() instanceof net.minecraft.server.level.ServerPlayer) { -+ if (event == null || event.willIncreaseTradeUses()) { -+ merchantRecipe.increaseUses(); -+ } -+ if (event == null || event.isRewardingExp()) { -+ this.tradingPlayer.level.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(tradingPlayer.level, tradingPlayer.getX(), tradingPlayer.getY(), tradingPlayer.getZ(), merchantRecipe.getXp(), org.bukkit.entity.ExperienceOrb.SpawnReason.VILLAGER_TRADE, this.tradingPlayer, null)); -+ } -+ } -+ this.notifyTrade(merchantRecipe); -+ } -+ // Paper end - @Override - public void notifyTrade(MerchantOffer offer) { - // increase recipe's uses -- offer.increaseUses(); -+ // offer.increaseUses(); // Paper - handled above in processTrade - } - - @Override diff --git a/patches/server/0544-Implement-TargetHitEvent.patch b/patches/server/0544-Implement-TargetHitEvent.patch new file mode 100644 index 0000000000..78722933d9 --- /dev/null +++ b/patches/server/0544-Implement-TargetHitEvent.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 25 Nov 2020 23:20:44 -0800 +Subject: [PATCH] Implement TargetHitEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/TargetBlock.java b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +index fdaba168dd1ed25ed8d5b4cdd89d4ad801b10388..69eaf1341d282c4783dab84533ea2c053deed529 100644 +--- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java +@@ -35,6 +35,10 @@ public class TargetBlock extends Block { + @Override + public void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { + int i = updateRedstoneOutput(world, state, hit, projectile); ++ // Paper start ++ } ++ private static void awardTargetHitCriteria(Projectile projectile, BlockHitResult hit, int i) { ++ // Paper end + Entity entity = projectile.getOwner(); + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.awardStat(Stats.TARGET_HIT); +@@ -46,6 +50,20 @@ public class TargetBlock extends Block { + private static int updateRedstoneOutput(LevelAccessor world, BlockState state, BlockHitResult hitResult, Entity entity) { + int i = getRedstoneStrength(hitResult, hitResult.getLocation()); + int j = entity instanceof AbstractArrow ? 20 : 8; ++ // Paper start ++ if (entity instanceof Projectile) { ++ final Projectile projectile = (Projectile) entity; ++ final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, hitResult.getBlockPos()); ++ final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(hitResult.getDirection()); ++ final io.papermc.paper.event.block.TargetHitEvent targetHitEvent = new io.papermc.paper.event.block.TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i); ++ if (targetHitEvent.callEvent()) { ++ i = targetHitEvent.getSignalStrength(); ++ awardTargetHitCriteria(projectile, hitResult, i); ++ } else { ++ return i; ++ } ++ } ++ // Paper end + if (!world.getBlockTicks().hasScheduledTick(hitResult.getBlockPos(), state.getBlock())) { + setOutputPower(world, state, i, hitResult.getBlockPos(), j); + } diff --git a/patches/server/0545-Implement-TargetHitEvent.patch b/patches/server/0545-Implement-TargetHitEvent.patch deleted file mode 100644 index 78722933d9..0000000000 --- a/patches/server/0545-Implement-TargetHitEvent.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 25 Nov 2020 23:20:44 -0800 -Subject: [PATCH] Implement TargetHitEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/TargetBlock.java b/src/main/java/net/minecraft/world/level/block/TargetBlock.java -index fdaba168dd1ed25ed8d5b4cdd89d4ad801b10388..69eaf1341d282c4783dab84533ea2c053deed529 100644 ---- a/src/main/java/net/minecraft/world/level/block/TargetBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TargetBlock.java -@@ -35,6 +35,10 @@ public class TargetBlock extends Block { - @Override - public void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) { - int i = updateRedstoneOutput(world, state, hit, projectile); -+ // Paper start -+ } -+ private static void awardTargetHitCriteria(Projectile projectile, BlockHitResult hit, int i) { -+ // Paper end - Entity entity = projectile.getOwner(); - if (entity instanceof ServerPlayer serverPlayer) { - serverPlayer.awardStat(Stats.TARGET_HIT); -@@ -46,6 +50,20 @@ public class TargetBlock extends Block { - private static int updateRedstoneOutput(LevelAccessor world, BlockState state, BlockHitResult hitResult, Entity entity) { - int i = getRedstoneStrength(hitResult, hitResult.getLocation()); - int j = entity instanceof AbstractArrow ? 20 : 8; -+ // Paper start -+ if (entity instanceof Projectile) { -+ final Projectile projectile = (Projectile) entity; -+ final org.bukkit.craftbukkit.block.CraftBlock craftBlock = org.bukkit.craftbukkit.block.CraftBlock.at(world, hitResult.getBlockPos()); -+ final org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(hitResult.getDirection()); -+ final io.papermc.paper.event.block.TargetHitEvent targetHitEvent = new io.papermc.paper.event.block.TargetHitEvent((org.bukkit.entity.Projectile) projectile.getBukkitEntity(), craftBlock, blockFace, i); -+ if (targetHitEvent.callEvent()) { -+ i = targetHitEvent.getSignalStrength(); -+ awardTargetHitCriteria(projectile, hitResult, i); -+ } else { -+ return i; -+ } -+ } -+ // Paper end - if (!world.getBlockTicks().hasScheduledTick(hitResult.getBlockPos(), state.getBlock())) { - setOutputPower(world, state, i, hitResult.getBlockPos(), j); - } diff --git a/patches/server/0545-MC-4-Fix-item-position-desync.patch b/patches/server/0545-MC-4-Fix-item-position-desync.patch new file mode 100644 index 0000000000..2630f549d6 --- /dev/null +++ b/patches/server/0545-MC-4-Fix-item-position-desync.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Tue, 8 Dec 2020 20:24:52 -0600 +Subject: [PATCH] MC-4: Fix item position desync + +This fixes item position desync (MC-4) by running the item coordinates +through the encode/decode methods of the packet that causes the precision +loss, which forces the server to lose the same precision as the client +keeping them in sync. + +diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +index 3768a71491ef7836b9739bdaec7a077c523dbacd..a57957ace1a72b3308487f180a366c3879eceb21 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java ++++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java +@@ -8,11 +8,11 @@ public class VecDeltaCodec { + public Vec3 base = Vec3.ZERO; // Paper + + private static long encode(double value) { +- return Mth.lfloor(value * 4096.0D); ++ return Mth.lfloor(value * 4096.0D); // Paper - check ItemEntity#setPosRaw on update + } + + private static double decode(long value) { +- return (double)value / 4096.0D; ++ return (double)value / 4096.0D; // Paper - check ItemEntity#setPosRaw on update + } + + public Vec3 decode(long x, long y, long z) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 96bb4a32a04851bd3a83f2b214efd2297ff8b57e..9770cc15579d90480993135a5a54ad1ac1133df1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3904,6 +3904,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { + // Paper end ++ // Paper start - fix MC-4 ++ if (this instanceof ItemEntity) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { ++ // encode/decode from ClientboundMoveEntityPacket ++ x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); ++ y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); ++ z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); ++ } ++ } ++ // Paper end - fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { + this.position = new Vec3(x, y, z); + int i = Mth.floor(x); diff --git a/patches/server/0546-Additional-Block-Material-API-s.patch b/patches/server/0546-Additional-Block-Material-API-s.patch new file mode 100644 index 0000000000..e93239aae5 --- /dev/null +++ b/patches/server/0546-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 653dcec33f83ab490af424faa6ede7df07d41742..817d7143fb60916a6747ee4b9f7c77becd9071c1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -463,6 +463,25 @@ public class CraftBlock implements Block { + return this.getNMS().getMaterial().isLiquid(); + } + ++ // Paper start ++ @Override ++ public boolean isBuildable() { ++ return getNMS().getMaterial().isSolid(); // This is in fact isSolid, despite the fact that isSolid below returns blocksMotion ++ } ++ @Override ++ public boolean isBurnable() { ++ return getNMS().getMaterial().isFlammable(); ++ } ++ @Override ++ public boolean isReplaceable() { ++ return getNMS().getMaterial().isReplaceable(); ++ } ++ @Override ++ public boolean isSolid() { ++ return getNMS().getMaterial().blocksMotion(); ++ } ++ // Paper end ++ + @Override + public PistonMoveReaction getPistonMoveReaction() { + return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); diff --git a/patches/server/0546-MC-4-Fix-item-position-desync.patch b/patches/server/0546-MC-4-Fix-item-position-desync.patch deleted file mode 100644 index 4172d387c4..0000000000 --- a/patches/server/0546-MC-4-Fix-item-position-desync.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Tue, 8 Dec 2020 20:24:52 -0600 -Subject: [PATCH] MC-4: Fix item position desync - -This fixes item position desync (MC-4) by running the item coordinates -through the encode/decode methods of the packet that causes the precision -loss, which forces the server to lose the same precision as the client -keeping them in sync. - -diff --git a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -index 3768a71491ef7836b9739bdaec7a077c523dbacd..a57957ace1a72b3308487f180a366c3879eceb21 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -+++ b/src/main/java/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -8,11 +8,11 @@ public class VecDeltaCodec { - public Vec3 base = Vec3.ZERO; // Paper - - private static long encode(double value) { -- return Mth.lfloor(value * 4096.0D); -+ return Mth.lfloor(value * 4096.0D); // Paper - check ItemEntity#setPosRaw on update - } - - private static double decode(long value) { -- return (double)value / 4096.0D; -+ return (double)value / 4096.0D; // Paper - check ItemEntity#setPosRaw on update - } - - public Vec3 decode(long x, long y, long z) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 11a5a0b3002300b882511b4ebafe96af5e230cb8..81c70cf84618f4e987c68dba081317a658c6cd91 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3890,6 +3890,16 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { - // Paper end -+ // Paper start - fix MC-4 -+ if (this instanceof ItemEntity) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { -+ // encode/decode from ClientboundMoveEntityPacket -+ x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); -+ y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); -+ z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); -+ } -+ } -+ // Paper end - fix MC-4 - if (this.position.x != x || this.position.y != y || this.position.z != z) { - this.position = new Vec3(x, y, z); - int i = Mth.floor(x); diff --git a/patches/server/0547-Additional-Block-Material-API-s.patch b/patches/server/0547-Additional-Block-Material-API-s.patch deleted file mode 100644 index e93239aae5..0000000000 --- a/patches/server/0547-Additional-Block-Material-API-s.patch +++ /dev/null @@ -1,40 +0,0 @@ -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 653dcec33f83ab490af424faa6ede7df07d41742..817d7143fb60916a6747ee4b9f7c77becd9071c1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -463,6 +463,25 @@ public class CraftBlock implements Block { - return this.getNMS().getMaterial().isLiquid(); - } - -+ // Paper start -+ @Override -+ public boolean isBuildable() { -+ return getNMS().getMaterial().isSolid(); // This is in fact isSolid, despite the fact that isSolid below returns blocksMotion -+ } -+ @Override -+ public boolean isBurnable() { -+ return getNMS().getMaterial().isFlammable(); -+ } -+ @Override -+ public boolean isReplaceable() { -+ return getNMS().getMaterial().isReplaceable(); -+ } -+ @Override -+ public boolean isSolid() { -+ return getNMS().getMaterial().blocksMotion(); -+ } -+ // Paper end -+ - @Override - public PistonMoveReaction getPistonMoveReaction() { - return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); diff --git a/patches/server/0547-Fix-harming-potion-dupe.patch b/patches/server/0547-Fix-harming-potion-dupe.patch new file mode 100644 index 0000000000..d131d6c979 --- /dev/null +++ b/patches/server/0547-Fix-harming-potion-dupe.patch @@ -0,0 +1,49 @@ +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/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java +index 446ba28a38a597bc8c99e31087e5c90eb37f8335..44aecc47d94481cb6286a60fc2bb720e3486bcc2 100644 +--- a/src/main/java/net/minecraft/world/item/PotionItem.java ++++ b/src/main/java/net/minecraft/world/item/PotionItem.java +@@ -53,6 +53,7 @@ public class PotionItem extends Item { + CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) entityhuman, stack); + } + ++ List instantLater = new java.util.ArrayList<>(); // Paper - Fix harming potion dupe + if (!world.isClientSide) { + List list = PotionUtils.getMobEffects(stack); + Iterator iterator = list.iterator(); +@@ -61,7 +62,7 @@ public class PotionItem extends Item { + MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); + + if (mobeffect.getEffect().isInstantenous()) { +- mobeffect.getEffect().applyInstantenousEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); ++ instantLater.add(mobeffect); // Paper - Fix harming potion dupe + } else { + user.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit + } +@@ -75,7 +76,18 @@ public class PotionItem extends Item { + } + } + ++ // Paper start - Fix harming potion dupe ++ for (MobEffectInstance mobeffect : instantLater) { ++ mobeffect.getEffect().applyInstantenousEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); ++ } ++ // Paper end + if (entityhuman == null || !entityhuman.getAbilities().instabuild) { ++ // Paper start - Fix harming potion dupe ++ if (user.getHealth() <= 0 && !user.level.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY)) { ++ user.spawnAtLocation(new ItemStack(Items.GLASS_BOTTLE), 0); ++ return ItemStack.EMPTY; ++ } ++ // Paper end + if (stack.isEmpty()) { + return new ItemStack(Items.GLASS_BOTTLE); + } diff --git a/patches/server/0548-Fix-harming-potion-dupe.patch b/patches/server/0548-Fix-harming-potion-dupe.patch deleted file mode 100644 index d131d6c979..0000000000 --- a/patches/server/0548-Fix-harming-potion-dupe.patch +++ /dev/null @@ -1,49 +0,0 @@ -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/PotionItem.java b/src/main/java/net/minecraft/world/item/PotionItem.java -index 446ba28a38a597bc8c99e31087e5c90eb37f8335..44aecc47d94481cb6286a60fc2bb720e3486bcc2 100644 ---- a/src/main/java/net/minecraft/world/item/PotionItem.java -+++ b/src/main/java/net/minecraft/world/item/PotionItem.java -@@ -53,6 +53,7 @@ public class PotionItem extends Item { - CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) entityhuman, stack); - } - -+ List instantLater = new java.util.ArrayList<>(); // Paper - Fix harming potion dupe - if (!world.isClientSide) { - List list = PotionUtils.getMobEffects(stack); - Iterator iterator = list.iterator(); -@@ -61,7 +62,7 @@ public class PotionItem extends Item { - MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); - - if (mobeffect.getEffect().isInstantenous()) { -- mobeffect.getEffect().applyInstantenousEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); -+ instantLater.add(mobeffect); // Paper - Fix harming potion dupe - } else { - user.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit - } -@@ -75,7 +76,18 @@ public class PotionItem extends Item { - } - } - -+ // Paper start - Fix harming potion dupe -+ for (MobEffectInstance mobeffect : instantLater) { -+ mobeffect.getEffect().applyInstantenousEffect(entityhuman, entityhuman, user, mobeffect.getAmplifier(), 1.0D); -+ } -+ // Paper end - if (entityhuman == null || !entityhuman.getAbilities().instabuild) { -+ // Paper start - Fix harming potion dupe -+ if (user.getHealth() <= 0 && !user.level.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_KEEPINVENTORY)) { -+ user.spawnAtLocation(new ItemStack(Items.GLASS_BOTTLE), 0); -+ return ItemStack.EMPTY; -+ } -+ // Paper end - if (stack.isEmpty()) { - return new ItemStack(Items.GLASS_BOTTLE); - } diff --git a/patches/server/0548-Implement-API-to-get-Material-from-Boats-and-Minecar.patch b/patches/server/0548-Implement-API-to-get-Material-from-Boats-and-Minecar.patch new file mode 100644 index 0000000000..b68431ed82 --- /dev/null +++ b/patches/server/0548-Implement-API-to-get-Material-from-Boats-and-Minecar.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline 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/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +index 5871cd149fe07e97c5d68ffd83dae4d3fc6bcf03..f2896aa6fa5a5282b4be106320c0dad9dd6036c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java +@@ -80,6 +80,13 @@ public class CraftBoat extends CraftVehicle implements Boat { + this.getHandle().landBoats = workOnLand; + } + ++ // Paper start ++ @Override ++ public org.bukkit.Material getBoatMaterial() { ++ return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getHandle().getDropItem()); ++ } ++ // Paper end ++ + @Override + public Status getStatus() { + return CraftBoat.boatStatusFromNms(this.getHandle().status); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java +index 053112d7411caa6f439bd344e74aff8c844d93ac..067fcc1f44d59dd675a9cc5485234c87366ffe10 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.AbstractMinecart; ++import net.minecraft.world.item.Items; // Paper + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; ++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,22 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { + this.getHandle().setDerailedVelocityMod(derailed); + } + ++ // Paper start ++ @Override ++ public Material getMinecartMaterial() { ++ net.minecraft.world.item.Item minecartItem = switch (getHandle().getMinecartType()) { ++ case CHEST -> Items.CHEST_MINECART; ++ case FURNACE -> Items.FURNACE_MINECART; ++ case TNT -> Items.TNT_MINECART; ++ case HOPPER -> Items.HOPPER_MINECART; ++ case COMMAND_BLOCK -> Items.COMMAND_BLOCK_MINECART; ++ case RIDEABLE, SPAWNER -> Items.MINECART; ++ }; ++ ++ return CraftMagicNumbers.getMaterial(minecartItem); ++ } ++ // Paper end ++ + @Override + public AbstractMinecart getHandle() { + return (AbstractMinecart) entity; diff --git a/patches/server/0549-Cache-burn-durations.patch b/patches/server/0549-Cache-burn-durations.patch new file mode 100644 index 0000000000..35387cbe57 --- /dev/null +++ b/patches/server/0549-Cache-burn-durations.patch @@ -0,0 +1,36 @@ +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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index d3a9b42fd4fa487e0acb1c95d57f78ccfbefbdff..1cc2c13ab07b9dc4492cec55314e12d7536d5453 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -132,7 +132,13 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + this.recipeType = recipeType; // Paper + } + ++ private static Map cachedBurnDurations = null; // Paper - cache burn durations + public static Map getFuel() { ++ // Paper start - cache burn durations ++ if(cachedBurnDurations != null) { ++ return cachedBurnDurations; ++ } ++ // Paper end + Map map = Maps.newLinkedHashMap(); + + AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.LAVA_BUCKET, 20000); +@@ -200,7 +206,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.AZALEA, 100); + AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.FLOWERING_AZALEA, 100); + AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.MANGROVE_ROOTS, 300); +- return map; ++ // Paper start - cache burn durations ++ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); ++ return cachedBurnDurations; ++ // Paper end + } + + // CraftBukkit start - add fields and methods diff --git a/patches/server/0549-Implement-API-to-get-Material-from-Boats-and-Minecar.patch b/patches/server/0549-Implement-API-to-get-Material-from-Boats-and-Minecar.patch deleted file mode 100644 index b68431ed82..0000000000 --- a/patches/server/0549-Implement-API-to-get-Material-from-Boats-and-Minecar.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline 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/org/bukkit/craftbukkit/entity/CraftBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -index 5871cd149fe07e97c5d68ffd83dae4d3fc6bcf03..f2896aa6fa5a5282b4be106320c0dad9dd6036c5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBoat.java -@@ -80,6 +80,13 @@ public class CraftBoat extends CraftVehicle implements Boat { - this.getHandle().landBoats = workOnLand; - } - -+ // Paper start -+ @Override -+ public org.bukkit.Material getBoatMaterial() { -+ return org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(this.getHandle().getDropItem()); -+ } -+ // Paper end -+ - @Override - public Status getStatus() { - return CraftBoat.boatStatusFromNms(this.getHandle().status); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecart.java -index 053112d7411caa6f439bd344e74aff8c844d93ac..067fcc1f44d59dd675a9cc5485234c87366ffe10 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.AbstractMinecart; -+import net.minecraft.world.item.Items; // Paper - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.state.BlockState; -+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,22 @@ public abstract class CraftMinecart extends CraftVehicle implements Minecart { - this.getHandle().setDerailedVelocityMod(derailed); - } - -+ // Paper start -+ @Override -+ public Material getMinecartMaterial() { -+ net.minecraft.world.item.Item minecartItem = switch (getHandle().getMinecartType()) { -+ case CHEST -> Items.CHEST_MINECART; -+ case FURNACE -> Items.FURNACE_MINECART; -+ case TNT -> Items.TNT_MINECART; -+ case HOPPER -> Items.HOPPER_MINECART; -+ case COMMAND_BLOCK -> Items.COMMAND_BLOCK_MINECART; -+ case RIDEABLE, SPAWNER -> Items.MINECART; -+ }; -+ -+ return CraftMagicNumbers.getMaterial(minecartItem); -+ } -+ // Paper end -+ - @Override - public AbstractMinecart getHandle() { - return (AbstractMinecart) entity; diff --git a/patches/server/0550-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0550-Allow-disabling-mob-spawner-spawn-egg-transformation.patch new file mode 100644 index 0000000000..b11735c5d7 --- /dev/null +++ b/patches/server/0550-Allow-disabling-mob-spawner-spawn-egg-transformation.patch @@ -0,0 +1,19 @@ +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/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +index b518c5d18c2d3486382ed4e59941b155d5534014..6df94a75b7c4c2593598088d84cf0a4a57e3fd99 100644 +--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java ++++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java +@@ -61,7 +61,7 @@ public class SpawnEggItem extends Item { + Direction enumdirection = context.getClickedFace(); + BlockState iblockdata = world.getBlockState(blockposition); + +- if (iblockdata.is(Blocks.SPAWNER)) { ++ if (!world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation && iblockdata.is(Blocks.SPAWNER)) { // Paper + BlockEntity tileentity = world.getBlockEntity(blockposition); + + if (tileentity instanceof SpawnerBlockEntity) { diff --git a/patches/server/0550-Cache-burn-durations.patch b/patches/server/0550-Cache-burn-durations.patch deleted file mode 100644 index 35387cbe57..0000000000 --- a/patches/server/0550-Cache-burn-durations.patch +++ /dev/null @@ -1,36 +0,0 @@ -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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index d3a9b42fd4fa487e0acb1c95d57f78ccfbefbdff..1cc2c13ab07b9dc4492cec55314e12d7536d5453 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -132,7 +132,13 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - this.recipeType = recipeType; // Paper - } - -+ private static Map cachedBurnDurations = null; // Paper - cache burn durations - public static Map getFuel() { -+ // Paper start - cache burn durations -+ if(cachedBurnDurations != null) { -+ return cachedBurnDurations; -+ } -+ // Paper end - Map map = Maps.newLinkedHashMap(); - - AbstractFurnaceBlockEntity.add(map, (ItemLike) Items.LAVA_BUCKET, 20000); -@@ -200,7 +206,10 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.AZALEA, 100); - AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.FLOWERING_AZALEA, 100); - AbstractFurnaceBlockEntity.add(map, (ItemLike) Blocks.MANGROVE_ROOTS, 300); -- return map; -+ // Paper start - cache burn durations -+ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); -+ return cachedBurnDurations; -+ // Paper end - } - - // CraftBukkit start - add fields and methods diff --git a/patches/server/0551-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0551-Allow-disabling-mob-spawner-spawn-egg-transformation.patch deleted file mode 100644 index b11735c5d7..0000000000 --- a/patches/server/0551-Allow-disabling-mob-spawner-spawn-egg-transformation.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -index b518c5d18c2d3486382ed4e59941b155d5534014..6df94a75b7c4c2593598088d84cf0a4a57e3fd99 100644 ---- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java -+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -@@ -61,7 +61,7 @@ public class SpawnEggItem extends Item { - Direction enumdirection = context.getClickedFace(); - BlockState iblockdata = world.getBlockState(blockposition); - -- if (iblockdata.is(Blocks.SPAWNER)) { -+ if (!world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation && iblockdata.is(Blocks.SPAWNER)) { // Paper - BlockEntity tileentity = world.getBlockEntity(blockposition); - - if (tileentity instanceof SpawnerBlockEntity) { diff --git a/patches/server/0551-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0551-Fix-Not-a-string-Map-Conversion-spam.patch new file mode 100644 index 0000000000..8967f3f735 --- /dev/null +++ b/patches/server/0551-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. + +Track spigot issue to see when fixed: https://hub.spigotmc.org/jira/browse/SPIGOT-6181 + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 913fabc7f42c05ccec6501247a5e8d1d481756ee..4acbcafc158cf11af51d9518ba5b83aaa75f52a1 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -15,6 +15,8 @@ import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.ListTag; + import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.StringTag; + import net.minecraft.nbt.Tag; + import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.Packet; +@@ -103,7 +105,26 @@ public class MapItemSavedData extends SavedData { + } + + public static MapItemSavedData load(CompoundTag nbt) { +- DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); // CraftBukkit - decompile error ++ // Paper start - fix "Not a string" spam ++ Tag dimension = nbt.get("dimension"); ++ if (dimension instanceof NumericTag && ((NumericTag) dimension).getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { ++ long least = nbt.getLong("UUIDLeast"); ++ long most = nbt.getLong("UUIDMost"); ++ ++ if (least != 0L && most != 0L) { ++ UUID uuid = new UUID(most, least); ++ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid); ++ if (world != null) { ++ dimension = StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); ++ } else { ++ dimension = StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } else { ++ dimension = StringTag.valueOf("bukkit:_invalidworld_"); ++ } ++ } ++ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error ++ // Paper end - fix "Not a string" spam + Logger logger = MapItemSavedData.LOGGER; + + Objects.requireNonNull(logger); diff --git a/patches/server/0552-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0552-Fix-Not-a-string-Map-Conversion-spam.patch deleted file mode 100644 index 8967f3f735..0000000000 --- a/patches/server/0552-Fix-Not-a-string-Map-Conversion-spam.patch +++ /dev/null @@ -1,54 +0,0 @@ -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. - -Track spigot issue to see when fixed: https://hub.spigotmc.org/jira/browse/SPIGOT-6181 - -diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 913fabc7f42c05ccec6501247a5e8d1d481756ee..4acbcafc158cf11af51d9518ba5b83aaa75f52a1 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -15,6 +15,8 @@ import net.minecraft.core.BlockPos; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.ListTag; - import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.StringTag; - import net.minecraft.nbt.Tag; - import net.minecraft.network.chat.Component; - import net.minecraft.network.protocol.Packet; -@@ -103,7 +105,26 @@ public class MapItemSavedData extends SavedData { - } - - public static MapItemSavedData load(CompoundTag nbt) { -- DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); // CraftBukkit - decompile error -+ // Paper start - fix "Not a string" spam -+ Tag dimension = nbt.get("dimension"); -+ if (dimension instanceof NumericTag && ((NumericTag) dimension).getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) { -+ long least = nbt.getLong("UUIDLeast"); -+ long most = nbt.getLong("UUIDMost"); -+ -+ if (least != 0L && most != 0L) { -+ UUID uuid = new UUID(most, least); -+ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid); -+ if (world != null) { -+ dimension = StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH)); -+ } else { -+ dimension = StringTag.valueOf("bukkit:_invalidworld_"); -+ } -+ } else { -+ dimension = StringTag.valueOf("bukkit:_invalidworld_"); -+ } -+ } -+ DataResult> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error -+ // Paper end - fix "Not a string" spam - Logger logger = MapItemSavedData.LOGGER; - - Objects.requireNonNull(logger); diff --git a/patches/server/0552-Implement-PlayerFlowerPotManipulateEvent.patch b/patches/server/0552-Implement-PlayerFlowerPotManipulateEvent.patch new file mode 100644 index 0000000000..8f512f54d6 --- /dev/null +++ b/patches/server/0552-Implement-PlayerFlowerPotManipulateEvent.patch @@ -0,0 +1,37 @@ +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/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +index 9a8fc69de43fcfeebcb31c895fa4b5868952fa0a..db05c1ea847d60ad45d33cd798cb34ad3f5cfd75 100644 +--- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java +@@ -52,6 +52,26 @@ public class FlowerPotBlock extends Block { + boolean bl = blockState.is(Blocks.AIR); + boolean bl2 = this.isEmpty(); + if (bl != bl2) { ++ // Paper start ++ org.bukkit.entity.Player player1 = (org.bukkit.entity.Player) player.getBukkitEntity(); ++ boolean placing = bl2; ++ org.bukkit.block.Block bukkitblock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ org.bukkit.inventory.ItemStack bukkititemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemStack); ++ org.bukkit.Material mat = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(content); ++ org.bukkit.inventory.ItemStack bukkititemstack1 = new org.bukkit.inventory.ItemStack(mat, 1); ++ org.bukkit.inventory.ItemStack whichitem = placing ? bukkititemstack : bukkititemstack1; ++ ++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent(player1, bukkitblock, whichitem, placing); ++ player1.getServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ // Update client ++ player1.sendBlockChange(bukkitblock.getLocation(), bukkitblock.getBlockData()); ++ player1.updateInventory(); ++ ++ return InteractionResult.PASS; ++ } ++ // Paper end + if (bl2) { + world.setBlock(pos, blockState, 3); + player.awardStat(Stats.POT_FLOWER); diff --git a/patches/server/0553-Fix-interact-event-not-being-called-in-adventure.patch b/patches/server/0553-Fix-interact-event-not-being-called-in-adventure.patch new file mode 100644 index 0000000000..8cd15192dc --- /dev/null +++ b/patches/server/0553-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/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 32227020d0cba1aba3ec0fcda5f4b4cd4b1ce394..c0cca7442d3bb7df393088d66c5962bcda78e609 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1852,7 +1852,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); + + this.player.sendSystemMessage(ichatmutablecomponent, true); +- } else if (enuminteractionresult.shouldSwing()) { ++ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper + this.player.swing(enumhand, true); + } + } +@@ -2610,7 +2610,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time + org.bukkit.util.RayTraceResult result = this.player.level.getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> entity != this.player.getBukkitEntity()); + +- if (result == null) { ++ if (result == null || this.player.gameMode.getGameModeForPlayer() == GameType.ADVENTURE) { // Paper - call PlayerInteractEvent when left-clicking on a block in adventure mode + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); + } + diff --git a/patches/server/0553-Implement-PlayerFlowerPotManipulateEvent.patch b/patches/server/0553-Implement-PlayerFlowerPotManipulateEvent.patch deleted file mode 100644 index 8f512f54d6..0000000000 --- a/patches/server/0553-Implement-PlayerFlowerPotManipulateEvent.patch +++ /dev/null @@ -1,37 +0,0 @@ -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/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -index 9a8fc69de43fcfeebcb31c895fa4b5868952fa0a..db05c1ea847d60ad45d33cd798cb34ad3f5cfd75 100644 ---- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java -@@ -52,6 +52,26 @@ public class FlowerPotBlock extends Block { - boolean bl = blockState.is(Blocks.AIR); - boolean bl2 = this.isEmpty(); - if (bl != bl2) { -+ // Paper start -+ org.bukkit.entity.Player player1 = (org.bukkit.entity.Player) player.getBukkitEntity(); -+ boolean placing = bl2; -+ org.bukkit.block.Block bukkitblock = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ org.bukkit.inventory.ItemStack bukkititemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemStack); -+ org.bukkit.Material mat = org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(content); -+ org.bukkit.inventory.ItemStack bukkititemstack1 = new org.bukkit.inventory.ItemStack(mat, 1); -+ org.bukkit.inventory.ItemStack whichitem = placing ? bukkititemstack : bukkititemstack1; -+ -+ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent(player1, bukkitblock, whichitem, placing); -+ player1.getServer().getPluginManager().callEvent(event); -+ -+ if (event.isCancelled()) { -+ // Update client -+ player1.sendBlockChange(bukkitblock.getLocation(), bukkitblock.getBlockData()); -+ player1.updateInventory(); -+ -+ return InteractionResult.PASS; -+ } -+ // Paper end - if (bl2) { - world.setBlock(pos, blockState, 3); - player.awardStat(Stats.POT_FLOWER); diff --git a/patches/server/0554-Fix-interact-event-not-being-called-in-adventure.patch b/patches/server/0554-Fix-interact-event-not-being-called-in-adventure.patch deleted file mode 100644 index 9395c91ad0..0000000000 --- a/patches/server/0554-Fix-interact-event-not-being-called-in-adventure.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheMolkaPL -Date: Sun, 21 Jun 2020 17:21:46 +0200 -Subject: [PATCH] Fix interact event not being called in adventure - -Call PlayerInteractEvent when left-clicking on a block in adventure mode - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index bdab60032896ad24add7d2efb49db07a1793670a..0840813bf89eb5d51f9dae02d5100ea9ba3de928 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1852,7 +1852,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); - - this.player.sendSystemMessage(ichatmutablecomponent, true); -- } else if (enuminteractionresult.shouldSwing()) { -+ } else if (enuminteractionresult.shouldSwing() && !this.player.gameMode.interactResult) { // Paper - this.player.swing(enumhand, true); - } - } -@@ -2606,7 +2606,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time - org.bukkit.util.RayTraceResult result = this.player.level.getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> entity != this.player.getBukkitEntity()); - -- if (result == null) { -+ if (result == null || this.player.gameMode.getGameModeForPlayer() == GameType.ADVENTURE) { // Paper - call PlayerInteractEvent when left-clicking on a block in adventure mode - CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); - } - diff --git a/patches/server/0554-Zombie-API-breaking-doors.patch b/patches/server/0554-Zombie-API-breaking-doors.patch new file mode 100644 index 0000000000..b89994b417 --- /dev/null +++ b/patches/server/0554-Zombie-API-breaking-doors.patch @@ -0,0 +1,22 @@ +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/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +index 1e0154f2d06b0cc5bc58ec2de98cbdce1346da35..9f4da46dce54fe4207e24b49402fe0d3fa548e29 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java +@@ -128,6 +128,11 @@ public class CraftZombie extends CraftMonster implements Zombie { + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } ++ ++ @Override ++ public boolean supportsBreakingDoors() { ++ return getHandle().supportsBreakDoorGoal(); ++ } + // Paper end + + @Override diff --git a/patches/server/0555-Fix-nerfed-slime-when-splitting.patch b/patches/server/0555-Fix-nerfed-slime-when-splitting.patch new file mode 100644 index 0000000000..726d362dff --- /dev/null +++ b/patches/server/0555-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/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 48feda682284321d18c846e4812909f0e7e649e3..15d5a8290be35c2caebf8e296300e8f32cb597c7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -239,6 +239,7 @@ public class Slime extends Mob implements Enemy { + entityslime.setPersistenceRequired(); + } + ++ entityslime.aware = this.aware; // Paper + entityslime.setCustomName(ichatbasecomponent); + entityslime.setNoAi(flag); + entityslime.setInvulnerable(this.isInvulnerable()); diff --git a/patches/server/0555-Zombie-API-breaking-doors.patch b/patches/server/0555-Zombie-API-breaking-doors.patch deleted file mode 100644 index b89994b417..0000000000 --- a/patches/server/0555-Zombie-API-breaking-doors.patch +++ /dev/null @@ -1,22 +0,0 @@ -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/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -index 1e0154f2d06b0cc5bc58ec2de98cbdce1346da35..9f4da46dce54fe4207e24b49402fe0d3fa548e29 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java -@@ -128,6 +128,11 @@ public class CraftZombie extends CraftMonster implements Zombie { - public void setShouldBurnInDay(boolean shouldBurnInDay) { - getHandle().setShouldBurnInDay(shouldBurnInDay); - } -+ -+ @Override -+ public boolean supportsBreakingDoors() { -+ return getHandle().supportsBreakDoorGoal(); -+ } - // Paper end - - @Override diff --git a/patches/server/0556-Add-EntityLoadCrossbowEvent.patch b/patches/server/0556-Add-EntityLoadCrossbowEvent.patch new file mode 100644 index 0000000000..6ffab47e6e --- /dev/null +++ b/patches/server/0556-Add-EntityLoadCrossbowEvent.patch @@ -0,0 +1,42 @@ +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/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java +index d86ec8c4c1c3e7974463a545d80ed9744de0fbbb..3b8629d31dc7bc66debe9c56593fbd071a6ddd11 100644 +--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java ++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java +@@ -88,7 +88,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + int j = this.getUseDuration(stack) - remainingUseTicks; + float f = CrossbowItem.getPowerForTime(j, stack); + +- if (f >= 1.0F && !CrossbowItem.isCharged(stack) && CrossbowItem.tryLoadProjectiles(user, stack)) { ++ // Paper start - EntityLoadCrossbowEvent ++ if (f >= 1.0F && !CrossbowItem.isCharged(stack) /*&& CrossbowItem.tryLoadProjectiles(entityliving, itemstack)*/) { ++ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), user.getUsedItemHand() == InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND); ++ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem())) { ++ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote(); ++ return; ++ } ++ // Paper end + CrossbowItem.setCharged(stack, true); + SoundSource soundcategory = user instanceof Player ? SoundSource.PLAYERS : SoundSource.HOSTILE; + +@@ -98,9 +105,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { + } + + private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile) { ++ // Paper start ++ return CrossbowItem.tryLoadProjectiles(shooter, projectile, true); ++ } ++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile, boolean consume) { ++ // Paper end + int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, projectile); + int j = i == 0 ? 1 : 3; +- boolean flag = shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; ++ boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; // Paper - add consume + ItemStack itemstack1 = shooter.getProjectile(projectile); + ItemStack itemstack2 = itemstack1.copy(); + diff --git a/patches/server/0556-Fix-nerfed-slime-when-splitting.patch b/patches/server/0556-Fix-nerfed-slime-when-splitting.patch deleted file mode 100644 index 726d362dff..0000000000 --- a/patches/server/0556-Fix-nerfed-slime-when-splitting.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index 48feda682284321d18c846e4812909f0e7e649e3..15d5a8290be35c2caebf8e296300e8f32cb597c7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -239,6 +239,7 @@ public class Slime extends Mob implements Enemy { - entityslime.setPersistenceRequired(); - } - -+ entityslime.aware = this.aware; // Paper - entityslime.setCustomName(ichatbasecomponent); - entityslime.setNoAi(flag); - entityslime.setInvulnerable(this.isInvulnerable()); diff --git a/patches/server/0557-Add-EntityLoadCrossbowEvent.patch b/patches/server/0557-Add-EntityLoadCrossbowEvent.patch deleted file mode 100644 index 6ffab47e6e..0000000000 --- a/patches/server/0557-Add-EntityLoadCrossbowEvent.patch +++ /dev/null @@ -1,42 +0,0 @@ -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/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index d86ec8c4c1c3e7974463a545d80ed9744de0fbbb..3b8629d31dc7bc66debe9c56593fbd071a6ddd11 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -88,7 +88,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - int j = this.getUseDuration(stack) - remainingUseTicks; - float f = CrossbowItem.getPowerForTime(j, stack); - -- if (f >= 1.0F && !CrossbowItem.isCharged(stack) && CrossbowItem.tryLoadProjectiles(user, stack)) { -+ // Paper start - EntityLoadCrossbowEvent -+ if (f >= 1.0F && !CrossbowItem.isCharged(stack) /*&& CrossbowItem.tryLoadProjectiles(entityliving, itemstack)*/) { -+ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), user.getUsedItemHand() == InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND); -+ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem())) { -+ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote(); -+ return; -+ } -+ // Paper end - CrossbowItem.setCharged(stack, true); - SoundSource soundcategory = user instanceof Player ? SoundSource.PLAYERS : SoundSource.HOSTILE; - -@@ -98,9 +105,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - } - - private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile) { -+ // Paper start -+ return CrossbowItem.tryLoadProjectiles(shooter, projectile, true); -+ } -+ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack projectile, boolean consume) { -+ // Paper end - int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, projectile); - int j = i == 0 ? 1 : 3; -- boolean flag = shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; -+ boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; // Paper - add consume - ItemStack itemstack1 = shooter.getProjectile(projectile); - ItemStack itemstack2 = itemstack1.copy(); - diff --git a/patches/server/0557-Guardian-beam-workaround.patch b/patches/server/0557-Guardian-beam-workaround.patch new file mode 100644 index 0000000000..fa66573d2b --- /dev/null +++ b/patches/server/0557-Guardian-beam-workaround.patch @@ -0,0 +1,20 @@ +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/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +index 9ec6145fe04ec64bbee8ec6a837719caebdbc6f5..689ad22925b2561f7c8db961743eb1f821dbb25f 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +@@ -8,7 +8,7 @@ public class ClientboundSetTimePacket implements Packet +Date: Sun, 20 Dec 2020 16:41:44 -0800 +Subject: [PATCH] Added WorldGameRuleChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +index 1b66c33a55a9516269c80f5052fb103418b11367..745b8724b7536a5b2c2c94ae8fd703ea755e8072 100644 +--- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java +@@ -33,7 +33,7 @@ public class GameRuleCommand { + CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); + T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit + +- t0.setFromArgument(context, "value"); ++ t0.setFromArgument(context, "value", key); // Paper + commandlistenerwrapper.sendSuccess(Component.translatable("commands.gamerule.set", key.getId(), t0.toString()), true); + return t0.getCommandResult(); + } +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 800325a544bb9f228ccbeb0a52d7f380a8c6083e..3c93bfeb94168f832904a8462ae23b06e81e080d 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -262,10 +262,10 @@ public class GameRules { + this.type = type; + } + +- protected abstract void updateFromArgument(CommandContext context, String name); ++ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper + +- public void setFromArgument(CommandContext context, String name) { +- this.updateFromArgument(context, name); ++ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper ++ this.updateFromArgument(context, name, gameRuleKey); // Paper + this.onChanged(((CommandSourceStack) context.getSource()).getServer()); + } + +@@ -323,8 +323,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = BoolArgumentType.getBool(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Boolean.parseBoolean(event.getValue()); ++ // Paper end + } + + public boolean get() { +@@ -388,8 +391,11 @@ public class GameRules { + } + + @Override +- protected void updateFromArgument(CommandContext context, String name) { +- this.value = IntegerArgumentType.getInteger(context, name); ++ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start ++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); ++ if (!event.callEvent()) return; ++ this.value = Integer.parseInt(event.getValue()); ++ // Paper end + } + + public int get() { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index b95aed97b48bf31091ee2f44a21ad88071e97bf3..42423b020f9c2ef2ba025b444be076c38314c721 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1803,8 +1803,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + if (!this.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.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); +- handle.deserialize(value); ++ handle.deserialize(event.getValue()); // Paper + handle.onChanged(this.getHandle().getServer()); + return true; + } +@@ -1839,8 +1844,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + if (!this.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.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); +- handle.deserialize(newValue.toString()); ++ handle.deserialize(event.getValue()); // Paper + handle.onChanged(this.getHandle().getServer()); + return true; + } diff --git a/patches/server/0558-Guardian-beam-workaround.patch b/patches/server/0558-Guardian-beam-workaround.patch deleted file mode 100644 index fa66573d2b..0000000000 --- a/patches/server/0558-Guardian-beam-workaround.patch +++ /dev/null @@ -1,20 +0,0 @@ -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/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java -index 9ec6145fe04ec64bbee8ec6a837719caebdbc6f5..689ad22925b2561f7c8db961743eb1f821dbb25f 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java -@@ -8,7 +8,7 @@ public class ClientboundSetTimePacket implements Packet +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 f02fc7d6d8d0aba84be5e85c8de0ccc0e5a01c78..5c3b5cb0ffc3920da82b525bbc7a8963c981c595 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1994,7 +1994,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { ++ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { ++ // Paper end + RegistryAccess.Frozen iregistrycustom_dimension = this.registryAccess(); + CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { + Stream stream = dataPacks.stream(); // CraftBukkit - decompile error +@@ -2020,6 +2026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { +- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { ++ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { + ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); + source.sendFailure(Component.translatable("commands.reload.failure")); + return null; +@@ -50,7 +50,7 @@ public class ReloadCommand { + WorldData savedata = minecraftserver.getWorldData(); + Collection collection = resourcepackrepository.getSelectedIds(); + Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); +- minecraftserver.reloadResources(collection1); ++ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper + } + // CraftBukkit end + diff --git a/patches/server/0559-Added-WorldGameRuleChangeEvent.patch b/patches/server/0559-Added-WorldGameRuleChangeEvent.patch deleted file mode 100644 index 86de4a1af6..0000000000 --- a/patches/server/0559-Added-WorldGameRuleChangeEvent.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 20 Dec 2020 16:41:44 -0800 -Subject: [PATCH] Added WorldGameRuleChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -index 1b66c33a55a9516269c80f5052fb103418b11367..745b8724b7536a5b2c2c94ae8fd703ea755e8072 100644 ---- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java -@@ -33,7 +33,7 @@ public class GameRuleCommand { - CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource(); - T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit - -- t0.setFromArgument(context, "value"); -+ t0.setFromArgument(context, "value", key); // Paper - commandlistenerwrapper.sendSuccess(Component.translatable("commands.gamerule.set", key.getId(), t0.toString()), true); - return t0.getCommandResult(); - } -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 800325a544bb9f228ccbeb0a52d7f380a8c6083e..3c93bfeb94168f832904a8462ae23b06e81e080d 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -262,10 +262,10 @@ public class GameRules { - this.type = type; - } - -- protected abstract void updateFromArgument(CommandContext context, String name); -+ protected abstract void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey); // Paper - -- public void setFromArgument(CommandContext context, String name) { -- this.updateFromArgument(context, name); -+ public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper -+ this.updateFromArgument(context, name, gameRuleKey); // Paper - this.onChanged(((CommandSourceStack) context.getSource()).getServer()); - } - -@@ -323,8 +323,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = BoolArgumentType.getBool(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Boolean.parseBoolean(event.getValue()); -+ // Paper end - } - - public boolean get() { -@@ -388,8 +391,11 @@ public class GameRules { - } - - @Override -- protected void updateFromArgument(CommandContext context, String name) { -- this.value = IntegerArgumentType.getInteger(context, name); -+ protected void updateFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper start -+ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name))); -+ if (!event.callEvent()) return; -+ this.value = Integer.parseInt(event.getValue()); -+ // Paper end - } - - public int get() { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index b95aed97b48bf31091ee2f44a21ad88071e97bf3..42423b020f9c2ef2ba025b444be076c38314c721 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1803,8 +1803,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.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.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); -- handle.deserialize(value); -+ handle.deserialize(event.getValue()); // Paper - handle.onChanged(this.getHandle().getServer()); - return true; - } -@@ -1839,8 +1844,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - if (!this.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.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); -- handle.deserialize(newValue.toString()); -+ handle.deserialize(event.getValue()); // Paper - handle.onChanged(this.getHandle().getServer()); - return true; - } diff --git a/patches/server/0560-Added-ServerResourcesReloadedEvent.patch b/patches/server/0560-Added-ServerResourcesReloadedEvent.patch deleted file mode 100644 index d030c34c58..0000000000 --- a/patches/server/0560-Added-ServerResourcesReloadedEvent.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 20:04:01 -0800 -Subject: [PATCH] Added ServerResourcesReloadedEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f02fc7d6d8d0aba84be5e85c8de0ccc0e5a01c78..5c3b5cb0ffc3920da82b525bbc7a8963c981c595 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1994,7 +1994,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop reloadResources(Collection dataPacks) { -+ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+ public CompletableFuture reloadResources(Collection dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) { -+ // Paper end - RegistryAccess.Frozen iregistrycustom_dimension = this.registryAccess(); - CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { - Stream stream = dataPacks.stream(); // CraftBukkit - decompile error -@@ -2020,6 +2026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop dataPacks, CommandSourceStack source) { -- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> { -+ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { - ReloadCommand.LOGGER.warn("Failed to execute reload", throwable); - source.sendFailure(Component.translatable("commands.reload.failure")); - return null; -@@ -50,7 +50,7 @@ public class ReloadCommand { - WorldData savedata = minecraftserver.getWorldData(); - Collection collection = resourcepackrepository.getSelectedIds(); - Collection collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection); -- minecraftserver.reloadResources(collection1); -+ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - } - // CraftBukkit end - diff --git a/patches/server/0560-Added-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0560-Added-world-settings-for-mobs-picking-up-loot.patch new file mode 100644 index 0000000000..4c1e7b2549 --- /dev/null +++ b/patches/server/0560-Added-world-settings-for-mobs-picking-up-loot.patch @@ -0,0 +1,32 @@ +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/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index 60064770f08cbee19eeb43bf7c144b6eefbc4888..c2a26b91d9065fdb52a1ded6c3295093c244d7eb 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -150,7 +150,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + this.populateDefaultEquipmentSlots(randomsource, difficulty); + this.populateDefaultEquipmentEnchantments(randomsource, difficulty); + this.reassessWeaponGoal(); +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); ++ this.setCanPickUpLoot(this.level.paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper + if (this.getItemBySlot(EquipmentSlot.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/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 67fe3b79b6fe4064b26f87734174a394b9e2b3e3..c84bae4aac13698893420e669ca323194e28ca48 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -509,7 +509,7 @@ public class Zombie extends Monster { + Object object = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + float f = difficulty.getSpecialMultiplier(); + +- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); ++ this.setCanPickUpLoot(this.level.paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper + if (object == null) { + object = new Zombie.ZombieGroupData(Zombie.getSpawnAsBabyOdds(randomsource), true); + } diff --git a/patches/server/0561-Added-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0561-Added-world-settings-for-mobs-picking-up-loot.patch deleted file mode 100644 index 4c1e7b2549..0000000000 --- a/patches/server/0561-Added-world-settings-for-mobs-picking-up-loot.patch +++ /dev/null @@ -1,32 +0,0 @@ -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/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index 60064770f08cbee19eeb43bf7c144b6eefbc4888..c2a26b91d9065fdb52a1ded6c3295093c244d7eb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -150,7 +150,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - this.populateDefaultEquipmentSlots(randomsource, difficulty); - this.populateDefaultEquipmentEnchantments(randomsource, difficulty); - this.reassessWeaponGoal(); -- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); -+ this.setCanPickUpLoot(this.level.paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - if (this.getItemBySlot(EquipmentSlot.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/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 67fe3b79b6fe4064b26f87734174a394b9e2b3e3..c84bae4aac13698893420e669ca323194e28ca48 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -509,7 +509,7 @@ public class Zombie extends Monster { - Object object = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); - float f = difficulty.getSpecialMultiplier(); - -- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); -+ this.setCanPickUpLoot(this.level.paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - if (object == null) { - object = new Zombie.ZombieGroupData(Zombie.getSpawnAsBabyOdds(randomsource), true); - } diff --git a/patches/server/0561-Implemented-BlockFailedDispenseEvent.patch b/patches/server/0561-Implemented-BlockFailedDispenseEvent.patch new file mode 100644 index 0000000000..9c14300d5a --- /dev/null +++ b/patches/server/0561-Implemented-BlockFailedDispenseEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> +Date: Wed, 22 Apr 2020 09:40:38 +0200 +Subject: [PATCH] Implemented BlockFailedDispenseEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index c0e19826163b0eaa429d217b54bb67b8b582a669..85c5319837295bd2f85baebfe8d6660b267f1d5f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -84,8 +84,10 @@ public class DispenserBlock extends BaseEntityBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) {// Paper - BlockFailedDispenseEvent is called here + world.levelEvent(1001, pos, 0); + world.gameEvent((Entity) null, GameEvent.DISPENSE_FAIL, pos); ++ } // Paper + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); +diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +index 1415ad60163f6584619cc7caa61f1848d6ebaa93..801c4c120e98584bcf218a4ef9bd66d7d18c1097 100644 +--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java +@@ -44,6 +44,7 @@ public class DropperBlock extends DispenserBlock { + int i = tileentitydispenser.getRandomSlot(world.random); + + if (i < 0) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - BlockFailedDispenseEvent is called here + world.levelEvent(1001, pos, 0); + } else { + ItemStack itemstack = tileentitydispenser.getItem(i); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 7f8dbc94ad04ff58e0ee7591b42e268ee4b75576..5162109ea1f8284f0302306f8dac3048ce0b7010 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1868,4 +1868,12 @@ public class CraftEventFactory { + EntitiesUnloadEvent event = new EntitiesUnloadEvent(new CraftChunk((ServerLevel) world, coords.x, coords.z), bukkitEntities); + Bukkit.getPluginManager().callEvent(event); + } ++ ++ // Paper start ++ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos blockposition) { ++ org.bukkit.block.Block block = serverLevel.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); ++ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); ++ return event.callEvent(); ++ } ++ // Paper end + } diff --git a/patches/server/0562-Added-PlayerLecternPageChangeEvent.patch b/patches/server/0562-Added-PlayerLecternPageChangeEvent.patch new file mode 100644 index 0000000000..d8fd03b1c7 --- /dev/null +++ b/patches/server/0562-Added-PlayerLecternPageChangeEvent.patch @@ -0,0 +1,46 @@ +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/LecternMenu.java b/src/main/java/net/minecraft/world/inventory/LecternMenu.java +index 9b80f533e94c47ebc0d46429698e084216b632ab..52471ef87994ac91b4b0017159b61ff50774848a 100644 +--- a/src/main/java/net/minecraft/world/inventory/LecternMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LecternMenu.java +@@ -64,6 +64,7 @@ public class LecternMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + int j; ++ io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper + + if (id >= 100) { + j = id - 100; +@@ -73,11 +74,25 @@ public class LecternMenu extends AbstractContainerMenu { + switch (id) { + case 1: + j = this.lecternData.get(0); +- this.setData(0, j - 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 2: + j = this.lecternData.get(0); +- this.setData(0, j + 1); ++ // Paper start ++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); ++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1); ++ if (!playerLecternPageChangeEvent.callEvent()) { ++ return false; ++ } ++ this.setData(0, playerLecternPageChangeEvent.getNewPage()); ++ // Paper end + return true; + case 3: + if (!player.mayBuild()) { diff --git a/patches/server/0562-Implemented-BlockFailedDispenseEvent.patch b/patches/server/0562-Implemented-BlockFailedDispenseEvent.patch deleted file mode 100644 index 9c14300d5a..0000000000 --- a/patches/server/0562-Implemented-BlockFailedDispenseEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TheViperShow <29604693+TheViperShow@users.noreply.github.com> -Date: Wed, 22 Apr 2020 09:40:38 +0200 -Subject: [PATCH] Implemented BlockFailedDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index c0e19826163b0eaa429d217b54bb67b8b582a669..85c5319837295bd2f85baebfe8d6660b267f1d5f 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -84,8 +84,10 @@ public class DispenserBlock extends BaseEntityBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) {// Paper - BlockFailedDispenseEvent is called here - world.levelEvent(1001, pos, 0); - world.gameEvent((Entity) null, GameEvent.DISPENSE_FAIL, pos); -+ } // Paper - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); -diff --git a/src/main/java/net/minecraft/world/level/block/DropperBlock.java b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -index 1415ad60163f6584619cc7caa61f1848d6ebaa93..801c4c120e98584bcf218a4ef9bd66d7d18c1097 100644 ---- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java -@@ -44,6 +44,7 @@ public class DropperBlock extends DispenserBlock { - int i = tileentitydispenser.getRandomSlot(world.random); - - if (i < 0) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - BlockFailedDispenseEvent is called here - world.levelEvent(1001, pos, 0); - } else { - ItemStack itemstack = tileentitydispenser.getItem(i); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 7f8dbc94ad04ff58e0ee7591b42e268ee4b75576..5162109ea1f8284f0302306f8dac3048ce0b7010 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1868,4 +1868,12 @@ public class CraftEventFactory { - EntitiesUnloadEvent event = new EntitiesUnloadEvent(new CraftChunk((ServerLevel) world, coords.x, coords.z), bukkitEntities); - Bukkit.getPluginManager().callEvent(event); - } -+ -+ // Paper start -+ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos blockposition) { -+ org.bukkit.block.Block block = serverLevel.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); -+ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); -+ return event.callEvent(); -+ } -+ // Paper end - } diff --git a/patches/server/0563-Added-PlayerLecternPageChangeEvent.patch b/patches/server/0563-Added-PlayerLecternPageChangeEvent.patch deleted file mode 100644 index d8fd03b1c7..0000000000 --- a/patches/server/0563-Added-PlayerLecternPageChangeEvent.patch +++ /dev/null @@ -1,46 +0,0 @@ -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/LecternMenu.java b/src/main/java/net/minecraft/world/inventory/LecternMenu.java -index 9b80f533e94c47ebc0d46429698e084216b632ab..52471ef87994ac91b4b0017159b61ff50774848a 100644 ---- a/src/main/java/net/minecraft/world/inventory/LecternMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LecternMenu.java -@@ -64,6 +64,7 @@ public class LecternMenu extends AbstractContainerMenu { - @Override - public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { - int j; -+ io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper - - if (id >= 100) { - j = id - 100; -@@ -73,11 +74,25 @@ public class LecternMenu extends AbstractContainerMenu { - switch (id) { - case 1: - j = this.lecternData.get(0); -- this.setData(0, j - 1); -+ // Paper start -+ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); -+ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1); -+ if (!playerLecternPageChangeEvent.callEvent()) { -+ return false; -+ } -+ this.setData(0, playerLecternPageChangeEvent.getNewPage()); -+ // Paper end - return true; - case 2: - j = this.lecternData.get(0); -- this.setData(0, j + 1); -+ // Paper start -+ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory(); -+ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1); -+ if (!playerLecternPageChangeEvent.callEvent()) { -+ return false; -+ } -+ this.setData(0, playerLecternPageChangeEvent.getNewPage()); -+ // Paper end - return true; - case 3: - if (!player.mayBuild()) { diff --git a/patches/server/0563-Added-PlayerLoomPatternSelectEvent.patch b/patches/server/0563-Added-PlayerLoomPatternSelectEvent.patch new file mode 100644 index 0000000000..a99d30af29 --- /dev/null +++ b/patches/server/0563-Added-PlayerLoomPatternSelectEvent.patch @@ -0,0 +1,48 @@ +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/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 942b4cc710bede4c942d269dcfc14ae105ab848d..178de5f2a11b4fe0b4f37f4fec26282cb17b4db5 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -173,8 +173,35 @@ public class LoomMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (id >= 0 && id < this.selectablePatterns.size()) { +- this.selectedBannerPatternIndex.set(id); +- this.setupResultSlot((Holder) this.selectablePatterns.get(id)); ++ // Paper start ++ int selectablePatternIndex = id; ++ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.block.banner.PatternType.getByIdentifier(this.selectablePatterns.get(selectablePatternIndex).value().getHashname())); ++ if (!event.callEvent()) { ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ Holder selectedPattern = null; ++ for (int i = 0; i < this.selectablePatterns.size(); i++) { ++ final Holder holder = this.selectablePatterns.get(i); ++ if (event.getPatternType().getIdentifier().equals(holder.value().getHashname())) { ++ selectablePatternIndex = i; ++ selectedPattern = holder; ++ break; ++ } ++ } ++ if (selectedPattern == null) { ++ for (BannerPattern pattern : Registry.BANNER_PATTERN) { ++ if (event.getPatternType().getIdentifier().equals(pattern.getHashname())) { ++ selectedPattern = Registry.BANNER_PATTERN.getHolder(Registry.BANNER_PATTERN.getId(pattern)).orElseThrow(); ++ break; ++ } ++ } ++ selectablePatternIndex = -1; ++ } ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ this.selectedBannerPatternIndex.set(selectablePatternIndex); ++ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected")); ++ // Paper end + return true; + } else { + return false; diff --git a/patches/server/0564-Added-PlayerLoomPatternSelectEvent.patch b/patches/server/0564-Added-PlayerLoomPatternSelectEvent.patch deleted file mode 100644 index a99d30af29..0000000000 --- a/patches/server/0564-Added-PlayerLoomPatternSelectEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -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/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -index 942b4cc710bede4c942d269dcfc14ae105ab848d..178de5f2a11b4fe0b4f37f4fec26282cb17b4db5 100644 ---- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java -@@ -173,8 +173,35 @@ public class LoomMenu extends AbstractContainerMenu { - @Override - public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { - if (id >= 0 && id < this.selectablePatterns.size()) { -- this.selectedBannerPatternIndex.set(id); -- this.setupResultSlot((Holder) this.selectablePatterns.get(id)); -+ // Paper start -+ int selectablePatternIndex = id; -+ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.block.banner.PatternType.getByIdentifier(this.selectablePatterns.get(selectablePatternIndex).value().getHashname())); -+ if (!event.callEvent()) { -+ ((Player) player.getBukkitEntity()).updateInventory(); -+ return false; -+ } -+ Holder selectedPattern = null; -+ for (int i = 0; i < this.selectablePatterns.size(); i++) { -+ final Holder holder = this.selectablePatterns.get(i); -+ if (event.getPatternType().getIdentifier().equals(holder.value().getHashname())) { -+ selectablePatternIndex = i; -+ selectedPattern = holder; -+ break; -+ } -+ } -+ if (selectedPattern == null) { -+ for (BannerPattern pattern : Registry.BANNER_PATTERN) { -+ if (event.getPatternType().getIdentifier().equals(pattern.getHashname())) { -+ selectedPattern = Registry.BANNER_PATTERN.getHolder(Registry.BANNER_PATTERN.getId(pattern)).orElseThrow(); -+ break; -+ } -+ } -+ selectablePatternIndex = -1; -+ } -+ ((Player) player.getBukkitEntity()).updateInventory(); -+ this.selectedBannerPatternIndex.set(selectablePatternIndex); -+ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected")); -+ // Paper end - return true; - } else { - return false; diff --git a/patches/server/0564-Configurable-door-breaking-difficulty.patch b/patches/server/0564-Configurable-door-breaking-difficulty.patch new file mode 100644 index 0000000000..45e5e3e168 --- /dev/null +++ b/patches/server/0564-Configurable-door-breaking-difficulty.patch @@ -0,0 +1,33 @@ +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 + +Co-authored-by: Doc + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +index ffc8e20d916940fb5e28bac610e3c6bd3d493f78..a9e75a16a7dc0ff5d4f0faa92ebc444559a39325 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +@@ -197,7 +197,7 @@ public class Vindicator extends AbstractIllager { + + static class VindicatorBreakDoorGoal extends BreakDoorGoal { + public VindicatorBreakDoorGoal(Mob mob) { +- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); ++ super(mob, 6, com.google.common.base.Predicates.in(mob.level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index c84bae4aac13698893420e669ca323194e28ca48..3e1fa4336cc18eaeb048d0a1d592cf74820ff3b2 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -98,7 +98,7 @@ public class Zombie extends Monster { + + public Zombie(EntityType type, Level world) { + super(type, world); +- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); ++ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper + } + + public Zombie(Level world) { diff --git a/patches/server/0565-Configurable-door-breaking-difficulty.patch b/patches/server/0565-Configurable-door-breaking-difficulty.patch deleted file mode 100644 index 45e5e3e168..0000000000 --- a/patches/server/0565-Configurable-door-breaking-difficulty.patch +++ /dev/null @@ -1,33 +0,0 @@ -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 - -Co-authored-by: Doc - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -index ffc8e20d916940fb5e28bac610e3c6bd3d493f78..a9e75a16a7dc0ff5d4f0faa92ebc444559a39325 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -197,7 +197,7 @@ public class Vindicator extends AbstractIllager { - - static class VindicatorBreakDoorGoal extends BreakDoorGoal { - public VindicatorBreakDoorGoal(Mob mob) { -- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE); -+ super(mob, 6, com.google.common.base.Predicates.in(mob.level.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper - this.setFlags(EnumSet.of(Goal.Flag.MOVE)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index c84bae4aac13698893420e669ca323194e28ca48..3e1fa4336cc18eaeb048d0a1d592cf74820ff3b2 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -98,7 +98,7 @@ public class Zombie extends Monster { - - public Zombie(EntityType type, Level world) { - super(type, world); -- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE); -+ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - } - - public Zombie(Level world) { diff --git a/patches/server/0565-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0565-Empty-commands-shall-not-be-dispatched.patch new file mode 100644 index 0000000000..1c382dc0f6 --- /dev/null +++ b/patches/server/0565-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/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index b141d251eedd31bd115342b878afd68dc51a8518..6ad3fe4718a0db17ad6115753e533bf069ce57c6 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -245,6 +245,7 @@ public class Commands { + 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/0566-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0566-Empty-commands-shall-not-be-dispatched.patch deleted file mode 100644 index 1c382dc0f6..0000000000 --- a/patches/server/0566-Empty-commands-shall-not-be-dispatched.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index b141d251eedd31bd115342b878afd68dc51a8518..6ad3fe4718a0db17ad6115753e533bf069ce57c6 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -245,6 +245,7 @@ public class Commands { - 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/0566-Implement-API-to-expose-exact-interaction-point.patch b/patches/server/0566-Implement-API-to-expose-exact-interaction-point.patch new file mode 100644 index 0000000000..0ede6bf153 --- /dev/null +++ b/patches/server/0566-Implement-API-to-expose-exact-interaction-point.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline 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/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 1b45c1483a7ebad47162483b51036f9dfcdf62f6..32746dfbc2fdfc150583676b1bf0762398b76d75 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -507,7 +507,7 @@ public class ServerPlayerGameMode { + cancelledBlock = true; + } + +- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand); ++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); // Paper + this.firedInteract = true; + this.interactResult = event.useItemInHand() == Event.Result.DENY; + this.interactPosition = blockposition.immutable(); +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 5162109ea1f8284f0302306f8dac3048ce0b7010..7bafab016aeb3b1177b23f44696e7178f25d414a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -56,7 +56,9 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.EntityHitResult; + import net.minecraft.world.phys.HitResult; ++import net.minecraft.world.phys.Vec3; + import org.bukkit.Bukkit; ++import org.bukkit.Location; // Paper + import org.bukkit.Material; + import org.bukkit.NamespacedKey; + import org.bukkit.Server; +@@ -483,7 +485,13 @@ public class CraftEventFactory { + return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, false, hand); + } + ++ // Paper start - Add interactionPoint + public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand) { ++ return callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, hand, null); ++ } ++ ++ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 hitVec) { ++ // Paper end + Player player = (who == null) ? null : (Player) who.getBukkitEntity(); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); + +@@ -509,7 +517,10 @@ public class CraftEventFactory { + itemInHand = null; + } + +- PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == InteractionHand.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 == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), interactionPoint); ++ // Paper end + if (cancelledBlock) { + event.setUseInteractedBlock(Event.Result.DENY); + } diff --git a/patches/server/0567-Implement-API-to-expose-exact-interaction-point.patch b/patches/server/0567-Implement-API-to-expose-exact-interaction-point.patch deleted file mode 100644 index 0ede6bf153..0000000000 --- a/patches/server/0567-Implement-API-to-expose-exact-interaction-point.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline 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/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 1b45c1483a7ebad47162483b51036f9dfcdf62f6..32746dfbc2fdfc150583676b1bf0762398b76d75 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -507,7 +507,7 @@ public class ServerPlayerGameMode { - cancelledBlock = true; - } - -- PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand); -+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation()); // Paper - this.firedInteract = true; - this.interactResult = event.useItemInHand() == Event.Result.DENY; - this.interactPosition = blockposition.immutable(); -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 5162109ea1f8284f0302306f8dac3048ce0b7010..7bafab016aeb3b1177b23f44696e7178f25d414a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -56,7 +56,9 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams; - import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.EntityHitResult; - import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; - import org.bukkit.Bukkit; -+import org.bukkit.Location; // Paper - import org.bukkit.Material; - import org.bukkit.NamespacedKey; - import org.bukkit.Server; -@@ -483,7 +485,13 @@ public class CraftEventFactory { - return CraftEventFactory.callPlayerInteractEvent(who, action, position, direction, itemstack, false, hand); - } - -+ // Paper start - Add interactionPoint - public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand) { -+ return callPlayerInteractEvent(who, action, position, direction, itemstack, cancelledBlock, hand, null); -+ } -+ -+ public static PlayerInteractEvent callPlayerInteractEvent(net.minecraft.world.entity.player.Player who, Action action, BlockPos position, Direction direction, ItemStack itemstack, boolean cancelledBlock, InteractionHand hand, Vec3 hitVec) { -+ // Paper end - Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); - -@@ -509,7 +517,10 @@ public class CraftEventFactory { - itemInHand = null; - } - -- PlayerInteractEvent event = new PlayerInteractEvent(player, action, itemInHand, blockClicked, blockFace, (hand == null) ? null : ((hand == InteractionHand.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 == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND), interactionPoint); -+ // Paper end - if (cancelledBlock) { - event.setUseInteractedBlock(Event.Result.DENY); - } diff --git a/patches/server/0567-Remove-stale-POIs.patch b/patches/server/0567-Remove-stale-POIs.patch new file mode 100644 index 0000000000..8627a969b7 --- /dev/null +++ b/patches/server/0567-Remove-stale-POIs.patch @@ -0,0 +1,22 @@ +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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 85c0a80f00f5968b684b3609fbe56197e4aeb202..3f4e3e57999245a83263e88e221723e72a11b31e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1920,6 +1920,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + }); + optional1.ifPresent((holder) -> { + this.getServer().execute(() -> { ++ // Paper start ++ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { ++ this.getPoiManager().remove(blockposition1); ++ } ++ // Paper end + this.getPoiManager().add(blockposition1, holder); + DebugPackets.sendPoiAddedPacket(this, blockposition1); + }); diff --git a/patches/server/0568-Fix-villager-boat-exploit.patch b/patches/server/0568-Fix-villager-boat-exploit.patch new file mode 100644 index 0000000000..293797f2f9 --- /dev/null +++ b/patches/server/0568-Fix-villager-boat-exploit.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +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 195fcf93c46967e14a3247768a575c7a73bf9e31..54fff5ebefa04f64ed1abfd12ebf48762015fae3 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -632,6 +632,14 @@ public abstract class PlayerList { + PlayerList.LOGGER.debug("Removing player mount"); + entityplayer.stopRiding(); + entity.getPassengersAndSelf().forEach((entity1) -> { ++ // Paper start ++ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { ++ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); ++ if (human != null) { ++ villager.setTradingPlayer(null); ++ } ++ } ++ // Paper end + entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); + }); + } diff --git a/patches/server/0568-Remove-stale-POIs.patch b/patches/server/0568-Remove-stale-POIs.patch deleted file mode 100644 index 8627a969b7..0000000000 --- a/patches/server/0568-Remove-stale-POIs.patch +++ /dev/null @@ -1,22 +0,0 @@ -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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 85c0a80f00f5968b684b3609fbe56197e4aeb202..3f4e3e57999245a83263e88e221723e72a11b31e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1920,6 +1920,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - }); - optional1.ifPresent((holder) -> { - this.getServer().execute(() -> { -+ // Paper start -+ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { -+ this.getPoiManager().remove(blockposition1); -+ } -+ // Paper end - this.getPoiManager().add(blockposition1, holder); - DebugPackets.sendPoiAddedPacket(this, blockposition1); - }); diff --git a/patches/server/0569-Add-sendOpLevel-API.patch b/patches/server/0569-Add-sendOpLevel-API.patch new file mode 100644 index 0000000000..67a367d475 --- /dev/null +++ b/patches/server/0569-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 54fff5ebefa04f64ed1abfd12ebf48762015fae3..c4f5a3bf60302743329b17f9b2dffcee9c1e5ab3 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1130,6 +1130,11 @@ public abstract class PlayerList { + } + + private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { ++ // Paper start - add recalculatePermissions parameter ++ this.sendPlayerPermissionLevel(player, permissionLevel, true); ++ } ++ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { ++ // Paper end + if (player.connection != null) { + byte b0; + +@@ -1144,8 +1149,10 @@ public abstract class PlayerList { + player.connection.send(new ClientboundEntityEventPacket(player, b0)); + } + ++ if (recalculatePermissions) { // Paper + player.getBukkitEntity().recalculatePermissions(); // CraftBukkit + this.server.getCommands().sendCommands(player); ++ } // Paper + } + + public boolean isWhiteListed(GameProfile profile) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 21f413b1e75c15fae888a4e6dccefbe822ad1c40..0528489348dc12c22bdb306a4b6c5c6a138f5347 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -606,6 +606,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().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); ++ } + // Paper end + + @Override diff --git a/patches/server/0569-Fix-villager-boat-exploit.patch b/patches/server/0569-Fix-villager-boat-exploit.patch deleted file mode 100644 index 293797f2f9..0000000000 --- a/patches/server/0569-Fix-villager-boat-exploit.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -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 195fcf93c46967e14a3247768a575c7a73bf9e31..54fff5ebefa04f64ed1abfd12ebf48762015fae3 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -632,6 +632,14 @@ public abstract class PlayerList { - PlayerList.LOGGER.debug("Removing player mount"); - entityplayer.stopRiding(); - entity.getPassengersAndSelf().forEach((entity1) -> { -+ // Paper start -+ if (entity1 instanceof net.minecraft.world.entity.npc.AbstractVillager villager) { -+ final net.minecraft.world.entity.player.Player human = villager.getTradingPlayer(); -+ if (human != null) { -+ villager.setTradingPlayer(null); -+ } -+ } -+ // Paper end - entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER); - }); - } diff --git a/patches/server/0570-Add-PaperRegistry.patch b/patches/server/0570-Add-PaperRegistry.patch new file mode 100644 index 0000000000..613898a9f2 --- /dev/null +++ b/patches/server/0570-Add-PaperRegistry.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Mar 2022 13:33:08 -0800 +Subject: [PATCH] Add PaperRegistry + +PaperRegistry is a server-backed impl of bukkit's Registry interface + +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistry.java b/src/main/java/io/papermc/paper/registry/PaperRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7c265d27da034986be73921d35bf08ae250b42f3 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistry.java +@@ -0,0 +1,167 @@ ++package io.papermc.paper.registry; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.base.Suppliers; ++import net.minecraft.core.Holder; ++import net.minecraft.core.Registry; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Optional; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++ ++@DefaultQualifier(NonNull.class) ++public abstract class PaperRegistry implements org.bukkit.Registry { ++ ++ @SuppressWarnings("FieldMayBeFinal") // non-final for testing ++ private static Supplier REGISTRY_ACCESS = Suppliers.memoize(() -> MinecraftServer.getServer().registryAccess()); ++ private static final Map, PaperRegistry> INTERNAL_REGISTRIES = new HashMap<>(); ++ public static final Map, PaperRegistry> REGISTRIES = Collections.unmodifiableMap(INTERNAL_REGISTRIES); ++ private static final Map, PaperRegistry> REGISTRY_BY_API_CLASS = new HashMap<>(); ++ private static final Map>, PaperRegistry> REGISTRY_BY_RES_KEY = new HashMap<>(); ++ ++ private boolean registered; ++ private final RegistryKey registryKey; ++ private final Supplier> registry; ++ private final Map cache = new ConcurrentHashMap<>(); ++ private final Map> resourceKeyCache = new ConcurrentHashMap<>(); ++ ++ public PaperRegistry(RegistryKey registryKey) { ++ this.registryKey = registryKey; ++ this.registry = Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(this.registryKey.resourceKey())); ++ } ++ ++ @Override ++ public @Nullable API get(NamespacedKey key) { ++ return this.cache.computeIfAbsent(key, k -> { ++ final @Nullable MINECRAFT nms = this.registry.get().get(CraftNamespacedKey.toMinecraft(k)); ++ if (nms != null) { ++ return this.convertToApi(k, nms); ++ } ++ return null; ++ }); ++ } ++ ++ public abstract @Nullable API convertToApi(NamespacedKey key, MINECRAFT nms); ++ ++ public API convertToApiOrThrow(ResourceLocation resourceLocation, MINECRAFT nms) { ++ return Objects.requireNonNull(this.convertToApi(resourceLocation, nms), resourceLocation + " has a null api representation"); ++ } ++ ++ public @Nullable API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) { ++ return this.convertToApi(CraftNamespacedKey.fromMinecraft(resourceLocation), nms); ++ } ++ ++ public API convertToApiOrThrow(Holder nmsHolder) { ++ return Objects.requireNonNull(this.convertToApi(nmsHolder), nmsHolder + " has a null api representation"); ++ } ++ ++ public @Nullable API convertToApi(Holder nmsHolder) { ++ final Optional> key = nmsHolder.unwrapKey(); ++ if (nmsHolder.isBound() && key.isPresent()) { ++ return this.convertToApi(key.get().location(), nmsHolder.value()); ++ } else if (!nmsHolder.isBound() && key.isPresent()) { ++ return this.convertToApi(key.get().location(), this.registry.get().getOrThrow(key.get())); ++ } else if (nmsHolder.isBound() && key.isEmpty()) { ++ final @Nullable ResourceLocation loc = this.registry.get().getKey(nmsHolder.value()); ++ if (loc != null) { ++ return this.convertToApi(loc, nmsHolder.value()); ++ } ++ } ++ throw new IllegalStateException("Cannot convert " + nmsHolder + " to an API type in: " + this.registryKey); ++ } ++ ++ public void convertToApi(Iterable> holders, Consumer apiConsumer, boolean throwOnNull) { ++ for (Holder holder : holders) { ++ final @Nullable API api = this.convertToApi(holder); ++ if (api == null && throwOnNull) { ++ throw new NullPointerException(holder + " has a null api representation"); ++ } else if (api != null) { ++ apiConsumer.accept(api); ++ } ++ } ++ } ++ ++ public MINECRAFT getMinecraftValue(API apiValue) { ++ return this.registry.get().getOptional(CraftNamespacedKey.toMinecraft(apiValue.getKey())).orElseThrow(); ++ } ++ ++ public Holder getMinecraftHolder(API apiValue) { ++ return this.registry.get().getHolderOrThrow(this.resourceKeyCache.computeIfAbsent(apiValue.getKey(), key -> ResourceKey.create(this.registryKey.resourceKey(), CraftNamespacedKey.toMinecraft(key)))); ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return this.registry.get().keySet().stream().map(key -> this.get(CraftNamespacedKey.fromMinecraft(key))).iterator(); ++ } ++ ++ public void clearCache() { ++ this.cache.clear(); ++ } ++ ++ public void register() { ++ if (this.registered) { ++ throw new IllegalStateException("Already registered: " + this.registryKey.apiClass()); ++ } ++ INTERNAL_REGISTRIES.put(this.registryKey, this); ++ REGISTRY_BY_API_CLASS.put(this.registryKey.apiClass(), this); ++ REGISTRY_BY_RES_KEY.put(this.registryKey.resourceKey(), this); ++ this.registered = true; ++ } ++ ++ @Override ++ public boolean equals(@Nullable Object o) { ++ if (this == o) return true; ++ if (o == null || !PaperRegistry.class.isAssignableFrom(o.getClass())) return false; ++ PaperRegistry that = (PaperRegistry) o; ++ return this.registryKey.equals(that.registryKey); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(this.registryKey); ++ } ++ ++ protected static Supplier> registryFor(ResourceKey> registryKey) { ++ return Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(registryKey)); ++ } ++ ++ public static void clearCaches() { ++ for (PaperRegistry registry : INTERNAL_REGISTRIES.values()) { ++ registry.clearCache(); ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static PaperRegistry getRegistry(Class classOfT) { ++ Preconditions.checkArgument(REGISTRY_BY_API_CLASS.containsKey(classOfT), "No registry for that type"); ++ return (PaperRegistry) REGISTRY_BY_API_CLASS.get(classOfT); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static PaperRegistry getRegistry(ResourceKey> resourceKey) { ++ Preconditions.checkArgument(REGISTRY_BY_RES_KEY.containsKey(resourceKey)); ++ return (PaperRegistry) REGISTRY_BY_RES_KEY.get(resourceKey); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public static PaperRegistry getRegistry(RegistryKey registryKey) { ++ Preconditions.checkArgument(INTERNAL_REGISTRIES.containsKey(registryKey)); ++ return (PaperRegistry) INTERNAL_REGISTRIES.get(registryKey); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6f39e343147803e15e7681c993b8797a629702e7 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java +@@ -0,0 +1,8 @@ ++package io.papermc.paper.registry; ++ ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++ ++public record RegistryKey(Class apiClass, ResourceKey> resourceKey) { ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5c3b5cb0ffc3920da82b525bbc7a8963c981c595..d60439d49de781b12af6fbe4ff89b7270f57cbeb 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2026,6 +2026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Registry registryFor(Class classOfT) { ++ return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT); ++ } + // Paper end + + /** +diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java +index e73a9a957cd55bf838e301ed531295162f2cfb89..23435a5914a29ac4f87ad0f34ce83842422df49f 100644 +--- a/src/test/java/org/bukkit/support/AbstractTestingBase.java ++++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java +@@ -39,6 +39,15 @@ public abstract class AbstractTestingBase { + MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, Collections.singletonList(new VanillaPackResources(ServerPacksSource.BUILT_IN_METADATA, "minecraft"))); + // add tags and loot tables for unit tests + REGISTRY_CUSTOM = RegistryAccess.builtinCopy().freeze(); ++ // Paper start ++ try { ++ java.lang.reflect.Field field = io.papermc.paper.registry.PaperRegistry.class.getDeclaredField("REGISTRY_ACCESS"); ++ field.trySetAccessible(); ++ field.set(null, com.google.common.base.Suppliers.ofInstance(REGISTRY_CUSTOM)); ++ } catch (ReflectiveOperationException ex) { ++ throw new IllegalStateException("Could not reflectively set RegistryAccess in PaperRegistry", ex); ++ } ++ // Paper end + // Register vanilla pack + DATA_PACK = ReloadableServerResources.loadResources(resourceManager, REGISTRY_CUSTOM, Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join(); + // Bind tags diff --git a/patches/server/0570-Add-sendOpLevel-API.patch b/patches/server/0570-Add-sendOpLevel-API.patch deleted file mode 100644 index 67a367d475..0000000000 --- a/patches/server/0570-Add-sendOpLevel-API.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Tue, 29 Dec 2020 15:03:03 +0100 -Subject: [PATCH] Add sendOpLevel API - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 54fff5ebefa04f64ed1abfd12ebf48762015fae3..c4f5a3bf60302743329b17f9b2dffcee9c1e5ab3 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1130,6 +1130,11 @@ public abstract class PlayerList { - } - - private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { -+ // Paper start - add recalculatePermissions parameter -+ this.sendPlayerPermissionLevel(player, permissionLevel, true); -+ } -+ public void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel, boolean recalculatePermissions) { -+ // Paper end - if (player.connection != null) { - byte b0; - -@@ -1144,8 +1149,10 @@ public abstract class PlayerList { - player.connection.send(new ClientboundEntityEventPacket(player, b0)); - } - -+ if (recalculatePermissions) { // Paper - player.getBukkitEntity().recalculatePermissions(); // CraftBukkit - this.server.getCommands().sendCommands(player); -+ } // Paper - } - - public boolean isWhiteListed(GameProfile profile) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 21f413b1e75c15fae888a4e6dccefbe822ad1c40..0528489348dc12c22bdb306a4b6c5c6a138f5347 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -606,6 +606,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().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); -+ } - // Paper end - - @Override diff --git a/patches/server/0571-Add-PaperRegistry.patch b/patches/server/0571-Add-PaperRegistry.patch deleted file mode 100644 index 613898a9f2..0000000000 --- a/patches/server/0571-Add-PaperRegistry.patch +++ /dev/null @@ -1,242 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Mar 2022 13:33:08 -0800 -Subject: [PATCH] Add PaperRegistry - -PaperRegistry is a server-backed impl of bukkit's Registry interface - -diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistry.java b/src/main/java/io/papermc/paper/registry/PaperRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7c265d27da034986be73921d35bf08ae250b42f3 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/PaperRegistry.java -@@ -0,0 +1,167 @@ -+package io.papermc.paper.registry; -+ -+import com.google.common.base.Preconditions; -+import com.google.common.base.Suppliers; -+import net.minecraft.core.Holder; -+import net.minecraft.core.Registry; -+import net.minecraft.core.RegistryAccess; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Keyed; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Optional; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.function.Consumer; -+import java.util.function.Supplier; -+ -+@DefaultQualifier(NonNull.class) -+public abstract class PaperRegistry implements org.bukkit.Registry { -+ -+ @SuppressWarnings("FieldMayBeFinal") // non-final for testing -+ private static Supplier REGISTRY_ACCESS = Suppliers.memoize(() -> MinecraftServer.getServer().registryAccess()); -+ private static final Map, PaperRegistry> INTERNAL_REGISTRIES = new HashMap<>(); -+ public static final Map, PaperRegistry> REGISTRIES = Collections.unmodifiableMap(INTERNAL_REGISTRIES); -+ private static final Map, PaperRegistry> REGISTRY_BY_API_CLASS = new HashMap<>(); -+ private static final Map>, PaperRegistry> REGISTRY_BY_RES_KEY = new HashMap<>(); -+ -+ private boolean registered; -+ private final RegistryKey registryKey; -+ private final Supplier> registry; -+ private final Map cache = new ConcurrentHashMap<>(); -+ private final Map> resourceKeyCache = new ConcurrentHashMap<>(); -+ -+ public PaperRegistry(RegistryKey registryKey) { -+ this.registryKey = registryKey; -+ this.registry = Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(this.registryKey.resourceKey())); -+ } -+ -+ @Override -+ public @Nullable API get(NamespacedKey key) { -+ return this.cache.computeIfAbsent(key, k -> { -+ final @Nullable MINECRAFT nms = this.registry.get().get(CraftNamespacedKey.toMinecraft(k)); -+ if (nms != null) { -+ return this.convertToApi(k, nms); -+ } -+ return null; -+ }); -+ } -+ -+ public abstract @Nullable API convertToApi(NamespacedKey key, MINECRAFT nms); -+ -+ public API convertToApiOrThrow(ResourceLocation resourceLocation, MINECRAFT nms) { -+ return Objects.requireNonNull(this.convertToApi(resourceLocation, nms), resourceLocation + " has a null api representation"); -+ } -+ -+ public @Nullable API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) { -+ return this.convertToApi(CraftNamespacedKey.fromMinecraft(resourceLocation), nms); -+ } -+ -+ public API convertToApiOrThrow(Holder nmsHolder) { -+ return Objects.requireNonNull(this.convertToApi(nmsHolder), nmsHolder + " has a null api representation"); -+ } -+ -+ public @Nullable API convertToApi(Holder nmsHolder) { -+ final Optional> key = nmsHolder.unwrapKey(); -+ if (nmsHolder.isBound() && key.isPresent()) { -+ return this.convertToApi(key.get().location(), nmsHolder.value()); -+ } else if (!nmsHolder.isBound() && key.isPresent()) { -+ return this.convertToApi(key.get().location(), this.registry.get().getOrThrow(key.get())); -+ } else if (nmsHolder.isBound() && key.isEmpty()) { -+ final @Nullable ResourceLocation loc = this.registry.get().getKey(nmsHolder.value()); -+ if (loc != null) { -+ return this.convertToApi(loc, nmsHolder.value()); -+ } -+ } -+ throw new IllegalStateException("Cannot convert " + nmsHolder + " to an API type in: " + this.registryKey); -+ } -+ -+ public void convertToApi(Iterable> holders, Consumer apiConsumer, boolean throwOnNull) { -+ for (Holder holder : holders) { -+ final @Nullable API api = this.convertToApi(holder); -+ if (api == null && throwOnNull) { -+ throw new NullPointerException(holder + " has a null api representation"); -+ } else if (api != null) { -+ apiConsumer.accept(api); -+ } -+ } -+ } -+ -+ public MINECRAFT getMinecraftValue(API apiValue) { -+ return this.registry.get().getOptional(CraftNamespacedKey.toMinecraft(apiValue.getKey())).orElseThrow(); -+ } -+ -+ public Holder getMinecraftHolder(API apiValue) { -+ return this.registry.get().getHolderOrThrow(this.resourceKeyCache.computeIfAbsent(apiValue.getKey(), key -> ResourceKey.create(this.registryKey.resourceKey(), CraftNamespacedKey.toMinecraft(key)))); -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return this.registry.get().keySet().stream().map(key -> this.get(CraftNamespacedKey.fromMinecraft(key))).iterator(); -+ } -+ -+ public void clearCache() { -+ this.cache.clear(); -+ } -+ -+ public void register() { -+ if (this.registered) { -+ throw new IllegalStateException("Already registered: " + this.registryKey.apiClass()); -+ } -+ INTERNAL_REGISTRIES.put(this.registryKey, this); -+ REGISTRY_BY_API_CLASS.put(this.registryKey.apiClass(), this); -+ REGISTRY_BY_RES_KEY.put(this.registryKey.resourceKey(), this); -+ this.registered = true; -+ } -+ -+ @Override -+ public boolean equals(@Nullable Object o) { -+ if (this == o) return true; -+ if (o == null || !PaperRegistry.class.isAssignableFrom(o.getClass())) return false; -+ PaperRegistry that = (PaperRegistry) o; -+ return this.registryKey.equals(that.registryKey); -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hash(this.registryKey); -+ } -+ -+ protected static Supplier> registryFor(ResourceKey> registryKey) { -+ return Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(registryKey)); -+ } -+ -+ public static void clearCaches() { -+ for (PaperRegistry registry : INTERNAL_REGISTRIES.values()) { -+ registry.clearCache(); -+ } -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static PaperRegistry getRegistry(Class classOfT) { -+ Preconditions.checkArgument(REGISTRY_BY_API_CLASS.containsKey(classOfT), "No registry for that type"); -+ return (PaperRegistry) REGISTRY_BY_API_CLASS.get(classOfT); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static PaperRegistry getRegistry(ResourceKey> resourceKey) { -+ Preconditions.checkArgument(REGISTRY_BY_RES_KEY.containsKey(resourceKey)); -+ return (PaperRegistry) REGISTRY_BY_RES_KEY.get(resourceKey); -+ } -+ -+ @SuppressWarnings("unchecked") -+ public static PaperRegistry getRegistry(RegistryKey registryKey) { -+ Preconditions.checkArgument(INTERNAL_REGISTRIES.containsKey(registryKey)); -+ return (PaperRegistry) INTERNAL_REGISTRIES.get(registryKey); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6f39e343147803e15e7681c993b8797a629702e7 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java -@@ -0,0 +1,8 @@ -+package io.papermc.paper.registry; -+ -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import org.bukkit.Keyed; -+ -+public record RegistryKey(Class apiClass, ResourceKey> resourceKey) { -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 5c3b5cb0ffc3920da82b525bbc7a8963c981c595..d60439d49de781b12af6fbe4ff89b7270f57cbeb 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2026,6 +2026,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Registry registryFor(Class classOfT) { -+ return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT); -+ } - // Paper end - - /** -diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java -index e73a9a957cd55bf838e301ed531295162f2cfb89..23435a5914a29ac4f87ad0f34ce83842422df49f 100644 ---- a/src/test/java/org/bukkit/support/AbstractTestingBase.java -+++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java -@@ -39,6 +39,15 @@ public abstract class AbstractTestingBase { - MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, Collections.singletonList(new VanillaPackResources(ServerPacksSource.BUILT_IN_METADATA, "minecraft"))); - // add tags and loot tables for unit tests - REGISTRY_CUSTOM = RegistryAccess.builtinCopy().freeze(); -+ // Paper start -+ try { -+ java.lang.reflect.Field field = io.papermc.paper.registry.PaperRegistry.class.getDeclaredField("REGISTRY_ACCESS"); -+ field.trySetAccessible(); -+ field.set(null, com.google.common.base.Suppliers.ofInstance(REGISTRY_CUSTOM)); -+ } catch (ReflectiveOperationException ex) { -+ throw new IllegalStateException("Could not reflectively set RegistryAccess in PaperRegistry", ex); -+ } -+ // Paper end - // Register vanilla pack - DATA_PACK = ReloadableServerResources.loadResources(resourceManager, REGISTRY_CUSTOM, Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join(); - // Bind tags diff --git a/patches/server/0571-Add-StructuresLocateEvent.patch b/patches/server/0571-Add-StructuresLocateEvent.patch new file mode 100644 index 0000000000..c8d1241689 --- /dev/null +++ b/patches/server/0571-Add-StructuresLocateEvent.patch @@ -0,0 +1,214 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Wed, 16 Sep 2020 01:12:29 -0700 +Subject: [PATCH] Add StructuresLocateEvent + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java +index 6f39e343147803e15e7681c993b8797a629702e7..87154ae69788249960bca376aafd90bf64d5bfe7 100644 +--- a/src/main/java/io/papermc/paper/registry/RegistryKey.java ++++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java +@@ -1,8 +1,13 @@ + package io.papermc.paper.registry; + ++import io.papermc.paper.world.structure.ConfiguredStructure; + import net.minecraft.core.Registry; + import net.minecraft.resources.ResourceKey; ++import net.minecraft.world.level.levelgen.structure.Structure; + import org.bukkit.Keyed; + + public record RegistryKey(Class apiClass, ResourceKey> resourceKey) { ++ ++ public static final RegistryKey CONFIGURED_STRUCTURE_REGISTRY = new RegistryKey<>(ConfiguredStructure.class, Registry.STRUCTURE_REGISTRY); ++ + } +diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java +new file mode 100644 +index 0000000000000000000000000000000000000000..423bf87ebda7ea266dc7b48cbfadbc8551180721 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java +@@ -0,0 +1,42 @@ ++package io.papermc.paper.world.structure; ++ ++import io.papermc.paper.registry.PaperRegistry; ++import io.papermc.paper.registry.RegistryKey; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.level.levelgen.structure.Structure; ++import org.bukkit.NamespacedKey; ++import org.bukkit.StructureType; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import java.util.Objects; ++import java.util.function.Supplier; ++ ++@DefaultQualifier(NonNull.class) ++public final class PaperConfiguredStructure { ++ ++ private PaperConfiguredStructure() { ++ } ++ ++ public static void init() { ++ new ConfiguredStructureRegistry().register(); ++ } ++ ++ static final class ConfiguredStructureRegistry extends PaperRegistry { ++ ++ private static final Supplier> STRUCTURE_FEATURE_REGISTRY = registryFor(Registry.STRUCTURE_REGISTRY); ++ ++ public ConfiguredStructureRegistry() { ++ super(RegistryKey.CONFIGURED_STRUCTURE_REGISTRY); ++ } ++ ++ @Override ++ public @Nullable ConfiguredStructure convertToApi(NamespacedKey key, Structure nms) { ++ final ResourceLocation structureTypeLoc = Objects.requireNonNull(Registry.STRUCTURE_TYPES.getKey(nms.type()), "unexpected structure type " + nms.type()); ++ final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath()); ++ return structureType == null ? null : new ConfiguredStructure(key, structureType); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +index b1eecc184bc9daf0a9c7db8da06e11bfb63d9d88..28f01f29796a8a8e6e6331da5525a4306d78230e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -295,6 +295,26 @@ public abstract class ChunkGenerator { + + @Nullable + public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { ++ // Paper start - StructureLocateEvent ++ final org.bukkit.World bukkitWorld = world.getWorld(); ++ final org.bukkit.Location origin = net.minecraft.server.MCUtil.toLocation(world, center); ++ final var paperRegistry = io.papermc.paper.registry.PaperRegistry.getRegistry(io.papermc.paper.registry.RegistryKey.CONFIGURED_STRUCTURE_REGISTRY); ++ final List configuredStructures = new ArrayList<>(); ++ paperRegistry.convertToApi(structures, configuredStructures::add, false); // gracefully handle missing api, use tests to check (or exclude) ++ if (!configuredStructures.isEmpty()) { ++ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, configuredStructures, radius, skipReferencedStructures); ++ if (!event.callEvent()) { ++ return null; ++ } ++ if (event.getResult() != null) { ++ return Pair.of(net.minecraft.server.MCUtil.toBlockPosition(event.getResult().position()), paperRegistry.getMinecraftHolder(event.getResult().configuredStructure())); ++ } ++ center = net.minecraft.server.MCUtil.toBlockPosition(event.getOrigin()); ++ radius = event.getRadius(); ++ skipReferencedStructures = event.shouldFindUnexplored(); ++ structures = HolderSet.direct(paperRegistry::getMinecraftHolder, event.getConfiguredStructures()); ++ } ++ // Paper end + Map>> map = new Object2ObjectArrayMap(); + Iterator iterator = structures.iterator(); + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java +index 737956b316c02e4ccdc6eef8de4a0a299d36b9ca..b8649eab719a1b71dc686386a8db756eefb9802e 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java +@@ -41,6 +41,7 @@ public abstract class Structure { + public static final Codec DIRECT_CODEC = Registry.STRUCTURE_TYPES.byNameCodec().dispatch(Structure::type, StructureType::codec); + public static final Codec> CODEC = RegistryFileCodec.create(Registry.STRUCTURE_REGISTRY, DIRECT_CODEC); + protected final Structure.StructureSettings settings; ++ static { io.papermc.paper.world.structure.PaperConfiguredStructure.init(); } // Paper + + public static RecordCodecBuilder settingsCodec(RecordCodecBuilder.Instance instance) { + return Structure.StructureSettings.CODEC.forGetter((feature) -> { +diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..61efebe1d363b34e2043ccc4c6e28bb714c2fa31 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java +@@ -0,0 +1,92 @@ ++package io.papermc.paper.world.structure; ++ ++import io.papermc.paper.registry.Reference; ++import net.minecraft.data.BuiltinRegistries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.Bootstrap; ++import net.minecraft.world.level.levelgen.structure.Structure; ++import net.minecraft.world.level.levelgen.structure.BuiltinStructures; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.AfterClass; ++import org.junit.BeforeClass; ++import org.junit.Test; ++ ++import java.io.PrintStream; ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.LinkedHashMap; ++import java.util.Map; ++import java.util.StringJoiner; ++ ++import static org.junit.Assert.assertEquals; ++import static org.junit.Assert.assertNotNull; ++import static org.junit.Assert.assertTrue; ++ ++public class ConfiguredStructureTest extends AbstractTestingBase { ++ ++ private static final Map BUILT_IN_STRUCTURES = new LinkedHashMap<>(); ++ private static final Map> DEFAULT_CONFIGURED_STRUCTURES = new LinkedHashMap<>(); ++ ++ private static PrintStream out; ++ ++ @BeforeClass ++ public static void collectStructures() throws ReflectiveOperationException { ++ out = System.out; ++ System.setOut(Bootstrap.STDOUT); ++ for (Field field : BuiltinStructures.class.getDeclaredFields()) { ++ if (field.getType().equals(ResourceKey.class) && Modifier.isStatic(field.getModifiers())) { ++ BUILT_IN_STRUCTURES.put(((ResourceKey) field.get(null)).location(), field.getName()); ++ } ++ } ++ for (Field field : ConfiguredStructure.class.getDeclaredFields()) { ++ if (field.getType().equals(Reference.class) && Modifier.isStatic(field.getModifiers())) { ++ final Reference ref = (Reference) field.get(null); ++ DEFAULT_CONFIGURED_STRUCTURES.put(ref.getKey(), ref); ++ } ++ } ++ } ++ ++ @Test ++ public void testMinecraftToApi() { ++ assertEquals("configured structure maps should be the same size", BUILT_IN_STRUCTURES.size(), BuiltinRegistries.STRUCTURES.size()); ++ ++ Map missing = new LinkedHashMap<>(); ++ for (Structure feature : BuiltinRegistries.STRUCTURES) { ++ final ResourceLocation key = BuiltinRegistries.STRUCTURES.getKey(feature); ++ assertNotNull("Missing built-in registry key", key); ++ if (key.equals(BuiltinStructures.ANCIENT_CITY.location())) { ++ continue; // TODO remove when upstream adds "jigsaw" StructureType ++ } ++ if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) { ++ missing.put(key, feature); ++ } ++ } ++ ++ assertTrue(printMissing(missing), missing.isEmpty()); ++ } ++ ++ @Test ++ public void testApiToMinecraft() { ++ for (NamespacedKey apiKey : DEFAULT_CONFIGURED_STRUCTURES.keySet()) { ++ assertTrue(apiKey + " does not have a minecraft counterpart", BuiltinRegistries.STRUCTURES.containsKey(CraftNamespacedKey.toMinecraft(apiKey))); ++ } ++ } ++ ++ private static String printMissing(Map missing) { ++ final StringJoiner joiner = new StringJoiner("\n", "Missing: \n", ""); ++ ++ missing.forEach((key, configuredFeature) -> { ++ joiner.add("public static final Reference " + BUILT_IN_STRUCTURES.get(key) + " = create(\"" + key.getPath() + "\");"); ++ }); ++ ++ return joiner.toString(); ++ } ++ ++ @AfterClass ++ public static void after() { ++ System.setOut(out); ++ } ++} diff --git a/patches/server/0572-Add-StructuresLocateEvent.patch b/patches/server/0572-Add-StructuresLocateEvent.patch deleted file mode 100644 index c8d1241689..0000000000 --- a/patches/server/0572-Add-StructuresLocateEvent.patch +++ /dev/null @@ -1,214 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dfsek -Date: Wed, 16 Sep 2020 01:12:29 -0700 -Subject: [PATCH] Add StructuresLocateEvent - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java -index 6f39e343147803e15e7681c993b8797a629702e7..87154ae69788249960bca376aafd90bf64d5bfe7 100644 ---- a/src/main/java/io/papermc/paper/registry/RegistryKey.java -+++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java -@@ -1,8 +1,13 @@ - package io.papermc.paper.registry; - -+import io.papermc.paper.world.structure.ConfiguredStructure; - import net.minecraft.core.Registry; - import net.minecraft.resources.ResourceKey; -+import net.minecraft.world.level.levelgen.structure.Structure; - import org.bukkit.Keyed; - - public record RegistryKey(Class apiClass, ResourceKey> resourceKey) { -+ -+ public static final RegistryKey CONFIGURED_STRUCTURE_REGISTRY = new RegistryKey<>(ConfiguredStructure.class, Registry.STRUCTURE_REGISTRY); -+ - } -diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java -new file mode 100644 -index 0000000000000000000000000000000000000000..423bf87ebda7ea266dc7b48cbfadbc8551180721 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java -@@ -0,0 +1,42 @@ -+package io.papermc.paper.world.structure; -+ -+import io.papermc.paper.registry.PaperRegistry; -+import io.papermc.paper.registry.RegistryKey; -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.level.levelgen.structure.Structure; -+import org.bukkit.NamespacedKey; -+import org.bukkit.StructureType; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import java.util.Objects; -+import java.util.function.Supplier; -+ -+@DefaultQualifier(NonNull.class) -+public final class PaperConfiguredStructure { -+ -+ private PaperConfiguredStructure() { -+ } -+ -+ public static void init() { -+ new ConfiguredStructureRegistry().register(); -+ } -+ -+ static final class ConfiguredStructureRegistry extends PaperRegistry { -+ -+ private static final Supplier> STRUCTURE_FEATURE_REGISTRY = registryFor(Registry.STRUCTURE_REGISTRY); -+ -+ public ConfiguredStructureRegistry() { -+ super(RegistryKey.CONFIGURED_STRUCTURE_REGISTRY); -+ } -+ -+ @Override -+ public @Nullable ConfiguredStructure convertToApi(NamespacedKey key, Structure nms) { -+ final ResourceLocation structureTypeLoc = Objects.requireNonNull(Registry.STRUCTURE_TYPES.getKey(nms.type()), "unexpected structure type " + nms.type()); -+ final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath()); -+ return structureType == null ? null : new ConfiguredStructure(key, structureType); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index b1eecc184bc9daf0a9c7db8da06e11bfb63d9d88..28f01f29796a8a8e6e6331da5525a4306d78230e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -295,6 +295,26 @@ public abstract class ChunkGenerator { - - @Nullable - public Pair> findNearestMapStructure(ServerLevel world, HolderSet structures, BlockPos center, int radius, boolean skipReferencedStructures) { -+ // Paper start - StructureLocateEvent -+ final org.bukkit.World bukkitWorld = world.getWorld(); -+ final org.bukkit.Location origin = net.minecraft.server.MCUtil.toLocation(world, center); -+ final var paperRegistry = io.papermc.paper.registry.PaperRegistry.getRegistry(io.papermc.paper.registry.RegistryKey.CONFIGURED_STRUCTURE_REGISTRY); -+ final List configuredStructures = new ArrayList<>(); -+ paperRegistry.convertToApi(structures, configuredStructures::add, false); // gracefully handle missing api, use tests to check (or exclude) -+ if (!configuredStructures.isEmpty()) { -+ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, configuredStructures, radius, skipReferencedStructures); -+ if (!event.callEvent()) { -+ return null; -+ } -+ if (event.getResult() != null) { -+ return Pair.of(net.minecraft.server.MCUtil.toBlockPosition(event.getResult().position()), paperRegistry.getMinecraftHolder(event.getResult().configuredStructure())); -+ } -+ center = net.minecraft.server.MCUtil.toBlockPosition(event.getOrigin()); -+ radius = event.getRadius(); -+ skipReferencedStructures = event.shouldFindUnexplored(); -+ structures = HolderSet.direct(paperRegistry::getMinecraftHolder, event.getConfiguredStructures()); -+ } -+ // Paper end - Map>> map = new Object2ObjectArrayMap(); - Iterator iterator = structures.iterator(); - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java b/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java -index 737956b316c02e4ccdc6eef8de4a0a299d36b9ca..b8649eab719a1b71dc686386a8db756eefb9802e 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/Structure.java -@@ -41,6 +41,7 @@ public abstract class Structure { - public static final Codec DIRECT_CODEC = Registry.STRUCTURE_TYPES.byNameCodec().dispatch(Structure::type, StructureType::codec); - public static final Codec> CODEC = RegistryFileCodec.create(Registry.STRUCTURE_REGISTRY, DIRECT_CODEC); - protected final Structure.StructureSettings settings; -+ static { io.papermc.paper.world.structure.PaperConfiguredStructure.init(); } // Paper - - public static RecordCodecBuilder settingsCodec(RecordCodecBuilder.Instance instance) { - return Structure.StructureSettings.CODEC.forGetter((feature) -> { -diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..61efebe1d363b34e2043ccc4c6e28bb714c2fa31 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java -@@ -0,0 +1,92 @@ -+package io.papermc.paper.world.structure; -+ -+import io.papermc.paper.registry.Reference; -+import net.minecraft.data.BuiltinRegistries; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.Bootstrap; -+import net.minecraft.world.level.levelgen.structure.Structure; -+import net.minecraft.world.level.levelgen.structure.BuiltinStructures; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.AfterClass; -+import org.junit.BeforeClass; -+import org.junit.Test; -+ -+import java.io.PrintStream; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.LinkedHashMap; -+import java.util.Map; -+import java.util.StringJoiner; -+ -+import static org.junit.Assert.assertEquals; -+import static org.junit.Assert.assertNotNull; -+import static org.junit.Assert.assertTrue; -+ -+public class ConfiguredStructureTest extends AbstractTestingBase { -+ -+ private static final Map BUILT_IN_STRUCTURES = new LinkedHashMap<>(); -+ private static final Map> DEFAULT_CONFIGURED_STRUCTURES = new LinkedHashMap<>(); -+ -+ private static PrintStream out; -+ -+ @BeforeClass -+ public static void collectStructures() throws ReflectiveOperationException { -+ out = System.out; -+ System.setOut(Bootstrap.STDOUT); -+ for (Field field : BuiltinStructures.class.getDeclaredFields()) { -+ if (field.getType().equals(ResourceKey.class) && Modifier.isStatic(field.getModifiers())) { -+ BUILT_IN_STRUCTURES.put(((ResourceKey) field.get(null)).location(), field.getName()); -+ } -+ } -+ for (Field field : ConfiguredStructure.class.getDeclaredFields()) { -+ if (field.getType().equals(Reference.class) && Modifier.isStatic(field.getModifiers())) { -+ final Reference ref = (Reference) field.get(null); -+ DEFAULT_CONFIGURED_STRUCTURES.put(ref.getKey(), ref); -+ } -+ } -+ } -+ -+ @Test -+ public void testMinecraftToApi() { -+ assertEquals("configured structure maps should be the same size", BUILT_IN_STRUCTURES.size(), BuiltinRegistries.STRUCTURES.size()); -+ -+ Map missing = new LinkedHashMap<>(); -+ for (Structure feature : BuiltinRegistries.STRUCTURES) { -+ final ResourceLocation key = BuiltinRegistries.STRUCTURES.getKey(feature); -+ assertNotNull("Missing built-in registry key", key); -+ if (key.equals(BuiltinStructures.ANCIENT_CITY.location())) { -+ continue; // TODO remove when upstream adds "jigsaw" StructureType -+ } -+ if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) { -+ missing.put(key, feature); -+ } -+ } -+ -+ assertTrue(printMissing(missing), missing.isEmpty()); -+ } -+ -+ @Test -+ public void testApiToMinecraft() { -+ for (NamespacedKey apiKey : DEFAULT_CONFIGURED_STRUCTURES.keySet()) { -+ assertTrue(apiKey + " does not have a minecraft counterpart", BuiltinRegistries.STRUCTURES.containsKey(CraftNamespacedKey.toMinecraft(apiKey))); -+ } -+ } -+ -+ private static String printMissing(Map missing) { -+ final StringJoiner joiner = new StringJoiner("\n", "Missing: \n", ""); -+ -+ missing.forEach((key, configuredFeature) -> { -+ joiner.add("public static final Reference " + BUILT_IN_STRUCTURES.get(key) + " = create(\"" + key.getPath() + "\");"); -+ }); -+ -+ return joiner.toString(); -+ } -+ -+ @AfterClass -+ public static void after() { -+ System.setOut(out); -+ } -+} diff --git a/patches/server/0572-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0572-Collision-option-for-requiring-a-player-participant.patch new file mode 100644 index 0000000000..e8dceea39c --- /dev/null +++ b/patches/server/0572-Collision-option-for-requiring-a-player-participant.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sat, 14 Nov 2020 16:48:37 +0100 +Subject: [PATCH] Collision option for requiring a player participant + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 9770cc15579d90480993135a5a54ad1ac1133df1..525e712c5715f1fa323fae94d4158c4e66068fe3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1729,6 +1729,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + public void push(Entity entity) { + if (!this.isPassengerOfSameVehicle(entity)) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper + double d0 = entity.getX() - this.getX(); + double d1 = entity.getZ() - this.getZ(); + double d2 = Mth.absMax(d0, d1); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +index 4984b2b3294e425247b595bcf36812728fb4cd16..3f31a3c17ecca6e93b794478129b95ecff4e1a9c 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +@@ -833,6 +833,7 @@ public abstract class AbstractMinecart extends Entity { + public void push(Entity entity) { + if (!this.level.isClientSide) { + if (!entity.noPhysics && !this.noPhysics) { ++ if (!this.level.paperConfig().collisions.allowVehicleCollisions && this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper + if (!this.hasPassenger(entity)) { + // CraftBukkit start + VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 8471c34d02ba5819580754f98ce8cc0b50a0b328..5641a7b5c5e3d93cddabd91703c6f001700c5869 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -243,6 +243,7 @@ public class Boat extends Entity { + + @Override + public void push(Entity entity) { ++ if (!this.level.paperConfig().collisions.allowVehicleCollisions && this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper + if (entity instanceof Boat) { + if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { + // CraftBukkit start diff --git a/patches/server/0573-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0573-Collision-option-for-requiring-a-player-participant.patch deleted file mode 100644 index 8859599601..0000000000 --- a/patches/server/0573-Collision-option-for-requiring-a-player-participant.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sat, 14 Nov 2020 16:48:37 +0100 -Subject: [PATCH] Collision option for requiring a player participant - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 81c70cf84618f4e987c68dba081317a658c6cd91..bfac12f0e89c4c7d48321ea608363518742304af 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1715,6 +1715,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - public void push(Entity entity) { - if (!this.isPassengerOfSameVehicle(entity)) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof ServerPlayer || this instanceof ServerPlayer)) return; // Paper - double d0 = entity.getX() - this.getX(); - double d1 = entity.getZ() - this.getZ(); - double d2 = Mth.absMax(d0, d1); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index 4984b2b3294e425247b595bcf36812728fb4cd16..3f31a3c17ecca6e93b794478129b95ecff4e1a9c 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -833,6 +833,7 @@ public abstract class AbstractMinecart extends Entity { - public void push(Entity entity) { - if (!this.level.isClientSide) { - if (!entity.noPhysics && !this.noPhysics) { -+ if (!this.level.paperConfig().collisions.allowVehicleCollisions && this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - if (!this.hasPassenger(entity)) { - // CraftBukkit start - VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity()); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 8471c34d02ba5819580754f98ce8cc0b50a0b328..5641a7b5c5e3d93cddabd91703c6f001700c5869 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -243,6 +243,7 @@ public class Boat extends Entity { - - @Override - public void push(Entity entity) { -+ if (!this.level.paperConfig().collisions.allowVehicleCollisions && this.level.paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - if (entity instanceof Boat) { - if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) { - // CraftBukkit start diff --git a/patches/server/0573-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch b/patches/server/0573-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch new file mode 100644 index 0000000000..1af8b9fb5e --- /dev/null +++ b/patches/server/0573-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sat, 16 Jan 2021 14:30:12 -0500 +Subject: [PATCH] Remove ProjectileHitEvent call when fireballs dead + +The duplicate ProjectileHitEvent in EntityFireball was removed. The +event was always called before the duplicate call. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +index d71dc286673fa7ed708be5bec4c5a6868874c090..a603d99430aedb3c242c2833e0cc8a31aba49205 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +@@ -97,7 +97,7 @@ public abstract class AbstractHurtingProjectile extends Projectile { + + // CraftBukkit start - Fire ProjectileHitEvent + if (this.isRemoved()) { +- CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); ++ // CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); // Paper - this is an undesired duplicate event + } + // CraftBukkit end + } diff --git a/patches/server/0574-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch b/patches/server/0574-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch deleted file mode 100644 index 1af8b9fb5e..0000000000 --- a/patches/server/0574-Remove-ProjectileHitEvent-call-when-fireballs-dead.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Sat, 16 Jan 2021 14:30:12 -0500 -Subject: [PATCH] Remove ProjectileHitEvent call when fireballs dead - -The duplicate ProjectileHitEvent in EntityFireball was removed. The -event was always called before the duplicate call. - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -index d71dc286673fa7ed708be5bec4c5a6868874c090..a603d99430aedb3c242c2833e0cc8a31aba49205 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -@@ -97,7 +97,7 @@ public abstract class AbstractHurtingProjectile extends Projectile { - - // CraftBukkit start - Fire ProjectileHitEvent - if (this.isRemoved()) { -- CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); -+ // CraftEventFactory.callProjectileHitEvent(this, movingobjectposition); // Paper - this is an undesired duplicate event - } - // CraftBukkit end - } diff --git a/patches/server/0574-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0574-Return-chat-component-with-empty-text-instead-of-thr.patch new file mode 100644 index 0000000000..b83b5915fd --- /dev/null +++ b/patches/server/0574-Return-chat-component-with-empty-text-instead-of-thr.patch @@ -0,0 +1,25 @@ +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/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +index 823ead0fd1942db0219968cd383439b324b16f6e..49b063655dfc09e30d446dbf07503fdda04a7e30 100644 +--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -88,7 +88,12 @@ public abstract class AbstractContainerMenu { + } + private Component title; + public final Component 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 Component.literal(""); ++ } ++ // Paper end + return this.title; + } + public final void setTitle(Component title) { diff --git a/patches/server/0575-Make-schedule-command-per-world.patch b/patches/server/0575-Make-schedule-command-per-world.patch new file mode 100644 index 0000000000..60f5d1fc66 --- /dev/null +++ b/patches/server/0575-Make-schedule-command-per-world.patch @@ -0,0 +1,28 @@ +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/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +index 2c6d5f22ef41601f863b11e2e626621d8047dd14..1eb93353766208f52e385ab445cfb754e42b7f3d 100644 +--- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java ++++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java +@@ -31,7 +31,7 @@ public class ScheduleCommand { + return Component.translatable("commands.schedule.cleared.failure", object); + }); + private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { +- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); ++ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper + }; + + public ScheduleCommand() {} +@@ -82,7 +82,7 @@ public class ScheduleCommand { + } + + private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException { +- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName); ++ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper + + if (i == 0) { + throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName); diff --git a/patches/server/0575-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0575-Return-chat-component-with-empty-text-instead-of-thr.patch deleted file mode 100644 index b83b5915fd..0000000000 --- a/patches/server/0575-Return-chat-component-with-empty-text-instead-of-thr.patch +++ /dev/null @@ -1,25 +0,0 @@ -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/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 823ead0fd1942db0219968cd383439b324b16f6e..49b063655dfc09e30d446dbf07503fdda04a7e30 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -88,7 +88,12 @@ public abstract class AbstractContainerMenu { - } - private Component title; - public final Component 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 Component.literal(""); -+ } -+ // Paper end - return this.title; - } - public final void setTitle(Component title) { diff --git a/patches/server/0576-Configurable-max-leash-distance.patch b/patches/server/0576-Configurable-max-leash-distance.patch new file mode 100644 index 0000000000..ba76f79b7f --- /dev/null +++ b/patches/server/0576-Configurable-max-leash-distance.patch @@ -0,0 +1,28 @@ +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/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 3db309e709cd72e3aae184ff2f8b1a7b98f2c7a8..15f56707a1f06f5c33f231a15a6c5f4b4a85cb4e 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -48,7 +48,7 @@ public abstract class PathfinderMob extends Mob { + float f = this.distanceTo(entity); + + if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { +- if (f > 10.0F) { ++ if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper + this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.dropLeash(true, true); + } +@@ -57,7 +57,7 @@ public abstract class PathfinderMob extends Mob { + } + + this.onLeashDistance(f); +- if (f > 10.0F) { ++ if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper + this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit + this.dropLeash(true, true); + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); diff --git a/patches/server/0576-Make-schedule-command-per-world.patch b/patches/server/0576-Make-schedule-command-per-world.patch deleted file mode 100644 index 60f5d1fc66..0000000000 --- a/patches/server/0576-Make-schedule-command-per-world.patch +++ /dev/null @@ -1,28 +0,0 @@ -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/ScheduleCommand.java b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -index 2c6d5f22ef41601f863b11e2e626621d8047dd14..1eb93353766208f52e385ab445cfb754e42b7f3d 100644 ---- a/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -+++ b/src/main/java/net/minecraft/server/commands/ScheduleCommand.java -@@ -31,7 +31,7 @@ public class ScheduleCommand { - return Component.translatable("commands.schedule.cleared.failure", object); - }); - private static final SuggestionProvider SUGGEST_SCHEDULE = (commandcontext, suggestionsbuilder) -> { -- return SharedSuggestionProvider.suggest((Iterable) ((CommandSourceStack) commandcontext.getSource()).getServer().getWorldData().overworldData().getScheduledEvents().getEventsIds(), suggestionsbuilder); -+ return SharedSuggestionProvider.suggest((Iterable) ((net.minecraft.commands.CommandSourceStack) commandcontext.getSource()).getLevel().serverLevelData.getScheduledEvents().getEventsIds(), suggestionsbuilder); // Paper - }; - - public ScheduleCommand() {} -@@ -82,7 +82,7 @@ public class ScheduleCommand { - } - - private static int remove(CommandSourceStack source, String eventName) throws CommandSyntaxException { -- int i = source.getServer().getWorldData().overworldData().getScheduledEvents().remove(eventName); -+ int i = source.getLevel().serverLevelData.getScheduledEvents().remove(eventName); // Paper - - if (i == 0) { - throw ScheduleCommand.ERROR_CANT_REMOVE.create(eventName); diff --git a/patches/server/0577-Configurable-max-leash-distance.patch b/patches/server/0577-Configurable-max-leash-distance.patch deleted file mode 100644 index ba76f79b7f..0000000000 --- a/patches/server/0577-Configurable-max-leash-distance.patch +++ /dev/null @@ -1,28 +0,0 @@ -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/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index 3db309e709cd72e3aae184ff2f8b1a7b98f2c7a8..15f56707a1f06f5c33f231a15a6c5f4b4a85cb4e 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -48,7 +48,7 @@ public abstract class PathfinderMob extends Mob { - float f = this.distanceTo(entity); - - if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { -- if (f > 10.0F) { -+ if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper - this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit - this.dropLeash(true, true); - } -@@ -57,7 +57,7 @@ public abstract class PathfinderMob extends Mob { - } - - this.onLeashDistance(f); -- if (f > 10.0F) { -+ if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper - this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit - this.dropLeash(true, true); - this.goalSelector.disableControlFlag(Goal.Flag.MOVE); diff --git a/patches/server/0577-Implement-BlockPreDispenseEvent.patch b/patches/server/0577-Implement-BlockPreDispenseEvent.patch new file mode 100644 index 0000000000..34982b77cc --- /dev/null +++ b/patches/server/0577-Implement-BlockPreDispenseEvent.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Madeline 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/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +index 85c5319837295bd2f85baebfe8d6660b267f1d5f..8f55d0753fa26924235c943595f0d1a06a933a6f 100644 +--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +@@ -93,6 +93,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); + + if (idispensebehavior != DispenseItemBehavior.NOOP) { ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here + DispenserBlock.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 7bafab016aeb3b1177b23f44696e7178f25d414a..2c45c71274e026936c0e1c91e1b0555f21a7a611 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1886,5 +1886,11 @@ public class CraftEventFactory { + io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); + return event.callEvent(); + } ++ ++ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { ++ org.bukkit.block.Block block = serverLevel.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); ++ return event.callEvent(); ++ } + // Paper end + } diff --git a/patches/server/0578-Added-firing-of-PlayerChangeBeaconEffectEvent.patch b/patches/server/0578-Added-firing-of-PlayerChangeBeaconEffectEvent.patch new file mode 100644 index 0000000000..2f90dea41c --- /dev/null +++ b/patches/server/0578-Added-firing-of-PlayerChangeBeaconEffectEvent.patch @@ -0,0 +1,40 @@ +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/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +index 995a43ddcce8afb64404ef85641badd8035c6e3c..64e1571fab5f07cfe1b5203b36754f536b303f27 100644 +--- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java +@@ -158,13 +158,27 @@ public class BeaconMenu extends AbstractContainerMenu { + public MobEffect getSecondaryEffect() { + return MobEffect.byId(this.beaconData.get(2)); + } ++ // Paper start ++ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional effect) { ++ return effect.flatMap(net.minecraft.core.Registry.MOB_EFFECT::getResourceKey).map(key -> { ++ return org.bukkit.potion.PotionEffectType.getByKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(key.location())); ++ }).orElse(null); ++ } ++ // Paper end + + public void updateEffects(Optional primary, Optional secondary) { + if (this.paymentSlot.hasItem()) { +- this.beaconData.set(1, (Integer) primary.map(MobEffect::getId).orElse(-1)); +- this.beaconData.set(2, (Integer) secondary.map(MobEffect::getId).orElse(-1)); ++ // Paper start ++ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); ++ if (event.callEvent()) { ++ this.beaconData.set(1, event.getPrimary() == null ? -1 : event.getPrimary().getId()); ++ this.beaconData.set(2, event.getSecondary() == null ? -1 : event.getSecondary().getId()); ++ if (event.willConsumeItem()) { ++ // Paper end + this.paymentSlot.remove(1); ++ } + this.access.execute(Level::blockEntityChanged); ++ } // Paper end + } + + } diff --git a/patches/server/0578-Implement-BlockPreDispenseEvent.patch b/patches/server/0578-Implement-BlockPreDispenseEvent.patch deleted file mode 100644 index 34982b77cc..0000000000 --- a/patches/server/0578-Implement-BlockPreDispenseEvent.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Madeline Miller -Date: Sun, 17 Jan 2021 13:16:09 +1000 -Subject: [PATCH] Implement BlockPreDispenseEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index 85c5319837295bd2f85baebfe8d6660b267f1d5f..8f55d0753fa26924235c943595f0d1a06a933a6f 100644 ---- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -93,6 +93,7 @@ public class DispenserBlock extends BaseEntityBlock { - DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack); - - if (idispensebehavior != DispenseItemBehavior.NOOP) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here - DispenserBlock.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 7bafab016aeb3b1177b23f44696e7178f25d414a..2c45c71274e026936c0e1c91e1b0555f21a7a611 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1886,5 +1886,11 @@ public class CraftEventFactory { - io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block); - return event.callEvent(); - } -+ -+ public static boolean handleBlockPreDispenseEvent(ServerLevel serverLevel, BlockPos pos, ItemStack itemStack, int slot) { -+ org.bukkit.block.Block block = serverLevel.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -+ io.papermc.paper.event.block.BlockPreDispenseEvent event = new io.papermc.paper.event.block.BlockPreDispenseEvent(block, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), slot); -+ return event.callEvent(); -+ } - // Paper end - } diff --git a/patches/server/0579-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0579-Add-toggle-for-always-placing-the-dragon-egg.patch new file mode 100644 index 0000000000..caffaecf90 --- /dev/null +++ b/patches/server/0579-Add-toggle-for-always-placing-the-dragon-egg.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 26 Nov 2020 11:47:24 +0000 +Subject: [PATCH] Add toggle for always placing the dragon egg + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 3e1e8b42f963fab17e416760b93e7c1c0a9a7f45..42dc41311e9bc716dcd88f90cbbf533884bf92a3 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -368,7 +368,7 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); +- if (!this.previouslyKilled) { ++ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg + this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); + } + diff --git a/patches/server/0579-Added-firing-of-PlayerChangeBeaconEffectEvent.patch b/patches/server/0579-Added-firing-of-PlayerChangeBeaconEffectEvent.patch deleted file mode 100644 index 2f90dea41c..0000000000 --- a/patches/server/0579-Added-firing-of-PlayerChangeBeaconEffectEvent.patch +++ /dev/null @@ -1,40 +0,0 @@ -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/BeaconMenu.java b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -index 995a43ddcce8afb64404ef85641badd8035c6e3c..64e1571fab5f07cfe1b5203b36754f536b303f27 100644 ---- a/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/BeaconMenu.java -@@ -158,13 +158,27 @@ public class BeaconMenu extends AbstractContainerMenu { - public MobEffect getSecondaryEffect() { - return MobEffect.byId(this.beaconData.get(2)); - } -+ // Paper start -+ private static @Nullable org.bukkit.potion.PotionEffectType convert(Optional effect) { -+ return effect.flatMap(net.minecraft.core.Registry.MOB_EFFECT::getResourceKey).map(key -> { -+ return org.bukkit.potion.PotionEffectType.getByKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(key.location())); -+ }).orElse(null); -+ } -+ // Paper end - - public void updateEffects(Optional primary, Optional secondary) { - if (this.paymentSlot.hasItem()) { -- this.beaconData.set(1, (Integer) primary.map(MobEffect::getId).orElse(-1)); -- this.beaconData.set(2, (Integer) secondary.map(MobEffect::getId).orElse(-1)); -+ // Paper start -+ io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent event = new io.papermc.paper.event.player.PlayerChangeBeaconEffectEvent((org.bukkit.entity.Player) this.player.player.getBukkitEntity(), convert(primary), convert(secondary), this.access.getLocation().getBlock()); -+ if (event.callEvent()) { -+ this.beaconData.set(1, event.getPrimary() == null ? -1 : event.getPrimary().getId()); -+ this.beaconData.set(2, event.getSecondary() == null ? -1 : event.getSecondary().getId()); -+ if (event.willConsumeItem()) { -+ // Paper end - this.paymentSlot.remove(1); -+ } - this.access.execute(Level::blockEntityChanged); -+ } // Paper end - } - - } diff --git a/patches/server/0580-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0580-Add-toggle-for-always-placing-the-dragon-egg.patch deleted file mode 100644 index caffaecf90..0000000000 --- a/patches/server/0580-Add-toggle-for-always-placing-the-dragon-egg.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 26 Nov 2020 11:47:24 +0000 -Subject: [PATCH] Add toggle for always placing the dragon egg - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 3e1e8b42f963fab17e416760b93e7c1c0a9a7f45..42dc41311e9bc716dcd88f90cbbf533884bf92a3 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -368,7 +368,7 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -- if (!this.previouslyKilled) { -+ if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg - this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); - } - diff --git a/patches/server/0580-Added-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0580-Added-PlayerStonecutterRecipeSelectEvent.patch new file mode 100644 index 0000000000..1c6497d193 --- /dev/null +++ b/patches/server/0580-Added-PlayerStonecutterRecipeSelectEvent.patch @@ -0,0 +1,51 @@ +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/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index b47dc7671fab2117b989d647d7e8e36d12af5f76..d4f71422b25f70abfe50481d6071abea6ad147c0 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -62,7 +62,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + + public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { + super(MenuType.STONECUTTER, syncId); +- this.selectedRecipeIndex = DataSlot.standalone(); ++ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - allow replication + this.recipes = Lists.newArrayList(); + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { +@@ -156,7 +156,29 @@ public class StonecutterMenu extends AbstractContainerMenu { + @Override + public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { + if (this.isValidRecipeIndex(id)) { +- this.selectedRecipeIndex.set(id); ++ // Paper start ++ int recipeIndex = id; ++ this.selectedRecipeIndex.set(recipeIndex); ++ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed ++ if (this.isValidRecipeIndex(id)) { ++ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), (org.bukkit.inventory.StonecutterInventory) getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) this.getRecipes().get(id).toBukkitRecipe()); ++ if (!event.callEvent()) { ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ return false; ++ } ++ int newRecipeIndex; ++ if (!this.getRecipes().get(recipeIndex).getId().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).getId().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { ++ recipeIndex = newRecipeIndex; ++ break; ++ } ++ } ++ } ++ } ++ ((Player) player.getBukkitEntity()).updateInventory(); ++ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it ++ // Paper end + this.setupResultSlot(); + } + diff --git a/patches/server/0581-Add-dropLeash-variable-to-EntityUnleashEvent.patch b/patches/server/0581-Add-dropLeash-variable-to-EntityUnleashEvent.patch new file mode 100644 index 0000000000..1ab6c748f8 --- /dev/null +++ b/patches/server/0581-Add-dropLeash-variable-to-EntityUnleashEvent.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 29 Jan 2021 15:13:11 +0100 +Subject: [PATCH] Add dropLeash variable to EntityUnleashEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index d7b137a84deea68c75ee0b3c99b089b8dff25947..e08d69af81f4ca0535be522eef4792e4127f454c 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1248,12 +1248,15 @@ public abstract class Mob extends LivingEntity { + return InteractionResult.PASS; + } else if (this.getLeashHolder() == player) { + // CraftBukkit start - fire PlayerUnleashEntityEvent +- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player).isCancelled()) { ++ // Paper start - drop leash variable ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, !player.getAbilities().instabuild); ++ if (event.isCancelled()) { ++ // Paper end + ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder())); + return InteractionResult.PASS; + } + // CraftBukkit end +- this.dropLeash(true, !player.getAbilities().instabuild); ++ this.dropLeash(true, event.isDropLeash()); // Paper - drop leash variable + return InteractionResult.sidedSuccess(this.level.isClientSide); + } else { + InteractionResult enuminteractionresult = this.checkAndHandleImportantInteractions(player, hand); +@@ -1416,8 +1419,11 @@ public abstract class Mob extends LivingEntity { + + if (this.leashHolder != null) { + if (!this.isAlive() || !this.leashHolder.isAlive()) { +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + } +@@ -1480,8 +1486,11 @@ public abstract class Mob extends LivingEntity { + boolean flag1 = super.startRiding(entity, force); + + if (flag1 && this.isLeashed()) { +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + return flag1; +@@ -1659,8 +1668,11 @@ public abstract class Mob extends LivingEntity { + @Override + protected void removeAfterChangingDimensions() { + super.removeAfterChangingDimensions(); +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit +- this.dropLeash(true, false); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + this.getAllSlots().forEach((itemstack) -> { + if (!itemstack.isEmpty()) itemstack.setCount(0); // CraftBukkit + }); +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index 15f56707a1f06f5c33f231a15a6c5f4b4a85cb4e..f5cb3576aa2560c86f4a1df9d51d8ecde4e98905 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -49,8 +49,11 @@ public abstract class PathfinderMob extends Mob { + + if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { + if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + } + + return; +@@ -58,8 +61,11 @@ public abstract class PathfinderMob extends Mob { + + this.onLeashDistance(f); + if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper +- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit +- this.dropLeash(true, true); ++ // Paper start - drop leash variable ++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); ++ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ this.dropLeash(true, event.isDropLeash()); ++ // Paper end + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); + } else if (f > 6.0F) { + double d0 = (entity.getX() - this.getX()) / (double) f; +diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +index 7eb315aac7737cf443c693147c2cfd871f201724..03de59302041b0bc13922ec129501417804df915 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java +@@ -122,11 +122,14 @@ public class LeashFenceKnotEntity extends HangingEntity { + entityinsentient = (Mob) iterator.next(); + if (entityinsentient.isLeashed() && entityinsentient.getLeashHolder() == this) { + // CraftBukkit start +- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player).isCancelled()) { ++ // Paper start - drop leash variable ++ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player, !player.getAbilities().instabuild); ++ if (event.isCancelled()) { ++ // Paper end + die = false; + continue; + } +- entityinsentient.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean ++ entityinsentient.dropLeash(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 2c45c71274e026936c0e1c91e1b0555f21a7a611..9ccfe52a61b72addfa675af797ea4bafbff30bdb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -1527,8 +1527,10 @@ public class CraftEventFactory { + return itemInHand; + } + +- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player) { +- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity()); ++ // Paper start - drop leash variable ++ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, boolean dropLeash) { ++ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), dropLeash); ++ // Paper end + entity.level.getCraftServer().getPluginManager().callEvent(event); + return event; + } diff --git a/patches/server/0581-Added-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0581-Added-PlayerStonecutterRecipeSelectEvent.patch deleted file mode 100644 index 1c6497d193..0000000000 --- a/patches/server/0581-Added-PlayerStonecutterRecipeSelectEvent.patch +++ /dev/null @@ -1,51 +0,0 @@ -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/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -index b47dc7671fab2117b989d647d7e8e36d12af5f76..d4f71422b25f70abfe50481d6071abea6ad147c0 100644 ---- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java -@@ -62,7 +62,7 @@ public class StonecutterMenu extends AbstractContainerMenu { - - public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { - super(MenuType.STONECUTTER, syncId); -- this.selectedRecipeIndex = DataSlot.standalone(); -+ this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - allow replication - this.recipes = Lists.newArrayList(); - this.input = ItemStack.EMPTY; - this.slotUpdateListener = () -> { -@@ -156,7 +156,29 @@ public class StonecutterMenu extends AbstractContainerMenu { - @Override - public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { - if (this.isValidRecipeIndex(id)) { -- this.selectedRecipeIndex.set(id); -+ // Paper start -+ int recipeIndex = id; -+ this.selectedRecipeIndex.set(recipeIndex); -+ this.selectedRecipeIndex.checkAndClearUpdateFlag(); // mark as changed -+ if (this.isValidRecipeIndex(id)) { -+ io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent event = new io.papermc.paper.event.player.PlayerStonecutterRecipeSelectEvent((Player) player.getBukkitEntity(), (org.bukkit.inventory.StonecutterInventory) getBukkitView().getTopInventory(), (org.bukkit.inventory.StonecuttingRecipe) this.getRecipes().get(id).toBukkitRecipe()); -+ if (!event.callEvent()) { -+ ((Player) player.getBukkitEntity()).updateInventory(); -+ return false; -+ } -+ int newRecipeIndex; -+ if (!this.getRecipes().get(recipeIndex).getId().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).getId().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(event.getStonecuttingRecipe().getKey()))) { -+ recipeIndex = newRecipeIndex; -+ break; -+ } -+ } -+ } -+ } -+ ((Player) player.getBukkitEntity()).updateInventory(); -+ this.selectedRecipeIndex.set(recipeIndex); // set new index, so that listeners can read it -+ // Paper end - this.setupResultSlot(); - } - diff --git a/patches/server/0582-Add-dropLeash-variable-to-EntityUnleashEvent.patch b/patches/server/0582-Add-dropLeash-variable-to-EntityUnleashEvent.patch deleted file mode 100644 index 71ca7beca1..0000000000 --- a/patches/server/0582-Add-dropLeash-variable-to-EntityUnleashEvent.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 29 Jan 2021 15:13:11 +0100 -Subject: [PATCH] Add dropLeash variable to EntityUnleashEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 1a2e5e8c32a2fabe3b92ded6c630b8258b57bc0f..837c0a8bd15388bdb60d6950a437640459adde3c 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1242,12 +1242,15 @@ public abstract class Mob extends LivingEntity { - return InteractionResult.PASS; - } else if (this.getLeashHolder() == player) { - // CraftBukkit start - fire PlayerUnleashEntityEvent -- if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player).isCancelled()) { -+ // Paper start - drop leash variable -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, !player.getAbilities().instabuild); -+ if (event.isCancelled()) { -+ // Paper end - ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder())); - return InteractionResult.PASS; - } - // CraftBukkit end -- this.dropLeash(true, !player.getAbilities().instabuild); -+ this.dropLeash(true, event.isDropLeash()); // Paper - drop leash variable - return InteractionResult.sidedSuccess(this.level.isClientSide); - } else { - InteractionResult enuminteractionresult = this.checkAndHandleImportantInteractions(player, hand); -@@ -1410,8 +1413,11 @@ public abstract class Mob extends LivingEntity { - - if (this.leashHolder != null) { - if (!this.isAlive() || !this.leashHolder.isAlive()) { -- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - drop leash variable -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE, true); -+ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - } - - } -@@ -1474,8 +1480,11 @@ public abstract class Mob extends LivingEntity { - boolean flag1 = super.startRiding(entity, force); - - if (flag1 && this.isLeashed()) { -- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - drop leash variable -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); -+ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - } - - return flag1; -@@ -1653,8 +1662,11 @@ public abstract class Mob extends LivingEntity { - @Override - protected void removeAfterChangingDimensions() { - super.removeAfterChangingDimensions(); -- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit -- this.dropLeash(true, false); -+ // Paper start - drop leash variable -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, false); -+ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - this.getAllSlots().forEach((itemstack) -> { - if (!itemstack.isEmpty()) itemstack.setCount(0); // CraftBukkit - }); -diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index 15f56707a1f06f5c33f231a15a6c5f4b4a85cb4e..f5cb3576aa2560c86f4a1df9d51d8ecde4e98905 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -49,8 +49,11 @@ public abstract class PathfinderMob extends Mob { - - if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) { - if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper -- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - drop leash variable -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - } - - return; -@@ -58,8 +61,11 @@ public abstract class PathfinderMob extends Mob { - - this.onLeashDistance(f); - if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper -- this.level.getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit -- this.dropLeash(true, true); -+ // Paper start - drop leash variable -+ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -+ this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ this.dropLeash(true, event.isDropLeash()); -+ // Paper end - this.goalSelector.disableControlFlag(Goal.Flag.MOVE); - } else if (f > 6.0F) { - double d0 = (entity.getX() - this.getX()) / (double) f; -diff --git a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -index 7eb315aac7737cf443c693147c2cfd871f201724..03de59302041b0bc13922ec129501417804df915 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java -@@ -122,11 +122,14 @@ public class LeashFenceKnotEntity extends HangingEntity { - entityinsentient = (Mob) iterator.next(); - if (entityinsentient.isLeashed() && entityinsentient.getLeashHolder() == this) { - // CraftBukkit start -- if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player).isCancelled()) { -+ // Paper start - drop leash variable -+ org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient, player, !player.getAbilities().instabuild); -+ if (event.isCancelled()) { -+ // Paper end - die = false; - continue; - } -- entityinsentient.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean -+ entityinsentient.dropLeash(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 2c45c71274e026936c0e1c91e1b0555f21a7a611..9ccfe52a61b72addfa675af797ea4bafbff30bdb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -1527,8 +1527,10 @@ public class CraftEventFactory { - return itemInHand; - } - -- public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player) { -- PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity()); -+ // Paper start - drop leash variable -+ public static PlayerUnleashEntityEvent callPlayerUnleashEntityEvent(Mob entity, net.minecraft.world.entity.player.Player player, boolean dropLeash) { -+ PlayerUnleashEntityEvent event = new PlayerUnleashEntityEvent(entity.getBukkitEntity(), (Player) player.getBukkitEntity(), dropLeash); -+ // Paper end - entity.level.getCraftServer().getPluginManager().callEvent(event); - return event; - } diff --git a/patches/server/0582-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0582-Reset-shield-blocking-on-dimension-change.patch new file mode 100644 index 0000000000..d418f0bca8 --- /dev/null +++ b/patches/server/0582-Reset-shield-blocking-on-dimension-change.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Sun, 24 Jan 2021 08:55:19 -0800 +Subject: [PATCH] Reset shield blocking on dimension change + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index a802529b6f1adfd358295811c4e329e6fe82009b..d8ba103fd52c3c540fe386c9ff8264fcb3dbc136 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1181,6 +1181,11 @@ public class ServerPlayer extends Player { + this.level.getCraftServer().getPluginManager().callEvent(changeEvent); + // CraftBukkit end + } ++ // Paper start ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end + + return this; + } diff --git a/patches/server/0583-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0583-Reset-shield-blocking-on-dimension-change.patch deleted file mode 100644 index d418f0bca8..0000000000 --- a/patches/server/0583-Reset-shield-blocking-on-dimension-change.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yive -Date: Sun, 24 Jan 2021 08:55:19 -0800 -Subject: [PATCH] Reset shield blocking on dimension change - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index a802529b6f1adfd358295811c4e329e6fe82009b..d8ba103fd52c3c540fe386c9ff8264fcb3dbc136 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1181,6 +1181,11 @@ public class ServerPlayer extends Player { - this.level.getCraftServer().getPluginManager().callEvent(changeEvent); - // CraftBukkit end - } -+ // Paper start -+ if (this.isBlocking()) { -+ this.stopUsingItem(); -+ } -+ // Paper end - - return this; - } diff --git a/patches/server/0583-add-DragonEggFormEvent.patch b/patches/server/0583-add-DragonEggFormEvent.patch new file mode 100644 index 0000000000..40273bbb47 --- /dev/null +++ b/patches/server/0583-add-DragonEggFormEvent.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Trigary +Date: Mon, 25 Jan 2021 14:53:57 +0100 +Subject: [PATCH] add DragonEggFormEvent + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 42dc41311e9bc716dcd88f90cbbf533884bf92a3..99b175625c79fe5c4d944810e3fe11be5eed997f 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -368,9 +368,23 @@ public class EndDragonFight { + this.dragonEvent.setVisible(false); + this.spawnExitPortal(true); + this.spawnNewGateway(); ++ // Paper start - DragonEggFormEvent ++ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION); ++ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); ++ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); ++ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, ++ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); ++ // Paper end - DragonEggFormEvent + if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg +- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); ++ // Paper start - DragonEggFormEvent ++ //this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); ++ } else { ++ eggEvent.setCancelled(true); ++ } ++ if (eggEvent.callEvent()) { ++ eggEvent.getNewState().update(true); + } ++ // Paper end - DragonEggFormEvent + + this.previouslyKilled = true; + this.dragonKilled = true; diff --git a/patches/server/0584-EntityMoveEvent.patch b/patches/server/0584-EntityMoveEvent.patch new file mode 100644 index 0000000000..921e7daaf0 --- /dev/null +++ b/patches/server/0584-EntityMoveEvent.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Tue, 11 Feb 2020 21:56:48 -0600 +Subject: [PATCH] EntityMoveEvent + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d60439d49de781b12af6fbe4ff89b7270f57cbeb..abd1935ebc12f963b563023eb5279ad16ed1d8df 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1495,6 +1495,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper ++ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.profiler.push(() -> { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3f4e3e57999245a83263e88e221723e72a11b31e..a1b0256c8faceae89e1eaf5e26c8b588085fa760 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -217,6 +217,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final LevelStorageSource.LevelStorageAccess convertable; + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper ++ public boolean hasEntityMoveEvent = false; // Paper + public 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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d6ad8cfb509920c448fc51ec02e867b1552730df..f774d25eaaa1b7966b16251619da80a2d5c9228c 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3303,6 +3303,20 @@ public abstract class LivingEntity extends Entity { + + this.pushEntities(); + this.level.getProfiler().pop(); ++ // Paper start ++ if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { ++ if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { ++ Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); ++ Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); ++ if (!event.callEvent()) { ++ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); ++ } else if (!to.equals(event.getTo())) { ++ absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); ++ } ++ } ++ } ++ // Paper end + if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { + this.hurt(DamageSource.DROWN, 1.0F); + } diff --git a/patches/server/0584-add-DragonEggFormEvent.patch b/patches/server/0584-add-DragonEggFormEvent.patch deleted file mode 100644 index 40273bbb47..0000000000 --- a/patches/server/0584-add-DragonEggFormEvent.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Mon, 25 Jan 2021 14:53:57 +0100 -Subject: [PATCH] add DragonEggFormEvent - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 42dc41311e9bc716dcd88f90cbbf533884bf92a3..99b175625c79fe5c4d944810e3fe11be5eed997f 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -368,9 +368,23 @@ public class EndDragonFight { - this.dragonEvent.setVisible(false); - this.spawnExitPortal(true); - this.spawnNewGateway(); -+ // Paper start - DragonEggFormEvent -+ BlockPos eggPosition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION); -+ org.bukkit.craftbukkit.block.CraftBlockState eggState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.level, eggPosition); -+ eggState.setData(Blocks.DRAGON_EGG.defaultBlockState()); -+ io.papermc.paper.event.block.DragonEggFormEvent eggEvent = new io.papermc.paper.event.block.DragonEggFormEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this.level, eggPosition), eggState, -+ new org.bukkit.craftbukkit.boss.CraftDragonBattle(this)); -+ // Paper end - DragonEggFormEvent - if (this.level.paperConfig().entities.behavior.enderDragonsDeathAlwaysPlacesDragonEgg || !this.previouslyKilled) { // Paper - always place dragon egg -- this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); -+ // Paper start - DragonEggFormEvent -+ //this.level.setBlockAndUpdate(this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION), Blocks.DRAGON_EGG.defaultBlockState()); -+ } else { -+ eggEvent.setCancelled(true); -+ } -+ if (eggEvent.callEvent()) { -+ eggEvent.getNewState().update(true); - } -+ // Paper end - DragonEggFormEvent - - this.previouslyKilled = true; - this.dragonKilled = true; diff --git a/patches/server/0585-EntityMoveEvent.patch b/patches/server/0585-EntityMoveEvent.patch deleted file mode 100644 index 739f60de98..0000000000 --- a/patches/server/0585-EntityMoveEvent.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Tue, 11 Feb 2020 21:56:48 -0600 -Subject: [PATCH] EntityMoveEvent - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d60439d49de781b12af6fbe4ff89b7270f57cbeb..abd1935ebc12f963b563023eb5279ad16ed1d8df 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1495,6 +1495,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper -+ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - - this.profiler.push(() -> { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 3f4e3e57999245a83263e88e221723e72a11b31e..a1b0256c8faceae89e1eaf5e26c8b588085fa760 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -217,6 +217,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final LevelStorageSource.LevelStorageAccess convertable; - public final UUID uuid; - public boolean hasPhysicsEvent = true; // Paper -+ public boolean hasEntityMoveEvent = false; // Paper - public 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/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 502d54845e1bcf538149a51be995320c6df10acb..ede6e560cf0fe7dadab481f0d27a5edc4218d8bd 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3281,6 +3281,20 @@ public abstract class LivingEntity extends Entity { - - this.pushEntities(); - this.level.getProfiler().pop(); -+ // Paper start -+ if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); -+ Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); -+ if (!event.callEvent()) { -+ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); -+ } else if (!to.equals(event.getTo())) { -+ absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); -+ } -+ } -+ } -+ // Paper end - if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { - this.hurt(DamageSource.DROWN, 1.0F); - } diff --git a/patches/server/0585-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0585-added-option-to-disable-pathfinding-updates-on-block.patch new file mode 100644 index 0000000000..24045de2fb --- /dev/null +++ b/patches/server/0585-added-option-to-disable-pathfinding-updates-on-block.patch @@ -0,0 +1,26 @@ +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/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a1b0256c8faceae89e1eaf5e26c8b588085fa760..7f121ce977fd5779032450443c94634bc919009d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1494,6 +1494,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + this.getChunkSource().blockChanged(pos); ++ if(this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape voxelshape = oldState.getCollisionShape(this, pos); + VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); + +@@ -1535,6 +1536,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + } ++ } // Paper + } + + @Override diff --git a/patches/server/0586-Inline-shift-direction-fields.patch b/patches/server/0586-Inline-shift-direction-fields.patch new file mode 100644 index 0000000000..d4353368a2 --- /dev/null +++ b/patches/server/0586-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/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index d23c679485641ee1b21b7c457dccdec8f4baa792..a3bbebcdaea9e0dfddd9825272f84fc76cd13e89 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -61,6 +61,11 @@ public enum Direction implements StringRepresentable { + }, (direction1, direction2) -> { + throw new IllegalArgumentException("Duplicate keys"); + }, Long2ObjectOpenHashMap::new)); ++ // Paper start ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end + + private Direction(int id, int idOpposite, int idHorizontal, String name, Direction.AxisDirection direction, Direction.Axis axis, Vec3i vector) { + this.data3d = id; +@@ -70,6 +75,11 @@ public enum Direction implements StringRepresentable { + this.axis = axis; + this.axisDirection = direction; + this.normal = vector; ++ // Paper start ++ this.adjX = vector.getX(); ++ this.adjY = vector.getY(); ++ this.adjZ = vector.getZ(); ++ // Paper end + } + + public static Direction[] orderedByNearest(Entity entity) { +@@ -362,15 +372,15 @@ public enum Direction implements StringRepresentable { + } + + public int getStepX() { +- return this.normal.getX(); ++ return this.adjX; // Paper + } + + public int getStepY() { +- return this.normal.getY(); ++ return this.adjY; // Paper + } + + public int getStepZ() { +- return this.normal.getZ(); ++ return this.adjZ; // Paper + } + + public Vector3f step() { diff --git a/patches/server/0586-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0586-added-option-to-disable-pathfinding-updates-on-block.patch deleted file mode 100644 index 24045de2fb..0000000000 --- a/patches/server/0586-added-option-to-disable-pathfinding-updates-on-block.patch +++ /dev/null @@ -1,26 +0,0 @@ -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/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a1b0256c8faceae89e1eaf5e26c8b588085fa760..7f121ce977fd5779032450443c94634bc919009d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1494,6 +1494,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - this.getChunkSource().blockChanged(pos); -+ if(this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates - VoxelShape voxelshape = oldState.getCollisionShape(this, pos); - VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); - -@@ -1535,6 +1536,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - } -+ } // Paper - } - - @Override diff --git a/patches/server/0587-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0587-Allow-adding-items-to-BlockDropItemEvent.patch new file mode 100644 index 0000000000..e71e9cff71 --- /dev/null +++ b/patches/server/0587-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 9ccfe52a61b72addfa675af797ea4bafbff30bdb..82d8a8c2199673315c7b52e694f798cc59c5f96c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -396,13 +396,30 @@ public class CraftEventFactory { + } + + public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { +- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); ++ // Paper start ++ List list = new ArrayList<>(); ++ for (ItemEntity 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 (ItemEntity item : items) { +- item.level.addFreshEntity(item); ++ // Paper start ++ for (Item bukkit : list) { ++ if (!bukkit.isValid()) { ++ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); ++ item.level.addFreshEntity(item); ++ } ++ } ++ } else { ++ for (Item bukkit : list) { ++ if (bukkit.isValid()) { ++ bukkit.remove(); ++ } + } ++ // Paper end + } + } + diff --git a/patches/server/0587-Inline-shift-direction-fields.patch b/patches/server/0587-Inline-shift-direction-fields.patch deleted file mode 100644 index d4353368a2..0000000000 --- a/patches/server/0587-Inline-shift-direction-fields.patch +++ /dev/null @@ -1,55 +0,0 @@ -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/Direction.java b/src/main/java/net/minecraft/core/Direction.java -index d23c679485641ee1b21b7c457dccdec8f4baa792..a3bbebcdaea9e0dfddd9825272f84fc76cd13e89 100644 ---- a/src/main/java/net/minecraft/core/Direction.java -+++ b/src/main/java/net/minecraft/core/Direction.java -@@ -61,6 +61,11 @@ public enum Direction implements StringRepresentable { - }, (direction1, direction2) -> { - throw new IllegalArgumentException("Duplicate keys"); - }, Long2ObjectOpenHashMap::new)); -+ // Paper start -+ private final int adjX; -+ private final int adjY; -+ private final int adjZ; -+ // Paper end - - private Direction(int id, int idOpposite, int idHorizontal, String name, Direction.AxisDirection direction, Direction.Axis axis, Vec3i vector) { - this.data3d = id; -@@ -70,6 +75,11 @@ public enum Direction implements StringRepresentable { - this.axis = axis; - this.axisDirection = direction; - this.normal = vector; -+ // Paper start -+ this.adjX = vector.getX(); -+ this.adjY = vector.getY(); -+ this.adjZ = vector.getZ(); -+ // Paper end - } - - public static Direction[] orderedByNearest(Entity entity) { -@@ -362,15 +372,15 @@ public enum Direction implements StringRepresentable { - } - - public int getStepX() { -- return this.normal.getX(); -+ return this.adjX; // Paper - } - - public int getStepY() { -- return this.normal.getY(); -+ return this.adjY; // Paper - } - - public int getStepZ() { -- return this.normal.getZ(); -+ return this.adjZ; // Paper - } - - public Vector3f step() { diff --git a/patches/server/0588-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0588-Add-getMainThreadExecutor-to-BukkitScheduler.patch new file mode 100644 index 0000000000..8cfe3357dc --- /dev/null +++ b/patches/server/0588-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 a423970cf7c927ea8a1bf842aaa236d3cf2d54c2..cdefb2025eedea7e204d70d568adaf1c1ec4c03c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -655,4 +655,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/0588-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0588-Allow-adding-items-to-BlockDropItemEvent.patch deleted file mode 100644 index e71e9cff71..0000000000 --- a/patches/server/0588-Allow-adding-items-to-BlockDropItemEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Wed, 20 Jan 2021 14:23:37 -0600 -Subject: [PATCH] Allow adding items to BlockDropItemEvent - - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 9ccfe52a61b72addfa675af797ea4bafbff30bdb..82d8a8c2199673315c7b52e694f798cc59c5f96c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -396,13 +396,30 @@ public class CraftEventFactory { - } - - public static void handleBlockDropItemEvent(Block block, BlockState state, ServerPlayer player, List items) { -- BlockDropItemEvent event = new BlockDropItemEvent(block, state, player.getBukkitEntity(), Lists.transform(items, (item) -> (org.bukkit.entity.Item) item.getBukkitEntity())); -+ // Paper start -+ List list = new ArrayList<>(); -+ for (ItemEntity 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 (ItemEntity item : items) { -- item.level.addFreshEntity(item); -+ // Paper start -+ for (Item bukkit : list) { -+ if (!bukkit.isValid()) { -+ Entity item = ((org.bukkit.craftbukkit.entity.CraftItem) bukkit).getHandle(); -+ item.level.addFreshEntity(item); -+ } -+ } -+ } else { -+ for (Item bukkit : list) { -+ if (bukkit.isValid()) { -+ bukkit.remove(); -+ } - } -+ // Paper end - } - } - diff --git a/patches/server/0589-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0589-Add-getMainThreadExecutor-to-BukkitScheduler.patch deleted file mode 100644 index 8cfe3357dc..0000000000 --- a/patches/server/0589-Add-getMainThreadExecutor-to-BukkitScheduler.patch +++ /dev/null @@ -1,26 +0,0 @@ -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 a423970cf7c927ea8a1bf842aaa236d3cf2d54c2..cdefb2025eedea7e204d70d568adaf1c1ec4c03c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -655,4 +655,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/0589-living-entity-allow-attribute-registration.patch b/patches/server/0589-living-entity-allow-attribute-registration.patch new file mode 100644 index 0000000000..8b29222bec --- /dev/null +++ b/patches/server/0589-living-entity-allow-attribute-registration.patch @@ -0,0 +1,60 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ysl3000 +Date: Sat, 24 Oct 2020 16:37:44 +0200 +Subject: [PATCH] living entity allow attribute registration + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 4d2c02a1e1b4a8fad12894a7439ec7e53a67d97a..c770ee21b7b699522941f6a1584d532001c04082 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -132,4 +132,12 @@ public class AttributeMap { + } + + } ++ ++ // Paper - start ++ public void registerAttribute(Attribute attributeBase) { ++ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); ++ attributes.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 46c313d581b9af6aa0a48f97ae3cc800a88535f2..07d700382fc356837045c46d320b7b69ad13af68 100644 +--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +@@ -38,6 +38,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 net.minecraft.world.entity.ai.attributes.Attribute toMinecraft(Attribute attribute) { + return net.minecraft.core.Registry.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 d16ea002ca558502be2f04528f0346d9aacff30f..1322f5a059743e7e2245ef2e25e9bffda138aa7c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -688,6 +688,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.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 Mob) { diff --git a/patches/server/0590-fix-dead-slime-setSize-invincibility.patch b/patches/server/0590-fix-dead-slime-setSize-invincibility.patch new file mode 100644 index 0000000000..33ce1b72f2 --- /dev/null +++ b/patches/server/0590-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 4d401403de2399919043651345eed91c11ac986f..3c5326b1b4b18365e06292eca447778442201176 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java +@@ -17,7 +17,7 @@ public class CraftSlime extends CraftMob implements Slime { + + @Override + public void setSize(int size) { +- this.getHandle().setSize(size, true); ++ this.getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility + } + + @Override diff --git a/patches/server/0590-living-entity-allow-attribute-registration.patch b/patches/server/0590-living-entity-allow-attribute-registration.patch deleted file mode 100644 index 88cf7f15c6..0000000000 --- a/patches/server/0590-living-entity-allow-attribute-registration.patch +++ /dev/null @@ -1,60 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: ysl3000 -Date: Sat, 24 Oct 2020 16:37:44 +0200 -Subject: [PATCH] living entity allow attribute registration - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 4d2c02a1e1b4a8fad12894a7439ec7e53a67d97a..c770ee21b7b699522941f6a1584d532001c04082 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -132,4 +132,12 @@ public class AttributeMap { - } - - } -+ -+ // Paper - start -+ public void registerAttribute(Attribute attributeBase) { -+ AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); -+ attributes.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 46c313d581b9af6aa0a48f97ae3cc800a88535f2..07d700382fc356837045c46d320b7b69ad13af68 100644 ---- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java -@@ -38,6 +38,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 net.minecraft.world.entity.ai.attributes.Attribute toMinecraft(Attribute attribute) { - return net.minecraft.core.Registry.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 0293d6fd1bb29f75fa1fa1cdfa36b3f679c1bc45..6555db49ff57bba13a7eb3c0bf7ecb66d7828dce 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -686,6 +686,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.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 Mob) { diff --git a/patches/server/0591-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0591-Merchant-getRecipes-should-return-an-immutable-list.patch new file mode 100644 index 0000000000..cbb0cdc052 --- /dev/null +++ b/patches/server/0591-Merchant-getRecipes-should-return-an-immutable-list.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +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 425c8de426cecc9919d03dc64325494104d1b294..71be1e4f17ded6ea42e36be0a9b534c6a65ec640 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java +@@ -24,7 +24,7 @@ public class CraftMerchant implements Merchant { + + @Override + public List getRecipes() { +- return Collections.unmodifiableList(Lists.transform(this.merchant.getOffers(), new Function() { ++ return List.copyOf(Lists.transform(this.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.MerchantOffer recipe) { + return recipe.asBukkit(); diff --git a/patches/server/0591-fix-dead-slime-setSize-invincibility.patch b/patches/server/0591-fix-dead-slime-setSize-invincibility.patch deleted file mode 100644 index 33ce1b72f2..0000000000 --- a/patches/server/0591-fix-dead-slime-setSize-invincibility.patch +++ /dev/null @@ -1,19 +0,0 @@ -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 4d401403de2399919043651345eed91c11ac986f..3c5326b1b4b18365e06292eca447778442201176 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSlime.java -@@ -17,7 +17,7 @@ public class CraftSlime extends CraftMob implements Slime { - - @Override - public void setSize(int size) { -- this.getHandle().setSize(size, true); -+ this.getHandle().setSize(size, /* true */ getHandle().isAlive()); // Paper - fix dead slime setSize invincibility - } - - @Override diff --git a/patches/server/0592-Add-support-for-hex-color-codes-in-console.patch b/patches/server/0592-Add-support-for-hex-color-codes-in-console.patch new file mode 100644 index 0000000000..64b7398ff4 --- /dev/null +++ b/patches/server/0592-Add-support-for-hex-color-codes-in-console.patch @@ -0,0 +1,326 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy <10731363+JRoy@users.noreply.github.com> +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/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +index 685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869..8f07539a82f449ad217e316a7513a1708781fb63 100644 +--- a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java ++++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +@@ -1,16 +1,25 @@ + package com.destroystokyo.paper.console; + ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.identity.Identity; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.logger.slf4j.ComponentLogger; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + import org.apache.logging.log4j.LogManager; +-import org.apache.logging.log4j.Logger; + import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; + + public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { + +- private static final Logger LOGGER = LogManager.getRootLogger(); ++ private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); + + @Override + public void sendRawMessage(String message) { +- // TerminalConsoleAppender supports color codes directly in log messages ++ final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); ++ this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); ++ } ++ ++ @Override ++ public void sendMessage(Identity identity, Component message, MessageType type) { + LOGGER.info(message); + } + +diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java +index c3631efda9c7fa531a8a9f18fbee7b5f8655382b..9a3c1314d5a0aa20380662595359580b1a97be89 100644 +--- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java ++++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java +@@ -1,9 +1,11 @@ + package io.papermc.paper.adventure.providers; + +-import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.console.HexFormattingConverter; ++import java.util.Locale; + import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.logger.slf4j.ComponentLogger; + import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider; ++import net.kyori.adventure.translation.GlobalTranslator; + import org.jetbrains.annotations.NotNull; + import org.slf4j.LoggerFactory; + +@@ -14,6 +16,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider { + } + + private String serialize(final Component message) { +- return PaperAdventure.asPlain(message, null); ++ return HexFormattingConverter.SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault())); + } + } +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..b4d0b7ecd56ab952319946854168c1299cb0b1be +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java +@@ -0,0 +1,212 @@ ++package io.papermc.paper.console; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++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.ConverterKeys; ++import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternFormatter; ++import org.apache.logging.log4j.core.pattern.PatternParser; ++import 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 Adventure [char]#rrggbb 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 = 0x7f; ++ public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() ++ .hexColors() ++ .flattener(PaperAdventure.FLATTENER) ++ .character(HexFormattingConverter.COLOR_CHAR) ++ .build(); ++ 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 + "#([0-9a-fA-F]){6}"); ++ ++ private static final String[] RGB_ANSI_CODES = new String[]{ ++ formatHexAnsi(NamedTextColor.BLACK), // Black §0 ++ formatHexAnsi(NamedTextColor.DARK_BLUE), // Dark Blue §1 ++ formatHexAnsi(NamedTextColor.DARK_GREEN), // Dark Green §2 ++ formatHexAnsi(NamedTextColor.DARK_AQUA), // Dark Aqua §3 ++ formatHexAnsi(NamedTextColor.DARK_RED), // Dark Red §4 ++ formatHexAnsi(NamedTextColor.DARK_PURPLE), // Dark Purple §5 ++ formatHexAnsi(NamedTextColor.GOLD), // Gold §6 ++ formatHexAnsi(NamedTextColor.GRAY), // Gray §7 ++ formatHexAnsi(NamedTextColor.DARK_GRAY), // Dark Gray §8 ++ formatHexAnsi(NamedTextColor.BLUE), // Blue §9 ++ formatHexAnsi(NamedTextColor.GREEN), // Green §a ++ formatHexAnsi(NamedTextColor.AQUA), // Aqua §b ++ formatHexAnsi(NamedTextColor.RED), // Red §c ++ formatHexAnsi(NamedTextColor.LIGHT_PURPLE), // Light Purple §d ++ formatHexAnsi(NamedTextColor.YELLOW), // Yellow §e ++ formatHexAnsi(NamedTextColor.WHITE), // White §f ++ "\u001B[5m", // Obfuscated §k ++ "\u001B[1m", // Bold §l ++ "\u001B[9m", // Strikethrough §m ++ "\u001B[4m", // Underline §n ++ "\u001B[3m", // Italic §o ++ ANSI_RESET, // Reset §r ++ }; ++ private static final String[] ANSI_ANSI_CODES = 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[1m", // 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(final String input) { ++ return RGB_PATTERN.matcher(input).replaceAll(result -> { ++ final int hex = Integer.decode(result.group().substring(1)); ++ return formatHexAnsi(hex); ++ }); ++ } ++ ++ private static String formatHexAnsi(final TextColor color) { ++ return formatHexAnsi(color.value()); ++ } ++ ++ private static String formatHexAnsi(final int color) { ++ final int red = color >> 16 & 0xFF; ++ final int green = color >> 8 & 0xFF; ++ final int blue = color & 0xFF; ++ return String.format(RGB_ANSI, red, green, blue); ++ } ++ ++ private static String stripRGBColors(final String input) { ++ return RGB_PATTERN.matcher(input).replaceAll(""); ++ } ++ ++ 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); ++ StringBuilder buffer = new StringBuilder(); ++ final String[] ansiCodes = GlobalConfiguration.get().logging.useRgbForNamedTextColors ? RGB_ANSI_CODES : ANSI_ANSI_CODES; ++ 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); ++ 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/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index abd1935ebc12f963b563023eb5279ad16ed1d8df..df570a64f1a8378f24977f0aa1e1f9b7191f0955 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1688,7 +1688,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop + + +- ++ + + + ++ 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/0592-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0592-Merchant-getRecipes-should-return-an-immutable-list.patch deleted file mode 100644 index cbb0cdc052..0000000000 --- a/patches/server/0592-Merchant-getRecipes-should-return-an-immutable-list.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: 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 425c8de426cecc9919d03dc64325494104d1b294..71be1e4f17ded6ea42e36be0a9b534c6a65ec640 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMerchant.java -@@ -24,7 +24,7 @@ public class CraftMerchant implements Merchant { - - @Override - public List getRecipes() { -- return Collections.unmodifiableList(Lists.transform(this.merchant.getOffers(), new Function() { -+ return List.copyOf(Lists.transform(this.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.MerchantOffer recipe) { - return recipe.asBukkit(); diff --git a/patches/server/0593-Add-support-for-hex-color-codes-in-console.patch b/patches/server/0593-Add-support-for-hex-color-codes-in-console.patch deleted file mode 100644 index 64b7398ff4..0000000000 --- a/patches/server/0593-Add-support-for-hex-color-codes-in-console.patch +++ /dev/null @@ -1,326 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy <10731363+JRoy@users.noreply.github.com> -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/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java -index 685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869..8f07539a82f449ad217e316a7513a1708781fb63 100644 ---- a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java -+++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java -@@ -1,16 +1,25 @@ - package com.destroystokyo.paper.console; - -+import net.kyori.adventure.audience.MessageType; -+import net.kyori.adventure.identity.Identity; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - import org.apache.logging.log4j.LogManager; --import org.apache.logging.log4j.Logger; - import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; - - public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { - -- private static final Logger LOGGER = LogManager.getRootLogger(); -+ private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); - - @Override - public void sendRawMessage(String message) { -- // TerminalConsoleAppender supports color codes directly in log messages -+ final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); -+ this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); -+ } -+ -+ @Override -+ public void sendMessage(Identity identity, Component message, MessageType type) { - LOGGER.info(message); - } - -diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -index c3631efda9c7fa531a8a9f18fbee7b5f8655382b..9a3c1314d5a0aa20380662595359580b1a97be89 100644 ---- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -+++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java -@@ -1,9 +1,11 @@ - package io.papermc.paper.adventure.providers; - --import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.console.HexFormattingConverter; -+import java.util.Locale; - import net.kyori.adventure.text.Component; - import net.kyori.adventure.text.logger.slf4j.ComponentLogger; - import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider; -+import net.kyori.adventure.translation.GlobalTranslator; - import org.jetbrains.annotations.NotNull; - import org.slf4j.LoggerFactory; - -@@ -14,6 +16,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider { - } - - private String serialize(final Component message) { -- return PaperAdventure.asPlain(message, null); -+ return HexFormattingConverter.SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault())); - } - } -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..b4d0b7ecd56ab952319946854168c1299cb0b1be ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java -@@ -0,0 +1,212 @@ -+package io.papermc.paper.console; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -+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.ConverterKeys; -+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternFormatter; -+import org.apache.logging.log4j.core.pattern.PatternParser; -+import 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 Adventure [char]#rrggbb 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 = 0x7f; -+ public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() -+ .hexColors() -+ .flattener(PaperAdventure.FLATTENER) -+ .character(HexFormattingConverter.COLOR_CHAR) -+ .build(); -+ 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 + "#([0-9a-fA-F]){6}"); -+ -+ private static final String[] RGB_ANSI_CODES = new String[]{ -+ formatHexAnsi(NamedTextColor.BLACK), // Black §0 -+ formatHexAnsi(NamedTextColor.DARK_BLUE), // Dark Blue §1 -+ formatHexAnsi(NamedTextColor.DARK_GREEN), // Dark Green §2 -+ formatHexAnsi(NamedTextColor.DARK_AQUA), // Dark Aqua §3 -+ formatHexAnsi(NamedTextColor.DARK_RED), // Dark Red §4 -+ formatHexAnsi(NamedTextColor.DARK_PURPLE), // Dark Purple §5 -+ formatHexAnsi(NamedTextColor.GOLD), // Gold §6 -+ formatHexAnsi(NamedTextColor.GRAY), // Gray §7 -+ formatHexAnsi(NamedTextColor.DARK_GRAY), // Dark Gray §8 -+ formatHexAnsi(NamedTextColor.BLUE), // Blue §9 -+ formatHexAnsi(NamedTextColor.GREEN), // Green §a -+ formatHexAnsi(NamedTextColor.AQUA), // Aqua §b -+ formatHexAnsi(NamedTextColor.RED), // Red §c -+ formatHexAnsi(NamedTextColor.LIGHT_PURPLE), // Light Purple §d -+ formatHexAnsi(NamedTextColor.YELLOW), // Yellow §e -+ formatHexAnsi(NamedTextColor.WHITE), // White §f -+ "\u001B[5m", // Obfuscated §k -+ "\u001B[1m", // Bold §l -+ "\u001B[9m", // Strikethrough §m -+ "\u001B[4m", // Underline §n -+ "\u001B[3m", // Italic §o -+ ANSI_RESET, // Reset §r -+ }; -+ private static final String[] ANSI_ANSI_CODES = 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[1m", // 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(final String input) { -+ return RGB_PATTERN.matcher(input).replaceAll(result -> { -+ final int hex = Integer.decode(result.group().substring(1)); -+ return formatHexAnsi(hex); -+ }); -+ } -+ -+ private static String formatHexAnsi(final TextColor color) { -+ return formatHexAnsi(color.value()); -+ } -+ -+ private static String formatHexAnsi(final int color) { -+ final int red = color >> 16 & 0xFF; -+ final int green = color >> 8 & 0xFF; -+ final int blue = color & 0xFF; -+ return String.format(RGB_ANSI, red, green, blue); -+ } -+ -+ private static String stripRGBColors(final String input) { -+ return RGB_PATTERN.matcher(input).replaceAll(""); -+ } -+ -+ 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); -+ StringBuilder buffer = new StringBuilder(); -+ final String[] ansiCodes = GlobalConfiguration.get().logging.useRgbForNamedTextColors ? RGB_ANSI_CODES : ANSI_ANSI_CODES; -+ 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); -+ 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/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index abd1935ebc12f963b563023eb5279ad16ed1d8df..df570a64f1a8378f24977f0aa1e1f9b7191f0955 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1688,7 +1688,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop - - -- -+ - - - -+ 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/0593-Expose-Tracked-Players.patch b/patches/server/0593-Expose-Tracked-Players.patch new file mode 100644 index 0000000000..e3f023c819 --- /dev/null +++ b/patches/server/0593-Expose-Tracked-Players.patch @@ -0,0 +1,29 @@ +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/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 142e6be6a41500b6e3b49173b7432e63de7ad3da..b30fb13db5524dcd05a155b014b93089af05c994 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1299,5 +1299,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + public boolean isTicking() { + return getHandle().isTicking(); + } ++ ++ @Override ++ public Set getTrackedPlayers() { ++ if (this.entity.tracker == null) { ++ return java.util.Collections.emptySet(); ++ } ++ ++ Set set = new java.util.HashSet<>(this.entity.tracker.seenBy.size()); ++ for (net.minecraft.server.network.ServerPlayerConnection connection : this.entity.tracker.seenBy) { ++ set.add(connection.getPlayer().getBukkitEntity().getPlayer()); ++ } ++ return set; ++ } + // Paper end + } diff --git a/patches/server/0594-Expose-Tracked-Players.patch b/patches/server/0594-Expose-Tracked-Players.patch deleted file mode 100644 index 2a57f6e90d..0000000000 --- a/patches/server/0594-Expose-Tracked-Players.patch +++ /dev/null @@ -1,29 +0,0 @@ -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/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index fa1e996157fb3470c08669801e7482af70714b11..e9828bab16ac05babccfb1fefad85860c1a4768c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1282,5 +1282,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - public boolean isTicking() { - return getHandle().isTicking(); - } -+ -+ @Override -+ public Set getTrackedPlayers() { -+ if (this.entity.tracker == null) { -+ return java.util.Collections.emptySet(); -+ } -+ -+ Set set = new java.util.HashSet<>(this.entity.tracker.seenBy.size()); -+ for (net.minecraft.server.network.ServerPlayerConnection connection : this.entity.tracker.seenBy) { -+ set.add(connection.getPlayer().getBukkitEntity().getPlayer()); -+ } -+ return set; -+ } - // Paper end - } diff --git a/patches/server/0594-Remove-streams-from-SensorNearest.patch b/patches/server/0594-Remove-streams-from-SensorNearest.patch new file mode 100644 index 0000000000..a7ae5d1a98 --- /dev/null +++ b/patches/server/0594-Remove-streams-from-SensorNearest.patch @@ -0,0 +1,88 @@ +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/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index a41c3365db8e9999b686f5bf4bd888458cee43a5..1dfcc5cba6ffb463acf161a23fff1ca452184290 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -28,11 +28,15 @@ public class NearestItemSensor extends Sensor { + return true; + }); + list.sort(Comparator.comparingDouble(entity::distanceToSqr)); +- Optional optional = list.stream().filter((itemEntity) -> { +- return entity.wantsToPickUp(itemEntity.getItem()); +- }).filter((itemEntity) -> { +- return itemEntity.closerThan(entity, 32.0D); +- }).filter(entity::hasLineOfSight).findFirst(); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); ++ // Paper start - remove streams in favour of lists ++ ItemEntity nearest = null; ++ for (ItemEntity entityItem : list) { ++ if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) { ++ nearest = entityItem; ++ break; ++ } ++ } ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); ++ // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index ed1b95ec694b0fe8b647964b18b8c33707fc0b47..312775d0430f793720211dc29bb293503e799d11 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -21,18 +21,25 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, LivingEntity entity) { +- List list = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((player) -> { +- return entity.closerThan(player, 16.0D); +- }).sorted(Comparator.comparingDouble(entity::distanceToSqr)).collect(Collectors.toList()); ++ List players = new java.util.ArrayList<>(world.players()); ++ players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); ++ players.sort(Comparator.comparingDouble(entity::distanceTo)); + Brain brain = entity.getBrain(); +- brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, list); +- List list2 = list.stream().filter((player) -> { +- return isEntityTargetable(entity, player); +- }).collect(Collectors.toList()); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list2.isEmpty() ? null : list2.get(0)); +- Optional optional = list2.stream().filter((player) -> { +- return isEntityAttackable(entity, player); +- }).findFirst(); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, optional); ++ ++ brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); ++ ++ Player nearest = null, nearestTargetable = null; ++ for (Player player : players) { ++ if (Sensor.isEntityTargetable(entity, player)) { ++ if (nearest == null) nearest = player; ++ if (Sensor.isEntityAttackable(entity, player)) { ++ nearestTargetable = player; ++ break; // Both variables are assigned, no reason to loop further ++ } ++ } ++ } ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); ++ // Paper end + } + } diff --git a/patches/server/0595-Remove-streams-from-SensorNearest.patch b/patches/server/0595-Remove-streams-from-SensorNearest.patch deleted file mode 100644 index a7ae5d1a98..0000000000 --- a/patches/server/0595-Remove-streams-from-SensorNearest.patch +++ /dev/null @@ -1,88 +0,0 @@ -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/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -index a41c3365db8e9999b686f5bf4bd888458cee43a5..1dfcc5cba6ffb463acf161a23fff1ca452184290 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -@@ -28,11 +28,15 @@ public class NearestItemSensor extends Sensor { - return true; - }); - list.sort(Comparator.comparingDouble(entity::distanceToSqr)); -- Optional optional = list.stream().filter((itemEntity) -> { -- return entity.wantsToPickUp(itemEntity.getItem()); -- }).filter((itemEntity) -> { -- return itemEntity.closerThan(entity, 32.0D); -- }).filter(entity::hasLineOfSight).findFirst(); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); -+ // Paper start - remove streams in favour of lists -+ ItemEntity nearest = null; -+ for (ItemEntity entityItem : list) { -+ if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) { -+ nearest = entityItem; -+ break; -+ } -+ } -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); -+ // Paper end - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -index ed1b95ec694b0fe8b647964b18b8c33707fc0b47..312775d0430f793720211dc29bb293503e799d11 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -@@ -21,18 +21,25 @@ public class PlayerSensor extends Sensor { - - @Override - protected void doTick(ServerLevel world, LivingEntity entity) { -- List list = world.players().stream().filter(EntitySelector.NO_SPECTATORS).filter((player) -> { -- return entity.closerThan(player, 16.0D); -- }).sorted(Comparator.comparingDouble(entity::distanceToSqr)).collect(Collectors.toList()); -+ List players = new java.util.ArrayList<>(world.players()); -+ players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); -+ players.sort(Comparator.comparingDouble(entity::distanceTo)); - Brain brain = entity.getBrain(); -- brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, list); -- List list2 = list.stream().filter((player) -> { -- return isEntityTargetable(entity, player); -- }).collect(Collectors.toList()); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list2.isEmpty() ? null : list2.get(0)); -- Optional optional = list2.stream().filter((player) -> { -- return isEntityAttackable(entity, player); -- }).findFirst(); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, optional); -+ -+ brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); -+ -+ Player nearest = null, nearestTargetable = null; -+ for (Player player : players) { -+ if (Sensor.isEntityTargetable(entity, player)) { -+ if (nearest == null) nearest = player; -+ if (Sensor.isEntityAttackable(entity, player)) { -+ nearestTargetable = player; -+ break; // Both variables are assigned, no reason to loop further -+ } -+ } -+ } -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); -+ // Paper end - } - } diff --git a/patches/server/0595-Throw-proper-exception-on-empty-JsonList-file.patch b/patches/server/0595-Throw-proper-exception-on-empty-JsonList-file.patch new file mode 100644 index 0000000000..e013e32526 --- /dev/null +++ b/patches/server/0595-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/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java +index 91899909abd83611ac63633fef701be542cf64bf..4fd709a550bf8da1e996894a1ca6b91206c31e9e 100644 +--- a/src/main/java/net/minecraft/server/players/StoredUserList.java ++++ b/src/main/java/net/minecraft/server/players/StoredUserList.java +@@ -187,6 +187,7 @@ public abstract class StoredUserList> { + + try { + JsonArray jsonarray = (JsonArray) StoredUserList.GSON.fromJson(bufferedreader, JsonArray.class); ++ com.google.common.base.Preconditions.checkState(jsonarray != null, "The file \"" + this.file.getName() + "\" is either empty or corrupt"); // Paper + + this.map.clear(); + Iterator iterator = jsonarray.iterator(); diff --git a/patches/server/0596-Improve-ServerGUI.patch b/patches/server/0596-Improve-ServerGUI.patch new file mode 100644 index 0000000000..e573bd2f58 --- /dev/null +++ b/patches/server/0596-Improve-ServerGUI.patch @@ -0,0 +1,377 @@ +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 23239679d6584f1088b2b94c46eb9a5c1f9ad91d..fa56cd09102a89692b42f1d14257990508c5c720 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.tickTimes)) + " ms"); ++ vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); ++ + setListData(vector); + } + +@@ -70,4 +79,8 @@ 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/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +index 66464c10a6b33414c6d1b67b926a66c343d5f887..c07918aa1ed2469ad7a76a0add60ab648ff7f421 100644 +--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +@@ -59,6 +59,15 @@ public class MinecraftServerGui 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(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); ++ } catch (java.io.IOException ignore) { ++ } ++ // Paper end ++ + jframe.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowevent) { + if (!servergui.isClosing.getAndSet(true)) { +diff --git a/src/main/java/net/minecraft/server/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java +index 4c5059805715bbca53196bcabd7eda550a46c34d..88f10d729aa1e0a01790521821d691a0ecd373a2 100644 +--- a/src/main/java/net/minecraft/server/gui/StatsComponent.java ++++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java +@@ -35,8 +35,17 @@ public class StatsComponent extends JComponent { + + private void tick() { + long l = 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.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; + this.msgs[1] = "Avg tick: " + DECIMAL_FORMAT.format(this.getAverage(this.server.tickTimes) * 1.0E-6D) + " ms"; ++ this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); ++ // Paper end + this.values[this.vp++ & 255] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); + this.repaint(); + } +@@ -76,4 +85,10 @@ public class StatsComponent extends JComponent { + public void close() { + this.timer.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/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/0596-Throw-proper-exception-on-empty-JsonList-file.patch b/patches/server/0596-Throw-proper-exception-on-empty-JsonList-file.patch deleted file mode 100644 index e013e32526..0000000000 --- a/patches/server/0596-Throw-proper-exception-on-empty-JsonList-file.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java -index 91899909abd83611ac63633fef701be542cf64bf..4fd709a550bf8da1e996894a1ca6b91206c31e9e 100644 ---- a/src/main/java/net/minecraft/server/players/StoredUserList.java -+++ b/src/main/java/net/minecraft/server/players/StoredUserList.java -@@ -187,6 +187,7 @@ public abstract class StoredUserList> { - - try { - JsonArray jsonarray = (JsonArray) StoredUserList.GSON.fromJson(bufferedreader, JsonArray.class); -+ com.google.common.base.Preconditions.checkState(jsonarray != null, "The file \"" + this.file.getName() + "\" is either empty or corrupt"); // Paper - - this.map.clear(); - Iterator iterator = jsonarray.iterator(); diff --git a/patches/server/0597-Improve-ServerGUI.patch b/patches/server/0597-Improve-ServerGUI.patch deleted file mode 100644 index e573bd2f58..0000000000 --- a/patches/server/0597-Improve-ServerGUI.patch +++ /dev/null @@ -1,377 +0,0 @@ -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 23239679d6584f1088b2b94c46eb9a5c1f9ad91d..fa56cd09102a89692b42f1d14257990508c5c720 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.tickTimes)) + " ms"); -+ vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); -+ - setListData(vector); - } - -@@ -70,4 +79,8 @@ 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/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -index 66464c10a6b33414c6d1b67b926a66c343d5f887..c07918aa1ed2469ad7a76a0add60ab648ff7f421 100644 ---- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java -@@ -59,6 +59,15 @@ public class MinecraftServerGui 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(javax.imageio.ImageIO.read(Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png")))); -+ } catch (java.io.IOException ignore) { -+ } -+ // Paper end -+ - jframe.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent windowevent) { - if (!servergui.isClosing.getAndSet(true)) { -diff --git a/src/main/java/net/minecraft/server/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java -index 4c5059805715bbca53196bcabd7eda550a46c34d..88f10d729aa1e0a01790521821d691a0ecd373a2 100644 ---- a/src/main/java/net/minecraft/server/gui/StatsComponent.java -+++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java -@@ -35,8 +35,17 @@ public class StatsComponent extends JComponent { - - private void tick() { - long l = 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.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)"; - this.msgs[1] = "Avg tick: " + DECIMAL_FORMAT.format(this.getAverage(this.server.tickTimes) * 1.0E-6D) + " ms"; -+ this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); -+ // Paper end - this.values[this.vp++ & 255] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); - this.repaint(); - } -@@ -76,4 +85,10 @@ public class StatsComponent extends JComponent { - public void close() { - this.timer.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/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/0597-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch b/patches/server/0597-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch new file mode 100644 index 0000000000..1099f41d55 --- /dev/null +++ b/patches/server/0597-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/PressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java +index f12bf33aa8cc8043052aa1048087f61d9a6d4d52..ae5b052b80665bfba126f5ca5dcd78608cb27d48 100644 +--- a/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java +@@ -81,6 +81,7 @@ public class PressurePlateBlock extends BasePressurePlateBlock { + + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); ++ if (entity.isIgnoringBlockTriggers()) continue; // Paper - don't call event for ignored entities + + // CraftBukkit start - Call interact event when turning on a pressure plate + if (this.getSignalForState(world.getBlockState(pos)) == 0) { diff --git a/patches/server/0598-fix-converting-txt-to-json-file.patch b/patches/server/0598-fix-converting-txt-to-json-file.patch new file mode 100644 index 0000000000..a8f2f43419 --- /dev/null +++ b/patches/server/0598-fix-converting-txt-to-json-file.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Jan 2021 19:49:15 -0800 +Subject: [PATCH] fix converting txt to json file + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +index d83bed436d2ad51cef83ecbf0c7df227a67ff404..dc96b30c70cd79d7b2a0322f32b9399a0f2faa41 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java +@@ -17,6 +17,11 @@ public class DedicatedPlayerList extends PlayerList { + this.setViewDistance(dedicatedServerProperties.viewDistance); + this.setSimulationDistance(dedicatedServerProperties.simulationDistance); + super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); ++ // Paper start - moved from constructor ++ } ++ @Override ++ public void loadAndSaveFiles() { ++ // Paper end + this.loadUserBanList(); + this.saveUserBanList(); + this.loadIpBanList(); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index da83f111199a6b4c712a9bb8ab6f1d1b5c6ae77c..ef02ceba53943d34bd45070297c72beedd6e1883 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -203,6 +203,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + paperConfigurations.initializeGlobalConfiguration(); + paperConfigurations.initializeWorldDefaultsConfiguration(); ++ // Paper start - moved up to right after PlayerList creation but before file load/save ++ if (this.convertOldUsers()) { ++ this.getProfileCache().save(false); // Paper ++ } ++ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames ++ // Paper end - moved up + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); + io.papermc.paper.command.PaperCommands.registerCommands(this); + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); +@@ -258,9 +264,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); + } + +- if (this.convertOldUsers()) { +- this.getProfileCache().save(false); // Paper +- } + + if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { + return false; +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index c4f5a3bf60302743329b17f9b2dffcee9c1e5ab3..a0b752e09f375fb72ff5ad7952b5ef6ebdb6c14b 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -177,6 +177,7 @@ public abstract class PlayerList { + this.maxPlayers = maxPlayers; + this.playerIo = saveHandler; + } ++ abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor + + public void placeNewPlayer(Connection connection, ServerPlayer player) { + player.isRealPlayer = true; // Paper - Chunk priority diff --git a/patches/server/0598-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch b/patches/server/0598-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch deleted file mode 100644 index 1099f41d55..0000000000 --- a/patches/server/0598-stop-firing-pressure-plate-EntityInteractEvent-for-i.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/PressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java -index f12bf33aa8cc8043052aa1048087f61d9a6d4d52..ae5b052b80665bfba126f5ca5dcd78608cb27d48 100644 ---- a/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PressurePlateBlock.java -@@ -81,6 +81,7 @@ public class PressurePlateBlock extends BasePressurePlateBlock { - - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); -+ if (entity.isIgnoringBlockTriggers()) continue; // Paper - don't call event for ignored entities - - // CraftBukkit start - Call interact event when turning on a pressure plate - if (this.getSignalForState(world.getBlockState(pos)) == 0) { diff --git a/patches/server/0599-Add-worldborder-events.patch b/patches/server/0599-Add-worldborder-events.patch new file mode 100644 index 0000000000..af0cf2d370 --- /dev/null +++ b/patches/server/0599-Add-worldborder-events.patch @@ -0,0 +1,72 @@ +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 2442c287a7f26cfee10a19e9015558cdebdcf3ac..7a12a4da4864306ec6589ca81368e84718825047 100644 +--- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java ++++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java +@@ -123,6 +123,14 @@ public class WorldBorder { + } + + public void setCenter(double x, double z) { ++ // Paper start ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); ++ if (!event.callEvent()) return; ++ x = event.getNewCenter().getX(); ++ z = event.getNewCenter().getZ(); ++ } ++ // Paper end + this.centerX = x; + this.centerZ = z; + this.extent.onCenterChange(); +@@ -149,6 +157,17 @@ public class WorldBorder { + } + + public void setSize(double size) { ++ // Paper start ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); ++ if (!event.callEvent()) return; ++ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition ++ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); ++ return; ++ } ++ size = event.getNewSize(); ++ } ++ // Paper end + this.extent = new WorldBorder.StaticBorderExtent(size); + Iterator iterator = this.getListeners().iterator(); + +@@ -161,6 +180,20 @@ public class WorldBorder { + } + + public void lerpSizeBetween(double fromSize, double toSize, long time) { ++ // Paper start ++ if (this.world != null) { ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type; ++ if (fromSize == toSize) { // new size = old size ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. ++ } else { ++ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; ++ } ++ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); ++ if (!event.callEvent()) return; ++ toSize = event.getNewSize(); ++ time = event.getDuration(); ++ } ++ // Paper end + this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); + Iterator iterator = this.getListeners().iterator(); + +@@ -472,6 +505,7 @@ public class WorldBorder { + + @Override + public WorldBorder.BorderExtent update() { ++ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper + return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); + } + diff --git a/patches/server/0599-fix-converting-txt-to-json-file.patch b/patches/server/0599-fix-converting-txt-to-json-file.patch deleted file mode 100644 index a8f2f43419..0000000000 --- a/patches/server/0599-fix-converting-txt-to-json-file.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Jan 2021 19:49:15 -0800 -Subject: [PATCH] fix converting txt to json file - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -index d83bed436d2ad51cef83ecbf0c7df227a67ff404..dc96b30c70cd79d7b2a0322f32b9399a0f2faa41 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedPlayerList.java -@@ -17,6 +17,11 @@ public class DedicatedPlayerList extends PlayerList { - this.setViewDistance(dedicatedServerProperties.viewDistance); - this.setSimulationDistance(dedicatedServerProperties.simulationDistance); - super.setUsingWhiteList(dedicatedServerProperties.whiteList.get()); -+ // Paper start - moved from constructor -+ } -+ @Override -+ public void loadAndSaveFiles() { -+ // Paper end - this.loadUserBanList(); - this.saveUserBanList(); - this.loadIpBanList(); -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index da83f111199a6b4c712a9bb8ab6f1d1b5c6ae77c..ef02ceba53943d34bd45070297c72beedd6e1883 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -203,6 +203,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. - paperConfigurations.initializeGlobalConfiguration(); - paperConfigurations.initializeWorldDefaultsConfiguration(); -+ // Paper start - moved up to right after PlayerList creation but before file load/save -+ if (this.convertOldUsers()) { -+ this.getProfileCache().save(false); // Paper -+ } -+ this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames -+ // Paper end - moved up - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); - io.papermc.paper.command.PaperCommands.registerCommands(this); - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); -@@ -258,9 +264,6 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); - } - -- if (this.convertOldUsers()) { -- this.getProfileCache().save(false); // Paper -- } - - if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { - return false; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index c4f5a3bf60302743329b17f9b2dffcee9c1e5ab3..a0b752e09f375fb72ff5ad7952b5ef6ebdb6c14b 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -177,6 +177,7 @@ public abstract class PlayerList { - this.maxPlayers = maxPlayers; - this.playerIo = saveHandler; - } -+ abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor - - public void placeNewPlayer(Connection connection, ServerPlayer player) { - player.isRealPlayer = true; // Paper - Chunk priority diff --git a/patches/server/0600-Add-worldborder-events.patch b/patches/server/0600-Add-worldborder-events.patch deleted file mode 100644 index af0cf2d370..0000000000 --- a/patches/server/0600-Add-worldborder-events.patch +++ /dev/null @@ -1,72 +0,0 @@ -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 2442c287a7f26cfee10a19e9015558cdebdcf3ac..7a12a4da4864306ec6589ca81368e84718825047 100644 ---- a/src/main/java/net/minecraft/world/level/border/WorldBorder.java -+++ b/src/main/java/net/minecraft/world/level/border/WorldBorder.java -@@ -123,6 +123,14 @@ public class WorldBorder { - } - - public void setCenter(double x, double z) { -+ // Paper start -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderCenterChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), new org.bukkit.Location(world.getWorld(), this.getCenterX(), 0, this.getCenterZ()), new org.bukkit.Location(world.getWorld(), x, 0, z)); -+ if (!event.callEvent()) return; -+ x = event.getNewCenter().getX(); -+ z = event.getNewCenter().getZ(); -+ } -+ // Paper end - this.centerX = x; - this.centerZ = z; - this.extent.onCenterChange(); -@@ -149,6 +157,17 @@ public class WorldBorder { - } - - public void setSize(double size) { -+ // Paper start -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE, getSize(), size, 0); -+ if (!event.callEvent()) return; -+ if (event.getType() == io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE && event.getDuration() > 0) { // If changed to a timed transition -+ lerpSizeBetween(event.getOldSize(), event.getNewSize(), event.getDuration()); -+ return; -+ } -+ size = event.getNewSize(); -+ } -+ // Paper end - this.extent = new WorldBorder.StaticBorderExtent(size); - Iterator iterator = this.getListeners().iterator(); - -@@ -161,6 +180,20 @@ public class WorldBorder { - } - - public void lerpSizeBetween(double fromSize, double toSize, long time) { -+ // Paper start -+ if (this.world != null) { -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type type; -+ if (fromSize == toSize) { // new size = old size -+ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.INSTANT_MOVE; // Use INSTANT_MOVE because below it creates a Static border if they are equal. -+ } else { -+ type = io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent.Type.STARTED_MOVE; -+ } -+ io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent event = new io.papermc.paper.event.world.border.WorldBorderBoundsChangeEvent(world.getWorld(), world.getWorld().getWorldBorder(), type, fromSize, toSize, time); -+ if (!event.callEvent()) return; -+ toSize = event.getNewSize(); -+ time = event.getDuration(); -+ } -+ // Paper end - this.extent = (WorldBorder.BorderExtent) (fromSize == toSize ? new WorldBorder.StaticBorderExtent(toSize) : new WorldBorder.MovingBorderExtent(fromSize, toSize, time)); - Iterator iterator = this.getListeners().iterator(); - -@@ -472,6 +505,7 @@ public class WorldBorder { - - @Override - public WorldBorder.BorderExtent update() { -+ if (world != null && this.getLerpRemainingTime() <= 0L) new io.papermc.paper.event.world.border.WorldBorderBoundsChangeFinishEvent(world.getWorld(), world.getWorld().getWorldBorder(), this.from, this.to, this.lerpDuration).callEvent(); // Paper - return (WorldBorder.BorderExtent) (this.getLerpRemainingTime() <= 0L ? WorldBorder.this.new StaticBorderExtent(this.to) : this); - } - diff --git a/patches/server/0600-added-PlayerNameEntityEvent.patch b/patches/server/0600-added-PlayerNameEntityEvent.patch new file mode 100644 index 0000000000..c1df33b717 --- /dev/null +++ b/patches/server/0600-added-PlayerNameEntityEvent.patch @@ -0,0 +1,38 @@ +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/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java +index 13080fb2350d4ee2107063948dd2ef359dff8306..623f78c078fb3aa2665d7e8a37672438227bce6b 100644 +--- a/src/main/java/net/minecraft/world/item/NameTagItem.java ++++ b/src/main/java/net/minecraft/world/item/NameTagItem.java +@@ -1,5 +1,9 @@ + package net.minecraft.world.item; + ++// Paper start ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.event.player.PlayerNameEntityEvent; ++// Paper end + import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.entity.LivingEntity; +@@ -15,9 +19,14 @@ public class NameTagItem extends Item { + public InteractionResult interactLivingEntity(ItemStack stack, Player user, LivingEntity entity, InteractionHand hand) { + if (stack.hasCustomHoverName() && !(entity instanceof Player)) { + if (!user.level.isClientSide && entity.isAlive()) { +- entity.setCustomName(stack.getHoverName()); +- if (entity instanceof Mob) { +- ((Mob)entity).setPersistenceRequired(); ++ // Paper start ++ PlayerNameEntityEvent event = new PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), PaperAdventure.asAdventure(stack.getHoverName()), true); ++ if (!event.callEvent()) return InteractionResult.PASS; ++ LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); ++ newEntityLiving.setCustomName(event.getName() != null ? PaperAdventure.asVanilla(event.getName()) : null); ++ if (event.isPersistent() && newEntityLiving instanceof Mob) { ++ ((Mob) newEntityLiving).setPersistenceRequired(); ++ // Paper end + } + + stack.shrink(1); diff --git a/patches/server/0601-Prevent-grindstones-from-overstacking-items.patch b/patches/server/0601-Prevent-grindstones-from-overstacking-items.patch new file mode 100644 index 0000000000..3e79faf014 --- /dev/null +++ b/patches/server/0601-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/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +index 7a0c38c743ef02f5b9c052f88c2d6429a53b8286..740b778ce14f241e509f17c3a84b9ed47cdeeebe 100644 +--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +@@ -198,13 +198,13 @@ public class GrindstoneMenu extends AbstractContainerMenu { + i = Math.max(item.getMaxDamage() - l, 0); + itemstack2 = this.mergeEnchants(itemstack, itemstack1); + if (!itemstack2.isDamageableItem()) { +- if (!ItemStack.matches(itemstack, itemstack1)) { ++ if (!ItemStack.matches(itemstack, itemstack1) || itemstack2.getMaxStackSize() == 1) { // Paper - add max stack size check + this.resultSlots.setItem(0, ItemStack.EMPTY); + this.broadcastChanges(); + 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/0601-added-PlayerNameEntityEvent.patch b/patches/server/0601-added-PlayerNameEntityEvent.patch deleted file mode 100644 index c1df33b717..0000000000 --- a/patches/server/0601-added-PlayerNameEntityEvent.patch +++ /dev/null @@ -1,38 +0,0 @@ -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/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java -index 13080fb2350d4ee2107063948dd2ef359dff8306..623f78c078fb3aa2665d7e8a37672438227bce6b 100644 ---- a/src/main/java/net/minecraft/world/item/NameTagItem.java -+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java -@@ -1,5 +1,9 @@ - package net.minecraft.world.item; - -+// Paper start -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.event.player.PlayerNameEntityEvent; -+// Paper end - import net.minecraft.world.InteractionHand; - import net.minecraft.world.InteractionResult; - import net.minecraft.world.entity.LivingEntity; -@@ -15,9 +19,14 @@ public class NameTagItem extends Item { - public InteractionResult interactLivingEntity(ItemStack stack, Player user, LivingEntity entity, InteractionHand hand) { - if (stack.hasCustomHoverName() && !(entity instanceof Player)) { - if (!user.level.isClientSide && entity.isAlive()) { -- entity.setCustomName(stack.getHoverName()); -- if (entity instanceof Mob) { -- ((Mob)entity).setPersistenceRequired(); -+ // Paper start -+ PlayerNameEntityEvent event = new PlayerNameEntityEvent(((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity(), entity.getBukkitLivingEntity(), PaperAdventure.asAdventure(stack.getHoverName()), true); -+ if (!event.callEvent()) return InteractionResult.PASS; -+ LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); -+ newEntityLiving.setCustomName(event.getName() != null ? PaperAdventure.asVanilla(event.getName()) : null); -+ if (event.isPersistent() && newEntityLiving instanceof Mob) { -+ ((Mob) newEntityLiving).setPersistenceRequired(); -+ // Paper end - } - - stack.shrink(1); diff --git a/patches/server/0602-Add-recipe-to-cook-events.patch b/patches/server/0602-Add-recipe-to-cook-events.patch new file mode 100644 index 0000000000..61023ab91c --- /dev/null +++ b/patches/server/0602-Add-recipe-to-cook-events.patch @@ -0,0 +1,43 @@ +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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 1cc2c13ab07b9dc4492cec55314e12d7536d5453..9a40839ccedd6a39c2b8755e29f31a4f626fe15d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -442,7 +442,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result); ++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe) irecipe.toBukkitRecipe()); // Paper + world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); + + if (furnaceSmeltEvent.isCancelled()) { +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 003a66064c666db974c2b36f6579a87e1ad28685..18b22efe9f5335bb49aa0e899727d1911dc20718 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -60,7 +60,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { + SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack}); +- ItemStack itemstack1 = (ItemStack) campfire.quickCheck.getRecipeFor(inventorysubcontainer, world).map((recipecampfire) -> { ++ Optional recipe = campfire.quickCheck.getRecipeFor( inventorysubcontainer, world); ++ ItemStack itemstack1 = (ItemStack) recipe.map((recipecampfire) -> { ++ // Paper end + return recipecampfire.assemble(inventorysubcontainer); + }).orElse(itemstack); + +@@ -68,7 +70,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); + org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); + +- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result); ++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe) recipe.map(CampfireCookingRecipe::toBukkitRecipe).orElse(null)); // Paper + world.getCraftServer().getPluginManager().callEvent(blockCookEvent); + + if (blockCookEvent.isCancelled()) { diff --git a/patches/server/0602-Prevent-grindstones-from-overstacking-items.patch b/patches/server/0602-Prevent-grindstones-from-overstacking-items.patch deleted file mode 100644 index 3e79faf014..0000000000 --- a/patches/server/0602-Prevent-grindstones-from-overstacking-items.patch +++ /dev/null @@ -1,26 +0,0 @@ -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/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 7a0c38c743ef02f5b9c052f88c2d6429a53b8286..740b778ce14f241e509f17c3a84b9ed47cdeeebe 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -198,13 +198,13 @@ public class GrindstoneMenu extends AbstractContainerMenu { - i = Math.max(item.getMaxDamage() - l, 0); - itemstack2 = this.mergeEnchants(itemstack, itemstack1); - if (!itemstack2.isDamageableItem()) { -- if (!ItemStack.matches(itemstack, itemstack1)) { -+ if (!ItemStack.matches(itemstack, itemstack1) || itemstack2.getMaxStackSize() == 1) { // Paper - add max stack size check - this.resultSlots.setItem(0, ItemStack.EMPTY); - this.broadcastChanges(); - 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/0603-Add-Block-isValidTool.patch b/patches/server/0603-Add-Block-isValidTool.patch new file mode 100644 index 0000000000..95bf6c3432 --- /dev/null +++ b/patches/server/0603-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 817d7143fb60916a6747ee4b9f7c77becd9071c1..1bd1f5993fc296b7d8255cabaf28382c2e258099 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -718,5 +718,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/0603-Add-recipe-to-cook-events.patch b/patches/server/0603-Add-recipe-to-cook-events.patch deleted file mode 100644 index 61023ab91c..0000000000 --- a/patches/server/0603-Add-recipe-to-cook-events.patch +++ /dev/null @@ -1,43 +0,0 @@ -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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 1cc2c13ab07b9dc4492cec55314e12d7536d5453..9a40839ccedd6a39c2b8755e29f31a4f626fe15d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -442,7 +442,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); - org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); - -- FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result); -+ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result, (org.bukkit.inventory.CookingRecipe) irecipe.toBukkitRecipe()); // Paper - world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent); - - if (furnaceSmeltEvent.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 003a66064c666db974c2b36f6579a87e1ad28685..18b22efe9f5335bb49aa0e899727d1911dc20718 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -60,7 +60,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - - if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { - SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack}); -- ItemStack itemstack1 = (ItemStack) campfire.quickCheck.getRecipeFor(inventorysubcontainer, world).map((recipecampfire) -> { -+ Optional recipe = campfire.quickCheck.getRecipeFor( inventorysubcontainer, world); -+ ItemStack itemstack1 = (ItemStack) recipe.map((recipecampfire) -> { -+ // Paper end - return recipecampfire.assemble(inventorysubcontainer); - }).orElse(itemstack); - -@@ -68,7 +70,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - CraftItemStack source = CraftItemStack.asCraftMirror(itemstack); - org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1); - -- BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result); -+ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(world, pos), source, result, (org.bukkit.inventory.CookingRecipe) recipe.map(CampfireCookingRecipe::toBukkitRecipe).orElse(null)); // Paper - world.getCraftServer().getPluginManager().callEvent(blockCookEvent); - - if (blockCookEvent.isCancelled()) { diff --git a/patches/server/0604-Add-Block-isValidTool.patch b/patches/server/0604-Add-Block-isValidTool.patch deleted file mode 100644 index 95bf6c3432..0000000000 --- a/patches/server/0604-Add-Block-isValidTool.patch +++ /dev/null @@ -1,20 +0,0 @@ -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 817d7143fb60916a6747ee4b9f7c77becd9071c1..1bd1f5993fc296b7d8255cabaf28382c2e258099 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -718,5 +718,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/0604-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0604-Allow-using-signs-inside-spawn-protection.patch new file mode 100644 index 0000000000..e2dfe62f54 --- /dev/null +++ b/patches/server/0604-Allow-using-signs-inside-spawn-protection.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Anton Lindroth +Date: Wed, 15 Apr 2020 01:54:02 +0200 +Subject: [PATCH] Allow using signs inside spawn protection + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index c0cca7442d3bb7df393088d66c5962bcda78e609..6c64270b9d87ae3d08400ddd4effa689ce58070f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1844,7 +1844,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + int i = this.player.level.getMaxBuildHeight(); + + if (blockposition.getY() < i) { +- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { ++ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - sign check + this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + diff --git a/patches/server/0605-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0605-Allow-using-signs-inside-spawn-protection.patch deleted file mode 100644 index d48b61abd9..0000000000 --- a/patches/server/0605-Allow-using-signs-inside-spawn-protection.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Anton Lindroth -Date: Wed, 15 Apr 2020 01:54:02 +0200 -Subject: [PATCH] Allow using signs inside spawn protection - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 2ad4df9895728185d6e5db2e8525ed3b08a518a1..582f8bbc3eb169d2cf41cba9ab324c933466b2b2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1844,7 +1844,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - int i = this.player.level.getMaxBuildHeight(); - - if (blockposition.getY() < i) { -- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { -+ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - sign check - this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 - InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); - diff --git a/patches/server/0605-Expand-world-key-API.patch b/patches/server/0605-Expand-world-key-API.patch new file mode 100644 index 0000000000..9d99a03dc8 --- /dev/null +++ b/patches/server/0605-Expand-world-key-API.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jan 2021 00:34:04 -0800 +Subject: [PATCH] Expand world key API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index ee5e59c37301d9a806e2f696d52d9d217b232833..bb5d22125b6cd4e60d2b7e2e00af158c9ea22cd9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -938,5 +938,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public io.papermc.paper.world.MoonPhase getMoonPhase() { + return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index f5783a0df00c7c88c5d5d1c3b55ba671350cd0c6..cebc3c444681d6f422a8befff74476e7e0d8d22e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1143,9 +1143,15 @@ public final class CraftServer implements Server { + File folder = new File(this.getWorldContainer(), name); + World world = this.getWorld(name); + +- if (world != null) { +- return world; ++ // Paper start ++ World worldByKey = this.getWorld(creator.key()); ++ if (world != null || worldByKey != null) { ++ if (world == worldByKey) { ++ return world; ++ } ++ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); + } ++ // Paper end + + if ((folder.exists()) && (!folder.isDirectory())) { + throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder"); +@@ -1220,7 +1226,7 @@ public final class CraftServer implements Server { + } else if (name.equals(levelName + "_the_end")) { + worldKey = net.minecraft.world.level.Level.END; + } else { +- worldKey = ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, new ResourceLocation(name.toLowerCase(java.util.Locale.ENGLISH))); ++ worldKey = ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper + } + + ServerLevel internal = (ServerLevel) new ServerLevel(this.console, console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), +@@ -1312,6 +1318,15 @@ public final class CraftServer implements Server { + return null; + } + ++ // Paper start ++ @Override ++ public World getWorld(NamespacedKey worldKey) { ++ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, CraftNamespacedKey.toMinecraft(worldKey))); ++ if (worldServer == null) return null; ++ return worldServer.getWorld(); ++ } ++ // Paper end ++ + public void addWorld(World world) { + // Check if a World already exists with the UID. + if (this.getWorld(world.getUID()) != null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index dc034bd793842e02f0fea54d1ae49ac7a66af597..1343db872321fe14465ad2b1f363d41989096ed4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -517,6 +517,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public Registry registryFor(Class classOfT) { + return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT); + } ++ ++ @Override ++ public String getMainLevelName() { ++ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; ++ } + // Paper end + + /** diff --git a/patches/server/0606-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0606-Add-fast-alternative-constructor-for-Rotations.patch new file mode 100644 index 0000000000..bbdcc665bb --- /dev/null +++ b/patches/server/0606-Add-fast-alternative-constructor-for-Rotations.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 Rotations + +Signed-off-by: Irmo van den Berge + +diff --git a/src/main/java/net/minecraft/core/Rotations.java b/src/main/java/net/minecraft/core/Rotations.java +index e4b4e78de933b93be87805501d9cbfec429cf68c..152b1a2cb88c6456282b537611c18975d6da5f57 100644 +--- a/src/main/java/net/minecraft/core/Rotations.java ++++ b/src/main/java/net/minecraft/core/Rotations.java +@@ -19,6 +19,18 @@ public class Rotations { + this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2)); + } + ++ // Paper start - faster alternative constructor ++ private Rotations(float x, float y, float z, Void dummy_var) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ } ++ ++ public static Rotations createWithoutValidityChecks(float x, float y, float z) { ++ return new Rotations(x, y, z, null); ++ } ++ // Paper end ++ + public ListTag save() { + ListTag listTag = new ListTag(); + listTag.add(FloatTag.valueOf(this.x)); diff --git a/patches/server/0606-Expand-world-key-API.patch b/patches/server/0606-Expand-world-key-API.patch deleted file mode 100644 index 9d99a03dc8..0000000000 --- a/patches/server/0606-Expand-world-key-API.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Jan 2021 00:34:04 -0800 -Subject: [PATCH] Expand world key API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index ee5e59c37301d9a806e2f696d52d9d217b232833..bb5d22125b6cd4e60d2b7e2e00af158c9ea22cd9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -938,5 +938,10 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public io.papermc.paper.world.MoonPhase getMoonPhase() { - return io.papermc.paper.world.MoonPhase.getPhase(this.getHandle().dayTime() / 24000L); - } -+ -+ @Override -+ public org.bukkit.NamespacedKey getKey() { -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f5783a0df00c7c88c5d5d1c3b55ba671350cd0c6..cebc3c444681d6f422a8befff74476e7e0d8d22e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1143,9 +1143,15 @@ public final class CraftServer implements Server { - File folder = new File(this.getWorldContainer(), name); - World world = this.getWorld(name); - -- if (world != null) { -- return world; -+ // Paper start -+ World worldByKey = this.getWorld(creator.key()); -+ if (world != null || worldByKey != null) { -+ if (world == worldByKey) { -+ return world; -+ } -+ throw new IllegalArgumentException("Cannot create a world with key " + creator.key() + " and name " + name + " one (or both) already match a world that exists"); - } -+ // Paper end - - if ((folder.exists()) && (!folder.isDirectory())) { - throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder"); -@@ -1220,7 +1226,7 @@ public final class CraftServer implements Server { - } else if (name.equals(levelName + "_the_end")) { - worldKey = net.minecraft.world.level.Level.END; - } else { -- worldKey = ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, new ResourceLocation(name.toLowerCase(java.util.Locale.ENGLISH))); -+ worldKey = ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, new net.minecraft.resources.ResourceLocation(creator.key().getNamespace().toLowerCase(java.util.Locale.ENGLISH), creator.key().getKey().toLowerCase(java.util.Locale.ENGLISH))); // Paper - } - - ServerLevel internal = (ServerLevel) new ServerLevel(this.console, console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(11), -@@ -1312,6 +1318,15 @@ public final class CraftServer implements Server { - return null; - } - -+ // Paper start -+ @Override -+ public World getWorld(NamespacedKey worldKey) { -+ ServerLevel worldServer = console.getLevel(ResourceKey.create(net.minecraft.core.Registry.DIMENSION_REGISTRY, CraftNamespacedKey.toMinecraft(worldKey))); -+ if (worldServer == null) return null; -+ return worldServer.getWorld(); -+ } -+ // Paper end -+ - public void addWorld(World world) { - // Check if a World already exists with the UID. - if (this.getWorld(world.getUID()) != null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index dc034bd793842e02f0fea54d1ae49ac7a66af597..1343db872321fe14465ad2b1f363d41989096ed4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -517,6 +517,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public Registry registryFor(Class classOfT) { - return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT); - } -+ -+ @Override -+ public String getMainLevelName() { -+ return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; -+ } - // Paper end - - /** diff --git a/patches/server/0607-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0607-Add-fast-alternative-constructor-for-Rotations.patch deleted file mode 100644 index bbdcc665bb..0000000000 --- a/patches/server/0607-Add-fast-alternative-constructor-for-Rotations.patch +++ /dev/null @@ -1,30 +0,0 @@ -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 Rotations - -Signed-off-by: Irmo van den Berge - -diff --git a/src/main/java/net/minecraft/core/Rotations.java b/src/main/java/net/minecraft/core/Rotations.java -index e4b4e78de933b93be87805501d9cbfec429cf68c..152b1a2cb88c6456282b537611c18975d6da5f57 100644 ---- a/src/main/java/net/minecraft/core/Rotations.java -+++ b/src/main/java/net/minecraft/core/Rotations.java -@@ -19,6 +19,18 @@ public class Rotations { - this(serialized.getFloat(0), serialized.getFloat(1), serialized.getFloat(2)); - } - -+ // Paper start - faster alternative constructor -+ private Rotations(float x, float y, float z, Void dummy_var) { -+ this.x = x; -+ this.y = y; -+ this.z = z; -+ } -+ -+ public static Rotations createWithoutValidityChecks(float x, float y, float z) { -+ return new Rotations(x, y, z, null); -+ } -+ // Paper end -+ - public ListTag save() { - ListTag listTag = new ListTag(); - listTag.add(FloatTag.valueOf(this.x)); diff --git a/patches/server/0607-Item-Rarity-API.patch b/patches/server/0607-Item-Rarity-API.patch new file mode 100644 index 0000000000..e3e4b42355 --- /dev/null +++ b/patches/server/0607-Item-Rarity-API.patch @@ -0,0 +1,61 @@ +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/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 1343db872321fe14465ad2b1f363d41989096ed4..9e27257265dab677175b9b3d921e1fc3f3cb7817 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -522,6 +522,20 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getMainLevelName() { + return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; + } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemRarity(org.bukkit.Material material) { ++ Item item = getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item, and rarity does not apply to blocks"); ++ } ++ return io.papermc.paper.inventory.ItemRarity.values()[item.rarity.ordinal()]; ++ } ++ ++ @Override ++ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { ++ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..38e6d42098f216b1d24f50386e7be98181122d8d +--- /dev/null ++++ b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.inventory; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.minecraft.world.item.Rarity; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertEquals; ++ ++public class ItemRarityTest { ++ ++ @Test ++ public void testConvertFromNmsToBukkit() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ assertEquals("rarity names are mis-matched", ItemRarity.values()[nmsRarity.ordinal()].name(), nmsRarity.name()); ++ } ++ } ++ ++ @Test ++ public void testRarityFormatting() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ assertEquals("rarity formatting is mis-matched", nmsRarity.color, PaperAdventure.asVanilla(ItemRarity.values()[nmsRarity.ordinal()].color)); ++ } ++ } ++} diff --git a/patches/server/0608-Item-Rarity-API.patch b/patches/server/0608-Item-Rarity-API.patch deleted file mode 100644 index e3e4b42355..0000000000 --- a/patches/server/0608-Item-Rarity-API.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 12 Mar 2021 17:09:42 -0800 -Subject: [PATCH] Item Rarity API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 1343db872321fe14465ad2b1f363d41989096ed4..9e27257265dab677175b9b3d921e1fc3f3cb7817 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -522,6 +522,20 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getMainLevelName() { - return ((net.minecraft.server.dedicated.DedicatedServer) net.minecraft.server.MinecraftServer.getServer()).getProperties().levelName; - } -+ -+ @Override -+ public io.papermc.paper.inventory.ItemRarity getItemRarity(org.bukkit.Material material) { -+ Item item = getItem(material); -+ if (item == null) { -+ throw new IllegalArgumentException(material + " is not an item, and rarity does not apply to blocks"); -+ } -+ return io.papermc.paper.inventory.ItemRarity.values()[item.rarity.ordinal()]; -+ } -+ -+ @Override -+ public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { -+ return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; -+ } - // Paper end - - /** -diff --git a/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..38e6d42098f216b1d24f50386e7be98181122d8d ---- /dev/null -+++ b/src/test/java/io/papermc/paper/inventory/ItemRarityTest.java -@@ -0,0 +1,24 @@ -+package io.papermc.paper.inventory; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.minecraft.world.item.Rarity; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertEquals; -+ -+public class ItemRarityTest { -+ -+ @Test -+ public void testConvertFromNmsToBukkit() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ assertEquals("rarity names are mis-matched", ItemRarity.values()[nmsRarity.ordinal()].name(), nmsRarity.name()); -+ } -+ } -+ -+ @Test -+ public void testRarityFormatting() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ assertEquals("rarity formatting is mis-matched", nmsRarity.color, PaperAdventure.asVanilla(ItemRarity.values()[nmsRarity.ordinal()].color)); -+ } -+ } -+} diff --git a/patches/server/0608-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server/0608-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch new file mode 100644 index 0000000000..a8810d8bca --- /dev/null +++ b/patches/server/0608-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +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/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 4b8024f8f62caaa417de6798522c2beb98e00fc4..e7bfd61be4974dbe6abc2ebb870664ff1fce19cb 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -347,6 +347,12 @@ public class EntityType implements EntityTypeTest { + + @Nullable + public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ // Paper start - add consumer to modify entity before spawn ++ return this.spawn(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, null); ++ } ++ @Nullable ++ public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType 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(EntityType.getKey(this).getPath()); + if (type != null) { +@@ -362,6 +368,7 @@ public class EntityType implements EntityTypeTest { + } + // Paper end + T t0 = this.create(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); ++ if (t0 != null && op != null) op.accept(t0); // Paper + + if (t0 != null) { + worldserver.addFreshEntityWithPassengers(t0, spawnReason); +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index d7cb3d8b37f225ee4796246aa907da1092fa9a0d..abb2c5c4ac481c7529aa29322babb1929235e15a 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -59,7 +59,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + + public WanderingTrader(EntityType type, Level world) { + super(type, world); +- 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/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +index fcde09e155727fe0b8b6acc79700fe4122fd22f0..daa7c4bd8a3f6c28f02b7117b061f0def29e3743 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +@@ -123,7 +123,7 @@ public class WanderingTraderSpawner implements CustomSpawner { + return false; + } + +- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, (CompoundTag) null, (Component) null, (Player) null, blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit ++ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, (CompoundTag) null, (Component) null, (Player) null, blockposition2, MobSpawnType.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/0609-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch b/patches/server/0609-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch deleted file mode 100644 index a8810d8bca..0000000000 --- a/patches/server/0609-Only-set-despawnTimer-for-Wandering-Traders-spawned-.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -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/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 4b8024f8f62caaa417de6798522c2beb98e00fc4..e7bfd61be4974dbe6abc2ebb870664ff1fce19cb 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -347,6 +347,12 @@ public class EntityType implements EntityTypeTest { - - @Nullable - public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { -+ // Paper start - add consumer to modify entity before spawn -+ return this.spawn(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, null); -+ } -+ @Nullable -+ public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Component ichatbasecomponent, @Nullable Player entityhuman, BlockPos blockposition, MobSpawnType 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(EntityType.getKey(this).getPath()); - if (type != null) { -@@ -362,6 +368,7 @@ public class EntityType implements EntityTypeTest { - } - // Paper end - T t0 = this.create(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1); -+ if (t0 != null && op != null) op.accept(t0); // Paper - - if (t0 != null) { - worldserver.addFreshEntityWithPassengers(t0, spawnReason); -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index d7cb3d8b37f225ee4796246aa907da1092fa9a0d..abb2c5c4ac481c7529aa29322babb1929235e15a 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -59,7 +59,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - - public WanderingTrader(EntityType type, Level world) { - super(type, world); -- 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/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -index fcde09e155727fe0b8b6acc79700fe4122fd22f0..daa7c4bd8a3f6c28f02b7117b061f0def29e3743 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -123,7 +123,7 @@ public class WanderingTraderSpawner implements CustomSpawner { - return false; - } - -- WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, (CompoundTag) null, (Component) null, (Player) null, blockposition2, MobSpawnType.EVENT, false, false, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit -+ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(world, (CompoundTag) null, (Component) null, (Player) null, blockposition2, MobSpawnType.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/0609-copy-TESign-isEditable-from-snapshots.patch b/patches/server/0609-copy-TESign-isEditable-from-snapshots.patch new file mode 100644 index 0000000000..766607080f --- /dev/null +++ b/patches/server/0609-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 a0950f5902c3719dc31205ec43dca9482278c744..dfd5d05830d3892e1d640149d1aa9d70d6f8a66b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java +@@ -115,6 +115,7 @@ public class CraftSign extends CraftBlockEntityState implements + } + // Paper end + } ++ sign.isEditable = getSnapshot().isEditable; // Paper - copy manually + } + + public static void openSign(Sign sign, org.bukkit.entity.HumanEntity player) { // Paper - change move open sign to HumanEntity diff --git a/patches/server/0610-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0610-Drop-carried-item-when-player-has-disconnected.patch new file mode 100644 index 0000000000..8d562472bc --- /dev/null +++ b/patches/server/0610-Drop-carried-item-when-player-has-disconnected.patch @@ -0,0 +1,27 @@ +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 a0b752e09f375fb72ff5ad7952b5ef6ebdb6c14b..8eae2de23226700b7186b5e673f7f776854a49bc 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -625,6 +625,14 @@ public abstract class PlayerList { + } + // Paper end + ++ // Paper - Drop carried item when player has disconnected ++ if (!entityplayer.containerMenu.getCarried().isEmpty()) { ++ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried(); ++ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY); ++ entityplayer.drop(carried, false); ++ } ++ // Paper end ++ + this.save(entityplayer); + if (entityplayer.isPassenger()) { + Entity entity = entityplayer.getRootVehicle(); diff --git a/patches/server/0610-copy-TESign-isEditable-from-snapshots.patch b/patches/server/0610-copy-TESign-isEditable-from-snapshots.patch deleted file mode 100644 index 766607080f..0000000000 --- a/patches/server/0610-copy-TESign-isEditable-from-snapshots.patch +++ /dev/null @@ -1,18 +0,0 @@ -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 a0950f5902c3719dc31205ec43dca9482278c744..dfd5d05830d3892e1d640149d1aa9d70d6f8a66b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java -@@ -115,6 +115,7 @@ public class CraftSign extends CraftBlockEntityState implements - } - // Paper end - } -+ sign.isEditable = getSnapshot().isEditable; // Paper - copy manually - } - - public static void openSign(Sign sign, org.bukkit.entity.HumanEntity player) { // Paper - change move open sign to HumanEntity diff --git a/patches/server/0611-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0611-Drop-carried-item-when-player-has-disconnected.patch deleted file mode 100644 index 8d562472bc..0000000000 --- a/patches/server/0611-Drop-carried-item-when-player-has-disconnected.patch +++ /dev/null @@ -1,27 +0,0 @@ -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 a0b752e09f375fb72ff5ad7952b5ef6ebdb6c14b..8eae2de23226700b7186b5e673f7f776854a49bc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -625,6 +625,14 @@ public abstract class PlayerList { - } - // Paper end - -+ // Paper - Drop carried item when player has disconnected -+ if (!entityplayer.containerMenu.getCarried().isEmpty()) { -+ net.minecraft.world.item.ItemStack carried = entityplayer.containerMenu.getCarried(); -+ entityplayer.containerMenu.setCarried(net.minecraft.world.item.ItemStack.EMPTY); -+ entityplayer.drop(carried, false); -+ } -+ // Paper end -+ - this.save(entityplayer); - if (entityplayer.isPassenger()) { - Entity entity = entityplayer.getRootVehicle(); diff --git a/patches/server/0611-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0611-forced-whitelist-use-configurable-kick-message.patch new file mode 100644 index 0000000000..3066195a02 --- /dev/null +++ b/patches/server/0611-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 df570a64f1a8378f24977f0aa1e1f9b7191f0955..3c5b7f4b2db421d56e5832e283bd60702b2d67de 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2115,7 +2115,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Mon, 5 Apr 2021 18:35:15 -0700 +Subject: [PATCH] Don't ignore result of PlayerEditBookEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 6c64270b9d87ae3d08400ddd4effa689ce58070f..63649e0d5547d1b904a6605868dfad52214c7b3c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1294,7 +1294,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + itemstack.addTagElement("pages", nbttaglist); +- CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit ++ this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + } + + @Override diff --git a/patches/server/0612-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0612-forced-whitelist-use-configurable-kick-message.patch deleted file mode 100644 index 3066195a02..0000000000 --- a/patches/server/0612-forced-whitelist-use-configurable-kick-message.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary -Date: Sat, 27 Mar 2021 09:24:23 +0100 -Subject: [PATCH] forced whitelist: use configurable kick message - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index df570a64f1a8378f24977f0aa1e1f9b7191f0955..3c5b7f4b2db421d56e5832e283bd60702b2d67de 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2115,7 +2115,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Mon, 5 Apr 2021 18:35:15 -0700 -Subject: [PATCH] Don't ignore result of PlayerEditBookEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 582f8bbc3eb169d2cf41cba9ab324c933466b2b2..ed1b8507c98cbc11ec6dd3ceae2800bd69d99cf2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1294,7 +1294,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - itemstack.addTagElement("pages", nbttaglist); -- CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit -+ this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) - } - - @Override diff --git a/patches/server/0613-Entity-load-save-limit-per-chunk.patch b/patches/server/0613-Entity-load-save-limit-per-chunk.patch new file mode 100644 index 0000000000..64d4f515d8 --- /dev/null +++ b/patches/server/0613-Entity-load-save-limit-per-chunk.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +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/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index e7bfd61be4974dbe6abc2ebb870664ff1fce19cb..ac0f0a4da4282c13f6e1f37710cb615d66b8ef2c 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -563,9 +563,20 @@ public class EntityType implements EntityTypeTest { + final Spliterator spliterator = entityNbtList.spliterator(); + + return StreamSupport.stream(new Spliterator() { ++ final java.util.Map, Integer> loadedEntityCounts = new java.util.HashMap<>(); // Paper + public boolean tryAdvance(Consumer consumer) { + return spliterator.tryAdvance((nbtbase) -> { + EntityType.loadEntityRecursive((CompoundTag) nbtbase, world, (entity) -> { ++ // Paper start ++ final EntityType entityType = entity.getType(); ++ final int saveLimit = world.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (this.loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ return null; ++ } ++ this.loadedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + consumer.accept(entity); + return entity; + }); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +index dae66dd5dbebc7fd8fc331b1f5f06ec461667830..de7afc737b1ab099edc29a4ef94baa76329c2947 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +@@ -90,7 +90,18 @@ public class EntityStorage implements EntityPersistentStorage { + + } else { + ListTag listTag = new ListTag(); ++ final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper + dataList.getEntities().forEach((entity) -> { ++ // Paper start ++ final EntityType entityType = entity.getType(); ++ final int saveLimit = this.level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); ++ if (saveLimit > -1) { ++ if (savedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { ++ return; ++ } ++ savedEntityCounts.merge(entityType, 1, Integer::sum); ++ } ++ // Paper end + CompoundTag compoundTag = new CompoundTag(); + if (entity.save(compoundTag)) { + listTag.add(compoundTag); diff --git a/patches/server/0614-Entity-load-save-limit-per-chunk.patch b/patches/server/0614-Entity-load-save-limit-per-chunk.patch deleted file mode 100644 index 64d4f515d8..0000000000 --- a/patches/server/0614-Entity-load-save-limit-per-chunk.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -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/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index e7bfd61be4974dbe6abc2ebb870664ff1fce19cb..ac0f0a4da4282c13f6e1f37710cb615d66b8ef2c 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -563,9 +563,20 @@ public class EntityType implements EntityTypeTest { - final Spliterator spliterator = entityNbtList.spliterator(); - - return StreamSupport.stream(new Spliterator() { -+ final java.util.Map, Integer> loadedEntityCounts = new java.util.HashMap<>(); // Paper - public boolean tryAdvance(Consumer consumer) { - return spliterator.tryAdvance((nbtbase) -> { - EntityType.loadEntityRecursive((CompoundTag) nbtbase, world, (entity) -> { -+ // Paper start -+ final EntityType entityType = entity.getType(); -+ final int saveLimit = world.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); -+ if (saveLimit > -1) { -+ if (this.loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { -+ return null; -+ } -+ this.loadedEntityCounts.merge(entityType, 1, Integer::sum); -+ } -+ // Paper end - consumer.accept(entity); - return entity; - }); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index dae66dd5dbebc7fd8fc331b1f5f06ec461667830..de7afc737b1ab099edc29a4ef94baa76329c2947 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -90,7 +90,18 @@ public class EntityStorage implements EntityPersistentStorage { - - } else { - ListTag listTag = new ListTag(); -+ final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper - dataList.getEntities().forEach((entity) -> { -+ // Paper start -+ final EntityType entityType = entity.getType(); -+ final int saveLimit = this.level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); -+ if (saveLimit > -1) { -+ if (savedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { -+ return; -+ } -+ savedEntityCounts.merge(entityType, 1, Integer::sum); -+ } -+ // Paper end - CompoundTag compoundTag = new CompoundTag(); - if (entity.save(compoundTag)) { - listTag.add(compoundTag); diff --git a/patches/server/0614-Expose-protocol-version.patch b/patches/server/0614-Expose-protocol-version.patch new file mode 100644 index 0000000000..d01a192416 --- /dev/null +++ b/patches/server/0614-Expose-protocol-version.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 26 Mar 2021 11:23:17 +0100 +Subject: [PATCH] Expose protocol version + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 9e27257265dab677175b9b3d921e1fc3f3cb7817..7f747fbaa1da49ab930d2a9ff60200a445ca477c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -536,6 +536,11 @@ public final class CraftMagicNumbers implements UnsafeValues { + public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } ++ ++ @Override ++ public int getProtocolVersion() { ++ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); ++ } + // Paper end + + /** diff --git a/patches/server/0615-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0615-Enhance-console-tab-completions-for-brigadier-comman.patch new file mode 100644 index 0000000000..604c72c03e --- /dev/null +++ b/patches/server/0615-Enhance-console-tab-completions-for-brigadier-comman.patch @@ -0,0 +1,295 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 30 Mar 2021 16:06:08 -0700 +Subject: [PATCH] Enhance console tab completions for brigadier commands + + +diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +index a4070b59e261f0f1ac4beec47b11492f4724bf27..c5d5648f4ca603ef2b1df723b58f9caf4dd3c722 100644 +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -16,11 +16,15 @@ public final class PaperConsole extends SimpleTerminalConsole { + + @Override + protected LineReader buildReader(LineReaderBuilder builder) { +- return super.buildReader(builder ++ builder + .appName("Paper") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) +- ); ++ .option(LineReader.Option.COMPLETE_IN_WORD, true); ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { ++ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); ++ } ++ return super.buildReader(builder); + } + + @Override +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0627c98cae0b5ebdd71a849ae1299d7d3d581850 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java +@@ -0,0 +1,99 @@ ++package io.papermc.paper.console; ++ ++import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.CommandDispatcher; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.StringReader; ++import com.mojang.brigadier.suggestion.Suggestion; ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.function.Supplier; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.ComponentUtils; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Candidate; ++import org.jline.reader.LineReader; ++import org.jline.reader.ParsedLine; ++ ++import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; ++ ++public final class BrigadierCommandCompleter { ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { ++ this.addCandidates(candidates, Collections.emptyList(), existing); ++ return; ++ } ++ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); ++ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack.get()); ++ this.addCandidates( ++ candidates, ++ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), ++ existing ++ ); ++ } ++ ++ private void addCandidates( ++ final @NonNull List candidates, ++ final @NonNull List brigSuggestions, ++ final @NonNull List existing ++ ) { ++ final List completions = new ArrayList<>(); ++ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); ++ for (final Completion completion : existing) { ++ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { ++ continue; ++ } ++ completions.add(completion); ++ } ++ for (final Completion completion : completions) { ++ if (completion.suggestion().isEmpty()) { ++ continue; ++ } ++ candidates.add(toCandidate(completion)); ++ } ++ } ++ ++ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { ++ final String suggestionText = completion.suggestion(); ++ final String suggestionTooltip = PaperAdventure.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(ComponentUtils.fromMessage(suggestion.getTooltip()))); ++ } ++ ++ static @NonNull StringReader prepareStringReader(final @NonNull String line) { ++ final StringReader stringReader = new StringReader(line); ++ if (stringReader.canRead() && stringReader.peek() == '/') { ++ stringReader.skip(); ++ } ++ return stringReader; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd9d77d7c7f1a5a130a1f4c15e5b1e68ae3753e1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java +@@ -0,0 +1,70 @@ ++package io.papermc.paper.console; ++ ++import com.google.common.base.Suppliers; ++import com.mojang.brigadier.ParseResults; ++import com.mojang.brigadier.context.ParsedCommandNode; ++import com.mojang.brigadier.tree.LiteralCommandNode; ++import java.util.function.Supplier; ++import java.util.regex.Pattern; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.dedicated.DedicatedServer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.jline.reader.Highlighter; ++import org.jline.reader.LineReader; ++import org.jline.utils.AttributedString; ++import org.jline.utils.AttributedStringBuilder; ++import org.jline.utils.AttributedStyle; ++ ++public final class BrigadierCommandHighlighter implements Highlighter { ++ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; ++ private final Supplier commandSourceStack; ++ private final DedicatedServer server; ++ ++ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { ++ this.server = server; ++ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); ++ } ++ ++ @Override ++ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { ++ //noinspection ConstantConditions ++ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet ++ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ final AttributedStringBuilder builder = new AttributedStringBuilder(); ++ final ParseResults results = this.server.getCommands().getDispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack.get()); ++ int pos = 0; ++ if (buffer.startsWith("/")) { ++ builder.append("/", AttributedStyle.DEFAULT); ++ pos = 1; ++ } ++ int component = -1; ++ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { ++ if (node.getRange().getStart() >= buffer.length()) { ++ break; ++ } ++ final int start = node.getRange().getStart(); ++ final int end = Math.min(node.getRange().getEnd(), buffer.length()); ++ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); ++ if (node.getNode() instanceof LiteralCommandNode) { ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); ++ } else { ++ if (++component >= COLORS.length) { ++ component = 0; ++ } ++ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); ++ } ++ pos = end; ++ } ++ if (pos < buffer.length()) { ++ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); ++ } ++ return builder.toAttributedString(); ++ } ++ ++ @Override ++ public void setErrorPattern(final Pattern errorPattern) {} ++ ++ @Override ++ public void setErrorIndex(final int errorIndex) {} ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index ef02ceba53943d34bd45070297c72beedd6e1883..f812b0a2d5534a7c443361bd69cfc2fe2110ba9a 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -178,7 +178,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + thread.setDaemon(true); + thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); +- thread.start(); ++ // thread.start(); // Paper - moved down + DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); + if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); +@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames + // Paper end - moved up + org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); ++ thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized + io.papermc.paper.command.PaperCommands.registerCommands(this); + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +index 14cd8ae69d9b25dc5edad4ff96ff4a9acb1f22cb..cd4ad8261e56365850068db1d83d6a8454026737 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); // Paper + } + + // Paper start - Change method signature for JLine update +@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { + } + } + +- if (!completions.isEmpty()) { ++ if (false && !completions.isEmpty()) { + for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { + if (completion.suggestion().isEmpty()) { + continue; +@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { + )); + } + } ++ this.addCompletions(reader, line, candidates, completions); + return; + } + +@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { + try { + List offers = waitable.get(); + if (offers == null) { ++ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper + return; // Paper - Method returns void + } + + // Paper start - JLine update ++ /* + for (String completion : offers) { + if (completion.isEmpty()) { + continue; +@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { + + candidates.add(new Candidate(completion)); + } ++ */ ++ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); + // Paper end + + // Paper start - JLine handles cursor now +@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { + } + return false; + } ++ ++ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { ++ this.brigadierCompleter.complete(reader, line, candidates, existing); ++ } + // Paper end + } diff --git a/patches/server/0615-Expose-protocol-version.patch b/patches/server/0615-Expose-protocol-version.patch deleted file mode 100644 index d01a192416..0000000000 --- a/patches/server/0615-Expose-protocol-version.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 26 Mar 2021 11:23:17 +0100 -Subject: [PATCH] Expose protocol version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 9e27257265dab677175b9b3d921e1fc3f3cb7817..7f747fbaa1da49ab930d2a9ff60200a445ca477c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -536,6 +536,11 @@ public final class CraftMagicNumbers implements UnsafeValues { - public io.papermc.paper.inventory.ItemRarity getItemStackRarity(org.bukkit.inventory.ItemStack itemStack) { - return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; - } -+ -+ @Override -+ public int getProtocolVersion() { -+ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); -+ } - // Paper end - - /** diff --git a/patches/server/0616-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0616-Enhance-console-tab-completions-for-brigadier-comman.patch deleted file mode 100644 index 604c72c03e..0000000000 --- a/patches/server/0616-Enhance-console-tab-completions-for-brigadier-comman.patch +++ /dev/null @@ -1,295 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 30 Mar 2021 16:06:08 -0700 -Subject: [PATCH] Enhance console tab completions for brigadier commands - - -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index a4070b59e261f0f1ac4beec47b11492f4724bf27..c5d5648f4ca603ef2b1df723b58f9caf4dd3c722 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -16,11 +16,15 @@ public final class PaperConsole extends SimpleTerminalConsole { - - @Override - protected LineReader buildReader(LineReaderBuilder builder) { -- return super.buildReader(builder -+ builder - .appName("Paper") - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) -- ); -+ .option(LineReader.Option.COMPLETE_IN_WORD, true); -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierHighlighting) { -+ builder.highlighter(new io.papermc.paper.console.BrigadierCommandHighlighter(this.server)); -+ } -+ return super.buildReader(builder); - } - - @Override -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0627c98cae0b5ebdd71a849ae1299d7d3d581850 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandCompleter.java -@@ -0,0 +1,99 @@ -+package io.papermc.paper.console; -+ -+import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion; -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.CommandDispatcher; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.StringReader; -+import com.mojang.brigadier.suggestion.Suggestion; -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.function.Supplier; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.ComponentUtils; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.jline.reader.Candidate; -+import org.jline.reader.LineReader; -+import org.jline.reader.ParsedLine; -+ -+import static com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion.completion; -+ -+public final class BrigadierCommandCompleter { -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandCompleter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ public void complete(final @NonNull LineReader reader, final @NonNull ParsedLine line, final @NonNull List candidates, final @NonNull List existing) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().console.enableBrigadierCompletions) { -+ this.addCandidates(candidates, Collections.emptyList(), existing); -+ return; -+ } -+ final CommandDispatcher dispatcher = this.server.getCommands().getDispatcher(); -+ final ParseResults results = dispatcher.parse(prepareStringReader(line.line()), this.commandSourceStack.get()); -+ this.addCandidates( -+ candidates, -+ dispatcher.getCompletionSuggestions(results, line.cursor()).join().getList(), -+ existing -+ ); -+ } -+ -+ private void addCandidates( -+ final @NonNull List candidates, -+ final @NonNull List brigSuggestions, -+ final @NonNull List existing -+ ) { -+ final List completions = new ArrayList<>(); -+ brigSuggestions.forEach(it -> completions.add(toCompletion(it))); -+ for (final Completion completion : existing) { -+ if (completion.suggestion().isEmpty() || brigSuggestions.stream().anyMatch(it -> it.getText().equals(completion.suggestion()))) { -+ continue; -+ } -+ completions.add(completion); -+ } -+ for (final Completion completion : completions) { -+ if (completion.suggestion().isEmpty()) { -+ continue; -+ } -+ candidates.add(toCandidate(completion)); -+ } -+ } -+ -+ private static @NonNull Candidate toCandidate(final @NonNull Completion completion) { -+ final String suggestionText = completion.suggestion(); -+ final String suggestionTooltip = PaperAdventure.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(ComponentUtils.fromMessage(suggestion.getTooltip()))); -+ } -+ -+ static @NonNull StringReader prepareStringReader(final @NonNull String line) { -+ final StringReader stringReader = new StringReader(line); -+ if (stringReader.canRead() && stringReader.peek() == '/') { -+ stringReader.skip(); -+ } -+ return stringReader; -+ } -+} -diff --git a/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd9d77d7c7f1a5a130a1f4c15e5b1e68ae3753e1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/console/BrigadierCommandHighlighter.java -@@ -0,0 +1,70 @@ -+package io.papermc.paper.console; -+ -+import com.google.common.base.Suppliers; -+import com.mojang.brigadier.ParseResults; -+import com.mojang.brigadier.context.ParsedCommandNode; -+import com.mojang.brigadier.tree.LiteralCommandNode; -+import java.util.function.Supplier; -+import java.util.regex.Pattern; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.dedicated.DedicatedServer; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.jline.reader.Highlighter; -+import org.jline.reader.LineReader; -+import org.jline.utils.AttributedString; -+import org.jline.utils.AttributedStringBuilder; -+import org.jline.utils.AttributedStyle; -+ -+public final class BrigadierCommandHighlighter implements Highlighter { -+ private static final int[] COLORS = {AttributedStyle.CYAN, AttributedStyle.YELLOW, AttributedStyle.GREEN, AttributedStyle.MAGENTA, /* Client uses GOLD here, not BLUE, however there is no GOLD AttributedStyle. */ AttributedStyle.BLUE}; -+ private final Supplier commandSourceStack; -+ private final DedicatedServer server; -+ -+ public BrigadierCommandHighlighter(final @NonNull DedicatedServer server) { -+ this.server = server; -+ this.commandSourceStack = Suppliers.memoize(this.server::createCommandSourceStack); -+ } -+ -+ @Override -+ public AttributedString highlight(final @NonNull LineReader reader, final @NonNull String buffer) { -+ //noinspection ConstantConditions -+ if (this.server.overworld() == null) { // check if overworld is null, as worlds haven't been loaded yet -+ return new AttributedString(buffer, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ final AttributedStringBuilder builder = new AttributedStringBuilder(); -+ final ParseResults results = this.server.getCommands().getDispatcher().parse(BrigadierCommandCompleter.prepareStringReader(buffer), this.commandSourceStack.get()); -+ int pos = 0; -+ if (buffer.startsWith("/")) { -+ builder.append("/", AttributedStyle.DEFAULT); -+ pos = 1; -+ } -+ int component = -1; -+ for (final ParsedCommandNode node : results.getContext().getLastChild().getNodes()) { -+ if (node.getRange().getStart() >= buffer.length()) { -+ break; -+ } -+ final int start = node.getRange().getStart(); -+ final int end = Math.min(node.getRange().getEnd(), buffer.length()); -+ builder.append(buffer.substring(pos, start), AttributedStyle.DEFAULT); -+ if (node.getNode() instanceof LiteralCommandNode) { -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT); -+ } else { -+ if (++component >= COLORS.length) { -+ component = 0; -+ } -+ builder.append(buffer.substring(start, end), AttributedStyle.DEFAULT.foreground(COLORS[component])); -+ } -+ pos = end; -+ } -+ if (pos < buffer.length()) { -+ builder.append((buffer.substring(pos)), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); -+ } -+ return builder.toAttributedString(); -+ } -+ -+ @Override -+ public void setErrorPattern(final Pattern errorPattern) {} -+ -+ @Override -+ public void setErrorIndex(final int errorIndex) {} -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index ef02ceba53943d34bd45070297c72beedd6e1883..f812b0a2d5534a7c443361bd69cfc2fe2110ba9a 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -178,7 +178,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - thread.setDaemon(true); - thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); -- thread.start(); -+ // thread.start(); // Paper - moved down - DedicatedServer.LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); - if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); -@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.getPlayerList().loadAndSaveFiles(); // Must be after convertNames - // Paper end - moved up - org.spigotmc.WatchdogThread.doStart(org.spigotmc.SpigotConfig.timeoutTime, org.spigotmc.SpigotConfig.restartOnCrash); -+ thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized - io.papermc.paper.command.PaperCommands.registerCommands(this); - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java -index 14cd8ae69d9b25dc5edad4ff96ff4a9acb1f22cb..cd4ad8261e56365850068db1d83d6a8454026737 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); // Paper - } - - // Paper start - Change method signature for JLine update -@@ -64,7 +66,7 @@ public class ConsoleCommandCompleter implements Completer { - } - } - -- if (!completions.isEmpty()) { -+ if (false && !completions.isEmpty()) { - for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { - if (completion.suggestion().isEmpty()) { - continue; -@@ -80,6 +82,7 @@ public class ConsoleCommandCompleter implements Completer { - )); - } - } -+ this.addCompletions(reader, line, candidates, completions); - return; - } - -@@ -99,10 +102,12 @@ public class ConsoleCommandCompleter implements Completer { - try { - List offers = waitable.get(); - if (offers == null) { -+ this.addCompletions(reader, line, candidates, Collections.emptyList()); // Paper - return; // Paper - Method returns void - } - - // Paper start - JLine update -+ /* - for (String completion : offers) { - if (completion.isEmpty()) { - continue; -@@ -110,6 +115,8 @@ public class ConsoleCommandCompleter implements Completer { - - candidates.add(new Candidate(completion)); - } -+ */ -+ this.addCompletions(reader, line, candidates, offers.stream().map(com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion::completion).collect(java.util.stream.Collectors.toList())); - // Paper end - - // Paper start - JLine handles cursor now -@@ -138,5 +145,9 @@ public class ConsoleCommandCompleter implements Completer { - } - return false; - } -+ -+ private void addCompletions(final LineReader reader, final ParsedLine line, final List candidates, final List existing) { -+ this.brigadierCompleter.complete(reader, line, candidates, existing); -+ } - // Paper end - } diff --git a/patches/server/0616-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0616-Fix-PlayerItemConsumeEvent-cancelling-properly.patch new file mode 100644 index 0000000000..419a5f5e50 --- /dev/null +++ b/patches/server/0616-Fix-PlayerItemConsumeEvent-cancelling-properly.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Fri, 19 Mar 2021 00:33:15 -0500 +Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly + +When the active item is not cleared, the item is still readied +for use and will repeatedly trigger the PlayerItemConsumeEvent +till their item is switched. +This patch clears the active item when the event is cancelled + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index f774d25eaaa1b7966b16251619da80a2d5c9228c..d427cd90177b14062ea56dcf6fa5fedddcdbb624 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3777,6 +3777,7 @@ public abstract class LivingEntity extends Entity { + level.getCraftServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { ++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use + // Update client + ((ServerPlayer) this).getBukkitEntity().updateInventory(); + ((ServerPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/patches/server/0617-Add-bypass-host-check.patch b/patches/server/0617-Add-bypass-host-check.patch new file mode 100644 index 0000000000..85854dc71d --- /dev/null +++ b/patches/server/0617-Add-bypass-host-check.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 18 Apr 2021 21:27:01 +0100 +Subject: [PATCH] Add bypass host check + +Paper.bypassHostCheck + +Seriously, fix your firewalls. -.- + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 750fef0f5b908b776a7306e54653eba497b7c50b..53833fdd748098b662d4420a254401c0d3982e56 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -29,6 +29,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + private static final Component IGNORE_STATUS_REASON = Component.literal("Ignoring status request"); + private final MinecraftServer server; + private final Connection connection; ++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper + + public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { + this.server = server; +@@ -117,7 +118,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + if (!handledByEvent && proxyLogicEnabled) { + // Paper end + // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! +- if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { ++ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper + packet.hostName = split[0]; + connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); + connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); diff --git a/patches/server/0617-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0617-Fix-PlayerItemConsumeEvent-cancelling-properly.patch deleted file mode 100644 index 6079bd7fd6..0000000000 --- a/patches/server/0617-Fix-PlayerItemConsumeEvent-cancelling-properly.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Fri, 19 Mar 2021 00:33:15 -0500 -Subject: [PATCH] Fix PlayerItemConsumeEvent cancelling properly - -When the active item is not cleared, the item is still readied -for use and will repeatedly trigger the PlayerItemConsumeEvent -till their item is switched. -This patch clears the active item when the event is cancelled - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index ede6e560cf0fe7dadab481f0d27a5edc4218d8bd..644d71c024103c39d7532559c810038d687106e5 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3755,6 +3755,7 @@ public abstract class LivingEntity extends Entity { - level.getCraftServer().getPluginManager().callEvent(event); - - if (event.isCancelled()) { -+ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use - // Update client - ((ServerPlayer) this).getBukkitEntity().updateInventory(); - ((ServerPlayer) this).getBukkitEntity().updateScaledHealth(); diff --git a/patches/server/0618-Add-bypass-host-check.patch b/patches/server/0618-Add-bypass-host-check.patch deleted file mode 100644 index 85854dc71d..0000000000 --- a/patches/server/0618-Add-bypass-host-check.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 18 Apr 2021 21:27:01 +0100 -Subject: [PATCH] Add bypass host check - -Paper.bypassHostCheck - -Seriously, fix your firewalls. -.- - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 750fef0f5b908b776a7306e54653eba497b7c50b..53833fdd748098b662d4420a254401c0d3982e56 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -29,6 +29,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - private static final Component IGNORE_STATUS_REASON = Component.literal("Ignoring status request"); - private final MinecraftServer server; - private final Connection connection; -+ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper - - public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { - this.server = server; -@@ -117,7 +118,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - if (!handledByEvent && proxyLogicEnabled) { - // Paper end - // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! -- if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { -+ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - packet.hostName = split[0]; - connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); - connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); diff --git a/patches/server/0618-Set-area-affect-cloud-rotation.patch b/patches/server/0618-Set-area-affect-cloud-rotation.patch new file mode 100644 index 0000000000..08e992b45c --- /dev/null +++ b/patches/server/0618-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/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index bb5d22125b6cd4e60d2b7e2e00af158c9ea22cd9..84d84ceda1855bd1d11b2917c16fdb845a7600fe 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -919,6 +919,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + entity.moveTo(location.getX(), location.getY(), location.getZ()); + } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.AreaEffectCloud(world, x, y, z); ++ entity.moveTo(x, y, z, yaw, pitch); // Paper - Set area effect cloud Rotation + } else if (EvokerFangs.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.projectile.EvokerFangs(world, x, y, z, (float) Math.toRadians(yaw), 0, null); + } else if (Marker.class.isAssignableFrom(clazz)) { diff --git a/patches/server/0619-Set-area-affect-cloud-rotation.patch b/patches/server/0619-Set-area-affect-cloud-rotation.patch deleted file mode 100644 index 08e992b45c..0000000000 --- a/patches/server/0619-Set-area-affect-cloud-rotation.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index bb5d22125b6cd4e60d2b7e2e00af158c9ea22cd9..84d84ceda1855bd1d11b2917c16fdb845a7600fe 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -919,6 +919,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - entity.moveTo(location.getX(), location.getY(), location.getZ()); - } else if (AreaEffectCloud.class.isAssignableFrom(clazz)) { - entity = new net.minecraft.world.entity.AreaEffectCloud(world, x, y, z); -+ entity.moveTo(x, y, z, yaw, pitch); // Paper - Set area effect cloud Rotation - } else if (EvokerFangs.class.isAssignableFrom(clazz)) { - entity = new net.minecraft.world.entity.projectile.EvokerFangs(world, x, y, z, (float) Math.toRadians(yaw), 0, null); - } else if (Marker.class.isAssignableFrom(clazz)) { diff --git a/patches/server/0619-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0619-add-isDeeplySleeping-to-HumanEntity.patch new file mode 100644 index 0000000000..23d24c3698 --- /dev/null +++ b/patches/server/0619-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 b8ba4e278de5c9a591789928c21bff26dcec2fb7..44dab523264c594aa9c619e3ee2e0d7f93982ddc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -125,6 +125,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + } + } + ++ // Paper start ++ @Override ++ public boolean isDeeplySleeping() { ++ return getHandle().isSleepingLongEnough(); ++ } ++ // Paper end ++ + @Override + public int getSleepTicks() { + return this.getHandle().sleepCounter; diff --git a/patches/server/0620-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0620-add-consumeFuel-to-FurnaceBurnEvent.patch new file mode 100644 index 0000000000..0c37516dd7 --- /dev/null +++ b/patches/server/0620-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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +index 9a40839ccedd6a39c2b8755e29f31a4f626fe15d..4977f3fad3bfc12fd4c5f9fbe8beea2895247c57 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +@@ -363,7 +363,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) { + // CraftBukkit end + flag1 = true; +- if (flag3) { ++ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper + Item item = itemstack.getItem(); + + itemstack.shrink(1); diff --git a/patches/server/0620-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0620-add-isDeeplySleeping-to-HumanEntity.patch deleted file mode 100644 index 18ba539fd0..0000000000 --- a/patches/server/0620-add-isDeeplySleeping-to-HumanEntity.patch +++ /dev/null @@ -1,24 +0,0 @@ -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 acf609e8d880156ba980b701816c475166c27bdb..6149e74262ca6c4c00686a89c273c59bfaac3e05 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -122,6 +122,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - } - } - -+ // Paper start -+ @Override -+ public boolean isDeeplySleeping() { -+ return getHandle().isSleepingLongEnough(); -+ } -+ // Paper end -+ - @Override - public int getSleepTicks() { - return this.getHandle().sleepCounter; diff --git a/patches/server/0621-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0621-add-consumeFuel-to-FurnaceBurnEvent.patch deleted file mode 100644 index 0c37516dd7..0000000000 --- a/patches/server/0621-add-consumeFuel-to-FurnaceBurnEvent.patch +++ /dev/null @@ -1,19 +0,0 @@ -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/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 9a40839ccedd6a39c2b8755e29f31a4f626fe15d..4977f3fad3bfc12fd4c5f9fbe8beea2895247c57 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -363,7 +363,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) { - // CraftBukkit end - flag1 = true; -- if (flag3) { -+ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - Item item = itemstack.getItem(); - - itemstack.shrink(1); diff --git a/patches/server/0621-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0621-add-get-set-drop-chance-to-EntityEquipment.patch new file mode 100644 index 0000000000..9720fcdaf4 --- /dev/null +++ b/patches/server/0621-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 cb704cef3845727c465fe3ea7210a11545da56c8..6827979a5b270ced53b46ecb9eff548727dadb81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +@@ -244,6 +244,17 @@ public class CraftEntityEquipment implements EntityEquipment { + public void setBootsDropChance(float chance) { + this.setDropChance(net.minecraft.world.entity.EquipmentSlot.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(net.minecraft.world.entity.EquipmentSlot slot, float chance) { + Preconditions.checkArgument(this.entity.getHandle() instanceof Mob, "Cannot set drop chance for non-Mob entity"); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index 8bca88f7b7d310b29bdc851125e4cd03718a4fb9..d6a228473ca6f425757683a4b17b035a53ab117f 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/0622-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0622-add-get-set-drop-chance-to-EntityEquipment.patch deleted file mode 100644 index 9720fcdaf4..0000000000 --- a/patches/server/0622-add-get-set-drop-chance-to-EntityEquipment.patch +++ /dev/null @@ -1,48 +0,0 @@ -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 cb704cef3845727c465fe3ea7210a11545da56c8..6827979a5b270ced53b46ecb9eff548727dadb81 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java -@@ -244,6 +244,17 @@ public class CraftEntityEquipment implements EntityEquipment { - public void setBootsDropChance(float chance) { - this.setDropChance(net.minecraft.world.entity.EquipmentSlot.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(net.minecraft.world.entity.EquipmentSlot slot, float chance) { - Preconditions.checkArgument(this.entity.getHandle() instanceof Mob, "Cannot set drop chance for non-Mob entity"); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -index 8bca88f7b7d310b29bdc851125e4cd03718a4fb9..d6a228473ca6f425757683a4b17b035a53ab117f 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/0622-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0622-fix-PigZombieAngerEvent-cancellation.patch new file mode 100644 index 0000000000..df4e7b0b17 --- /dev/null +++ b/patches/server/0622-fix-PigZombieAngerEvent-cancellation.patch @@ -0,0 +1,35 @@ +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/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +index 58d49da3f95f8cd5d61da199ddba1100c411276e..d0567234d1261227d29bb254f959604dc91b3c72 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +@@ -52,6 +52,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + private static final int ALERT_RANGE_Y = 10; + private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); + private int ticksUntilNextAlert; ++ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper + + public ZombifiedPiglin(EntityType type, Level world) { + super(type, world); +@@ -72,7 +73,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + protected void addBehaviourGoals() { + this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); +- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); ++ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - assign field + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); + this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); + } +@@ -175,6 +176,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { + this.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.setPersistentAngerTarget(null); ++ pathfinderGoalHurtByTarget.stop(); // Paper - clear goalTargets to fix cancellation + return; + } + this.setRemainingPersistentAngerTime(event.getNewAnger()); diff --git a/patches/server/0623-Fix-checkReach-check-for-Shulker-boxes.patch b/patches/server/0623-Fix-checkReach-check-for-Shulker-boxes.patch new file mode 100644 index 0000000000..e63651a68f --- /dev/null +++ b/patches/server/0623-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/ShulkerBoxMenu.java b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java +index e9cf1ebdfdd363b588b0f7679f1533bc9c2eb844..3991ef6319030e155cb3363f73e67a2bb6ab384f 100644 +--- a/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java +@@ -66,6 +66,7 @@ public class ShulkerBoxMenu extends AbstractContainerMenu { + + @Override + public boolean stillValid(Player player) { ++ if (!this.checkReachable) return true; // Paper - Add reachable override for ContainerShulkerBox + return this.container.stillValid(player); + } + diff --git a/patches/server/0623-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0623-fix-PigZombieAngerEvent-cancellation.patch deleted file mode 100644 index df4e7b0b17..0000000000 --- a/patches/server/0623-fix-PigZombieAngerEvent-cancellation.patch +++ /dev/null @@ -1,35 +0,0 @@ -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/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 58d49da3f95f8cd5d61da199ddba1100c411276e..d0567234d1261227d29bb254f959604dc91b3c72 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -52,6 +52,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - private static final int ALERT_RANGE_Y = 10; - private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6); - private int ticksUntilNextAlert; -+ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - - public ZombifiedPiglin(EntityType type, Level world) { - super(type, world); -@@ -72,7 +73,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - protected void addBehaviourGoals() { - this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); -- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); -+ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - assign field - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); - this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); - } -@@ -175,6 +176,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.setPersistentAngerTarget(null); -+ pathfinderGoalHurtByTarget.stop(); // Paper - clear goalTargets to fix cancellation - return; - } - this.setRemainingPersistentAngerTime(event.getNewAnger()); diff --git a/patches/server/0624-Fix-checkReach-check-for-Shulker-boxes.patch b/patches/server/0624-Fix-checkReach-check-for-Shulker-boxes.patch deleted file mode 100644 index e63651a68f..0000000000 --- a/patches/server/0624-Fix-checkReach-check-for-Shulker-boxes.patch +++ /dev/null @@ -1,18 +0,0 @@ -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/ShulkerBoxMenu.java b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java -index e9cf1ebdfdd363b588b0f7679f1533bc9c2eb844..3991ef6319030e155cb3363f73e67a2bb6ab384f 100644 ---- a/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ShulkerBoxMenu.java -@@ -66,6 +66,7 @@ public class ShulkerBoxMenu extends AbstractContainerMenu { - - @Override - public boolean stillValid(Player player) { -+ if (!this.checkReachable) return true; // Paper - Add reachable override for ContainerShulkerBox - return this.container.stillValid(player); - } - diff --git a/patches/server/0624-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0624-fix-PlayerItemHeldEvent-firing-twice.patch new file mode 100644 index 0000000000..3840306aa1 --- /dev/null +++ b/patches/server/0624-fix-PlayerItemHeldEvent-firing-twice.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Thu, 22 Apr 2021 19:02:07 -0700 +Subject: [PATCH] fix PlayerItemHeldEvent firing twice + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 63649e0d5547d1b904a6605868dfad52214c7b3c..917aa2bd63db9a63c75267564d0c3602b0f01392 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2054,6 +2054,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (this.player.isImmobile()) return; // CraftBukkit + if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change + PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); + this.cserver.getPluginManager().callEvent(event); + if (event.isCancelled()) { diff --git a/patches/server/0625-Added-PlayerDeepSleepEvent.patch b/patches/server/0625-Added-PlayerDeepSleepEvent.patch new file mode 100644 index 0000000000..b7989ecad1 --- /dev/null +++ b/patches/server/0625-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/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 0960e5ecc25fad3eb46a871c2749dd176b812460..a6bd94ed379ef1ab0ffe71183aef3cf3061fd092 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -254,6 +254,11 @@ public abstract class Player extends LivingEntity { + + if (this.isSleeping()) { + ++this.sleepCounter; ++ // Paper start ++ if (this.sleepCounter == 100) { ++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { this.sleepCounter = Integer.MIN_VALUE; } ++ } ++ // Paper end + if (this.sleepCounter > 100) { + this.sleepCounter = 100; + } diff --git a/patches/server/0625-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0625-fix-PlayerItemHeldEvent-firing-twice.patch deleted file mode 100644 index ad85704974..0000000000 --- a/patches/server/0625-fix-PlayerItemHeldEvent-firing-twice.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Thu, 22 Apr 2021 19:02:07 -0700 -Subject: [PATCH] fix PlayerItemHeldEvent firing twice - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ed1b8507c98cbc11ec6dd3ceae2800bd69d99cf2..35b906d74a4cc03a5878cedff2ee9e694bb03ad4 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2053,6 +2053,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (this.player.isImmobile()) return; // CraftBukkit - if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { -+ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change - PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); - this.cserver.getPluginManager().callEvent(event); - if (event.isCancelled()) { diff --git a/patches/server/0626-Added-PlayerDeepSleepEvent.patch b/patches/server/0626-Added-PlayerDeepSleepEvent.patch deleted file mode 100644 index b7989ecad1..0000000000 --- a/patches/server/0626-Added-PlayerDeepSleepEvent.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 21 Apr 2021 15:58:19 -0700 -Subject: [PATCH] Added PlayerDeepSleepEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 0960e5ecc25fad3eb46a871c2749dd176b812460..a6bd94ed379ef1ab0ffe71183aef3cf3061fd092 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -254,6 +254,11 @@ public abstract class Player extends LivingEntity { - - if (this.isSleeping()) { - ++this.sleepCounter; -+ // Paper start -+ if (this.sleepCounter == 100) { -+ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) { this.sleepCounter = Integer.MIN_VALUE; } -+ } -+ // Paper end - if (this.sleepCounter > 100) { - this.sleepCounter = 100; - } diff --git a/patches/server/0626-More-World-API.patch b/patches/server/0626-More-World-API.patch new file mode 100644 index 0000000000..fb9c49f999 --- /dev/null +++ b/patches/server/0626-More-World-API.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 7 Jul 2020 10:52:34 -0700 +Subject: [PATCH] More World API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 42423b020f9c2ef2ba025b444be076c38314c721..9ca188959484041f53a078963cb79d68fd2a4f48 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -2045,6 +2045,65 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value(), this.getHandle().registryAccess()), new Location(this, found.getFirst().getX(), found.getFirst().getY(), found.getFirst().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) { ++ BlockPos originPos = new BlockPos(origin.getX(), origin.getY(), origin.getZ()); ++ BlockPos nearest = getHandle().findClosestBiome3d( holder -> holder.is(CraftNamespacedKey.toMinecraft(biome.getKey())), originPos, radius, step, step).getFirst(); ++ return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); ++ } ++ ++ @Override ++ public boolean isUltrawarm() { ++ return getHandle().dimensionType().ultraWarm(); ++ } ++ ++ @Override ++ public double getCoordinateScale() { ++ return getHandle().dimensionType().coordinateScale(); ++ } ++ ++ @Override ++ public boolean hasSkylight() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean hasBedrockCeiling() { ++ return getHandle().dimensionType().hasSkyLight(); ++ } ++ ++ @Override ++ public boolean doesBedWork() { ++ return getHandle().dimensionType().bedWorks(); ++ } ++ ++ @Override ++ public boolean doesRespawnAnchorWork() { ++ return getHandle().dimensionType().respawnAnchorWorks(); ++ } ++ ++ @Override ++ public boolean isFixedTime() { ++ return getHandle().dimensionType().hasFixedTime(); ++ } ++ ++ @Override ++ public Collection getInfiniburn() { ++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.Registry.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftMagicNumbers.getMaterial(blockHolder.value()))); ++ } ++ ++ @Override ++ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.Registry.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); ++ } ++ // Paper end ++ + @Override + public Raid locateNearestRaid(Location location, int radius) { + Validate.notNull(location, "Location cannot be null"); +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +index 3071ac1ac0e733d73dade49597a39f7d156bbc04..60c4afd5cad66ffb0cfb5c1fa9857def593813ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java +@@ -12,4 +12,13 @@ public final class CraftVector { + public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { + return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); + } ++ // Paper start ++ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { ++ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); ++ } ++ ++ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { ++ return new net.minecraft.core.BlockPos(bukkit.getX(), bukkit.getY(), bukkit.getZ()); ++ } ++ // Paper end + } diff --git a/patches/server/0627-Added-PlayerBedFailEnterEvent.patch b/patches/server/0627-Added-PlayerBedFailEnterEvent.patch new file mode 100644 index 0000000000..ad6eaeb223 --- /dev/null +++ b/patches/server/0627-Added-PlayerBedFailEnterEvent.patch @@ -0,0 +1,36 @@ +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/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java +index 006541834811c20fbdef42d4ad299da1dc046d33..654a859a37bf991c7a7fa8a44a3d20f8feb223db 100644 +--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java +@@ -110,14 +110,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + BlockPos finalblockposition = pos; + // CraftBukkit end + player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { ++ // Paper start - PlayerBedFailEnterEvent ++ if (entityhuman_enumbedresult != null) { ++ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.VALUES[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage())); ++ if (!event.callEvent()) { ++ return; ++ } ++ // Paper end + // CraftBukkit start - handling bed explosion from below here +- if (!world.dimensionType().bedWorks()) { ++ if (event.getWillExplode()) { // Paper + this.explodeBed(finaliblockdata, world, finalblockposition); + } else + // CraftBukkit end + if (entityhuman_enumbedresult.getMessage() != null) { +- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); ++ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper ++ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper + } ++ } // Paper + + }); + return InteractionResult.SUCCESS; diff --git a/patches/server/0627-More-World-API.patch b/patches/server/0627-More-World-API.patch deleted file mode 100644 index fb9c49f999..0000000000 --- a/patches/server/0627-More-World-API.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 7 Jul 2020 10:52:34 -0700 -Subject: [PATCH] More World API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 42423b020f9c2ef2ba025b444be076c38314c721..9ca188959484041f53a078963cb79d68fd2a4f48 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2045,6 +2045,65 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value(), this.getHandle().registryAccess()), new Location(this, found.getFirst().getX(), found.getFirst().getY(), found.getFirst().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) { -+ BlockPos originPos = new BlockPos(origin.getX(), origin.getY(), origin.getZ()); -+ BlockPos nearest = getHandle().findClosestBiome3d( holder -> holder.is(CraftNamespacedKey.toMinecraft(biome.getKey())), originPos, radius, step, step).getFirst(); -+ return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); -+ } -+ -+ @Override -+ public boolean isUltrawarm() { -+ return getHandle().dimensionType().ultraWarm(); -+ } -+ -+ @Override -+ public double getCoordinateScale() { -+ return getHandle().dimensionType().coordinateScale(); -+ } -+ -+ @Override -+ public boolean hasSkylight() { -+ return getHandle().dimensionType().hasSkyLight(); -+ } -+ -+ @Override -+ public boolean hasBedrockCeiling() { -+ return getHandle().dimensionType().hasSkyLight(); -+ } -+ -+ @Override -+ public boolean doesBedWork() { -+ return getHandle().dimensionType().bedWorks(); -+ } -+ -+ @Override -+ public boolean doesRespawnAnchorWork() { -+ return getHandle().dimensionType().respawnAnchorWorks(); -+ } -+ -+ @Override -+ public boolean isFixedTime() { -+ return getHandle().dimensionType().hasFixedTime(); -+ } -+ -+ @Override -+ public Collection getInfiniburn() { -+ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.Registry.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftMagicNumbers.getMaterial(blockHolder.value()))); -+ } -+ -+ @Override -+ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.Registry.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); -+ } -+ // Paper end -+ - @Override - public Raid locateNearestRaid(Location location, int radius) { - Validate.notNull(location, "Location cannot be null"); -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -index 3071ac1ac0e733d73dade49597a39f7d156bbc04..60c4afd5cad66ffb0cfb5c1fa9857def593813ae 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java -@@ -12,4 +12,13 @@ public final class CraftVector { - public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) { - return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ()); - } -+ // Paper start -+ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) { -+ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ()); -+ } -+ -+ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) { -+ return new net.minecraft.core.BlockPos(bukkit.getX(), bukkit.getY(), bukkit.getZ()); -+ } -+ // Paper end - } diff --git a/patches/server/0628-Added-PlayerBedFailEnterEvent.patch b/patches/server/0628-Added-PlayerBedFailEnterEvent.patch deleted file mode 100644 index ad6eaeb223..0000000000 --- a/patches/server/0628-Added-PlayerBedFailEnterEvent.patch +++ /dev/null @@ -1,36 +0,0 @@ -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/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index 006541834811c20fbdef42d4ad299da1dc046d33..654a859a37bf991c7a7fa8a44a3d20f8feb223db 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -110,14 +110,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - BlockPos finalblockposition = pos; - // CraftBukkit end - player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> { -+ // Paper start - PlayerBedFailEnterEvent -+ if (entityhuman_enumbedresult != null) { -+ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.VALUES[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage())); -+ if (!event.callEvent()) { -+ return; -+ } -+ // Paper end - // CraftBukkit start - handling bed explosion from below here -- if (!world.dimensionType().bedWorks()) { -+ if (event.getWillExplode()) { // Paper - this.explodeBed(finaliblockdata, world, finalblockposition); - } else - // CraftBukkit end - if (entityhuman_enumbedresult.getMessage() != null) { -- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true); -+ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper -+ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - } -+ } // Paper - - }); - return InteractionResult.SUCCESS; diff --git a/patches/server/0628-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0628-Implement-methods-to-convert-between-Component-and-B.patch new file mode 100644 index 0000000000..3e1d661a26 --- /dev/null +++ b/patches/server/0628-Implement-methods-to-convert-between-Component-and-B.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 24 Apr 2021 02:09:32 -0700 +Subject: [PATCH] Implement methods to convert between Component and + Brigadier's Message + + +diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.brigadier; ++ ++import com.mojang.brigadier.Message; ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.minecraft.network.chat.ComponentUtils; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++import static java.util.Objects.requireNonNull; ++ ++public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { ++ INSTANCE; ++ ++ PaperBrigadierProviderImpl() { ++ PaperBrigadierProvider.initialize(this); ++ } ++ ++ @Override ++ public @NonNull Message message(final @NonNull ComponentLike componentLike) { ++ requireNonNull(componentLike, "componentLike"); ++ return PaperAdventure.asVanilla(componentLike.asComponent()); ++ } ++ ++ @Override ++ public @NonNull Component componentFromMessage(final @NonNull Message message) { ++ requireNonNull(message, "message"); ++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index f812b0a2d5534a7c443361bd69cfc2fe2110ba9a..2c215e2080f00d6c875fbde92fd2c1c051d0cf98 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -214,6 +214,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + io.papermc.paper.command.PaperCommands.registerCommands(this); + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now ++ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider + // Paper end + + this.setPvpAllowed(dedicatedserverproperties.pvp); diff --git a/patches/server/0629-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch b/patches/server/0629-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch new file mode 100644 index 0000000000..e51b27c728 --- /dev/null +++ b/patches/server/0629-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Fri, 23 Apr 2021 22:42:42 +0100 +Subject: [PATCH] Fix anchor respawn acting as a bed respawn from the end + portal + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 8eae2de23226700b7186b5e673f7f776854a49bc..6bdcfe46fd447cfdf2e57bb60250984f02c195da 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -858,6 +858,7 @@ public abstract class PlayerList { + + // Paper start + boolean isBedSpawn = false; ++ boolean isAnchorSpawn = false; + boolean isRespawn = false; + boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 + // Paper end +@@ -878,6 +879,7 @@ public abstract class PlayerList { + if (optional.isPresent()) { + BlockState iblockdata = worldserver1.getBlockState(blockposition); + boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR); ++ isAnchorSpawn = flag3; // Paper - Fix anchor respawn acting as a bed respawn from the end portal + Vec3 vec3d = (Vec3) optional.get(); + float f1; + +@@ -906,7 +908,7 @@ public abstract class PlayerList { + } + + Player respawnPlayer = entityplayer1.getBukkitEntity(); +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !flag2, flag2); ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal + this.cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0629-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0629-Implement-methods-to-convert-between-Component-and-B.patch deleted file mode 100644 index 3e1d661a26..0000000000 --- a/patches/server/0629-Implement-methods-to-convert-between-Component-and-B.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 24 Apr 2021 02:09:32 -0700 -Subject: [PATCH] Implement methods to convert between Component and - Brigadier's Message - - -diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.brigadier; -+ -+import com.mojang.brigadier.Message; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.minecraft.network.chat.ComponentUtils; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+import static java.util.Objects.requireNonNull; -+ -+public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider { -+ INSTANCE; -+ -+ PaperBrigadierProviderImpl() { -+ PaperBrigadierProvider.initialize(this); -+ } -+ -+ @Override -+ public @NonNull Message message(final @NonNull ComponentLike componentLike) { -+ requireNonNull(componentLike, "componentLike"); -+ return PaperAdventure.asVanilla(componentLike.asComponent()); -+ } -+ -+ @Override -+ public @NonNull Component componentFromMessage(final @NonNull Message message) { -+ requireNonNull(message, "message"); -+ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message)); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index f812b0a2d5534a7c443361bd69cfc2fe2110ba9a..2c215e2080f00d6c875fbde92fd2c1c051d0cf98 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -214,6 +214,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.command.PaperCommands.registerCommands(this); - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now -+ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider - // Paper end - - this.setPvpAllowed(dedicatedserverproperties.pvp); diff --git a/patches/server/0630-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch b/patches/server/0630-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch deleted file mode 100644 index e51b27c728..0000000000 --- a/patches/server/0630-Fix-anchor-respawn-acting-as-a-bed-respawn-from-the-.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Fri, 23 Apr 2021 22:42:42 +0100 -Subject: [PATCH] Fix anchor respawn acting as a bed respawn from the end - portal - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 8eae2de23226700b7186b5e673f7f776854a49bc..6bdcfe46fd447cfdf2e57bb60250984f02c195da 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -858,6 +858,7 @@ public abstract class PlayerList { - - // Paper start - boolean isBedSpawn = false; -+ boolean isAnchorSpawn = false; - boolean isRespawn = false; - boolean isLocAltered = false; // Paper - Fix SPIGOT-5989 - // Paper end -@@ -878,6 +879,7 @@ public abstract class PlayerList { - if (optional.isPresent()) { - BlockState iblockdata = worldserver1.getBlockState(blockposition); - boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR); -+ isAnchorSpawn = flag3; // Paper - Fix anchor respawn acting as a bed respawn from the end portal - Vec3 vec3d = (Vec3) optional.get(); - float f1; - -@@ -906,7 +908,7 @@ public abstract class PlayerList { - } - - Player respawnPlayer = entityplayer1.getBukkitEntity(); -- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !flag2, flag2); -+ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal - this.cserver.getPluginManager().callEvent(respawnEvent); - // Spigot Start - if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0630-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0630-Introduce-beacon-activation-deactivation-events.patch new file mode 100644 index 0000000000..b3f265d709 --- /dev/null +++ b/patches/server/0630-Introduce-beacon-activation-deactivation-events.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spyridon Pagkalos +Date: Thu, 25 Mar 2021 20:28:04 +0200 +Subject: [PATCH] Introduce beacon activation/deactivation events + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +index 812d21393ed2ed0623b7445585f1b0b3dcefb55d..5f6eeb36f57bd342b18590c8f0ffb668d2bf273c 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +@@ -210,6 +210,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); + } + } ++ // Paper start - beacon activation/deactivation events ++ if (i1 <= 0 && blockEntity.levels > 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); ++ } else if (i1 > 0 && blockEntity.levels <= 0) { ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ } ++ // Paper end + + if (blockEntity.lastCheckY >= l) { + blockEntity.lastCheckY = world.getMinBuildHeight() - 1; +@@ -267,6 +276,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { + + @Override + public void setRemoved() { ++ // Paper start - BeaconDeactivatedEvent ++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); ++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); ++ // Paper end + BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); + super.setRemoved(); + } diff --git a/patches/server/0631-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0631-Introduce-beacon-activation-deactivation-events.patch deleted file mode 100644 index b3f265d709..0000000000 --- a/patches/server/0631-Introduce-beacon-activation-deactivation-events.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spyridon Pagkalos -Date: Thu, 25 Mar 2021 20:28:04 +0200 -Subject: [PATCH] Introduce beacon activation/deactivation events - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 812d21393ed2ed0623b7445585f1b0b3dcefb55d..5f6eeb36f57bd342b18590c8f0ffb668d2bf273c 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -210,6 +210,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } - } -+ // Paper start - beacon activation/deactivation events -+ if (i1 <= 0 && blockEntity.levels > 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent(); -+ } else if (i1 > 0 && blockEntity.levels <= 0) { -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ } -+ // Paper end - - if (blockEntity.lastCheckY >= l) { - blockEntity.lastCheckY = world.getMinBuildHeight() - 1; -@@ -267,6 +276,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider { - - @Override - public void setRemoved() { -+ // Paper start - BeaconDeactivatedEvent -+ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition); -+ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent(); -+ // Paper end - BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE); - super.setRemoved(); - } diff --git a/patches/server/0631-add-RespawnFlags-to-PlayerRespawnEvent.patch b/patches/server/0631-add-RespawnFlags-to-PlayerRespawnEvent.patch new file mode 100644 index 0000000000..d03dd9a291 --- /dev/null +++ b/patches/server/0631-add-RespawnFlags-to-PlayerRespawnEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 22 Apr 2021 17:17:47 -0700 +Subject: [PATCH] add RespawnFlags to PlayerRespawnEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 917aa2bd63db9a63c75267564d0c3602b0f01392..161b5d6f0d420ac7b6ed112d1b03d42c3aaec421 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2894,7 +2894,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true); ++ this.player = this.server.getPlayerList().respawn(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - add isEndCreditsRespawn argument + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); + } else { + if (this.player.getHealth() > 0.0F) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 6bdcfe46fd447cfdf2e57bb60250984f02c195da..977a23684b061d7390f70f8754a1d879d7d7075a 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -817,6 +817,12 @@ public abstract class PlayerList { + } + + public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation) { ++ // Paper start ++ return respawn(entityplayer, worldserver, flag, location, avoidSuffocation, new org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag[0]); ++ } ++ ++ public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { ++ // Paper end + entityplayer.stopRiding(); // CraftBukkit + this.players.remove(entityplayer); + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -908,7 +914,7 @@ public abstract class PlayerList { + } + + Player respawnPlayer = entityplayer1.getBukkitEntity(); +- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal ++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn, com.google.common.collect.ImmutableSet.builder().add(respawnFlags)); // Paper - Fix anchor respawn acting as a bed respawn from the end portal + this.cserver.getPluginManager().callEvent(respawnEvent); + // Spigot Start + if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0632-Add-Channel-initialization-listeners.patch b/patches/server/0632-Add-Channel-initialization-listeners.patch new file mode 100644 index 0000000000..fbea93e057 --- /dev/null +++ b/patches/server/0632-Add-Channel-initialization-listeners.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 29 Apr 2021 21:19:33 +0200 +Subject: [PATCH] Add Channel initialization listeners + + +diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java +@@ -0,0 +1,15 @@ ++package io.papermc.paper.network; ++ ++import io.netty.channel.Channel; ++import org.checkerframework.checker.nullness.qual.NonNull; ++ ++/** ++ * Internal API to register channel initialization listeners. ++ *

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

++ * This is not officially supported API and we make no guarantees to the existence or state of this class. ++ */ ++public final class ChannelInitializeListenerHolder { ++ ++ private static final Map LISTENERS = new HashMap<>(); ++ private static final Map IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS); ++ ++ private ChannelInitializeListenerHolder() { ++ } ++ ++ /** ++ * Registers whether an initialization listener is registered under the given key. ++ * ++ * @param key key ++ * @return whether an initialization listener is registered under the given key ++ */ ++ public static boolean hasListener(@NonNull Key key) { ++ return LISTENERS.containsKey(key); ++ } ++ ++ /** ++ * Registers a channel initialization listener called after ServerConnection is initialized. ++ * ++ * @param key key ++ * @param listener initialization listeners ++ */ ++ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) { ++ LISTENERS.put(key, listener); ++ } ++ ++ /** ++ * Removes and returns an initialization listener registered by the given key if present. ++ * ++ * @param key key ++ * @return removed initialization listener if present ++ */ ++ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) { ++ return LISTENERS.remove(key); ++ } ++ ++ /** ++ * Returns an immutable map of registered initialization listeners. ++ * ++ * @return immutable map of registered initialization listeners ++ */ ++ public static @NonNull Map getListeners() { ++ return IMMUTABLE_VIEW; ++ } ++ ++ /** ++ * Calls the registered listeners with the given channel. ++ * ++ * @param channel channel ++ */ ++ public static void callListeners(@NonNull Channel channel) { ++ for (ChannelInitializeListener listener : LISTENERS.values()) { ++ listener.afterInitChannel(channel); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/network/ConnectionEvent.java b/src/main/java/io/papermc/paper/network/ConnectionEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d7e7db9e37ef0183c32b217bd944fb4f41ab83a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/network/ConnectionEvent.java +@@ -0,0 +1,10 @@ ++package io.papermc.paper.network; ++ ++/** ++ * Internal connection pipeline events. ++ */ ++public enum ConnectionEvent { ++ ++ COMPRESSION_THRESHOLD_SET, ++ COMPRESSION_DISABLED ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index dd81751f64695180331b82225ac878913afe4513..86cc291b5b14523d57c84f8ebd6ba9b9c3b0d1a6 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -567,6 +567,7 @@ public class Connection extends SimpleChannelInboundHandler> { + } else { + this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper + } else { + if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { + this.channel.pipeline().remove("decompress"); +@@ -575,6 +576,7 @@ public class Connection extends SimpleChannelInboundHandler> { + if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { + this.channel.pipeline().remove("compress"); + } ++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper + } + + } +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 1b38326c9a709536dc4cccf9af93aede98a1a782..83af90fb0dcb4b1a5a68f655cf66d101b472e8e7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -114,6 +114,7 @@ public class ServerConnectionListener { + pending.add((Connection) object); // Paper + channel.pipeline().addLast("packet_handler", (ChannelHandler) object); + ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); ++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper + } + }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit + } diff --git a/patches/server/0632-add-RespawnFlags-to-PlayerRespawnEvent.patch b/patches/server/0632-add-RespawnFlags-to-PlayerRespawnEvent.patch deleted file mode 100644 index f6c9a84995..0000000000 --- a/patches/server/0632-add-RespawnFlags-to-PlayerRespawnEvent.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 22 Apr 2021 17:17:47 -0700 -Subject: [PATCH] add RespawnFlags to PlayerRespawnEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a65ca51125de3935e610f9f5fdb047268d0c0102..c78d2aa1d4c2066716e274a26496ddd8eaf1be0a 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2890,7 +2890,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - case PERFORM_RESPAWN: - if (this.player.wonGame) { - this.player.wonGame = false; -- this.player = this.server.getPlayerList().respawn(this.player, true); -+ this.player = this.server.getPlayerList().respawn(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - add isEndCreditsRespawn argument - CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); - } else { - if (this.player.getHealth() > 0.0F) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 6bdcfe46fd447cfdf2e57bb60250984f02c195da..977a23684b061d7390f70f8754a1d879d7d7075a 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -817,6 +817,12 @@ public abstract class PlayerList { - } - - public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation) { -+ // Paper start -+ return respawn(entityplayer, worldserver, flag, location, avoidSuffocation, new org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag[0]); -+ } -+ -+ public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { -+ // Paper end - entityplayer.stopRiding(); // CraftBukkit - this.players.remove(entityplayer); - this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -908,7 +914,7 @@ public abstract class PlayerList { - } - - Player respawnPlayer = entityplayer1.getBukkitEntity(); -- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn); // Paper - Fix anchor respawn acting as a bed respawn from the end portal -+ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn && !isAnchorSpawn, isAnchorSpawn, com.google.common.collect.ImmutableSet.builder().add(respawnFlags)); // Paper - Fix anchor respawn acting as a bed respawn from the end portal - this.cserver.getPluginManager().callEvent(respawnEvent); - // Spigot Start - if (entityplayer.connection.isDisconnected()) { diff --git a/patches/server/0633-Add-Channel-initialization-listeners.patch b/patches/server/0633-Add-Channel-initialization-listeners.patch deleted file mode 100644 index fbea93e057..0000000000 --- a/patches/server/0633-Add-Channel-initialization-listeners.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 29 Apr 2021 21:19:33 +0200 -Subject: [PATCH] Add Channel initialization listeners - - -diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java -new file mode 100644 -index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java -@@ -0,0 +1,15 @@ -+package io.papermc.paper.network; -+ -+import io.netty.channel.Channel; -+import org.checkerframework.checker.nullness.qual.NonNull; -+ -+/** -+ * Internal API to register channel initialization listeners. -+ *

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

-+ * This is not officially supported API and we make no guarantees to the existence or state of this class. -+ */ -+public final class ChannelInitializeListenerHolder { -+ -+ private static final Map LISTENERS = new HashMap<>(); -+ private static final Map IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS); -+ -+ private ChannelInitializeListenerHolder() { -+ } -+ -+ /** -+ * Registers whether an initialization listener is registered under the given key. -+ * -+ * @param key key -+ * @return whether an initialization listener is registered under the given key -+ */ -+ public static boolean hasListener(@NonNull Key key) { -+ return LISTENERS.containsKey(key); -+ } -+ -+ /** -+ * Registers a channel initialization listener called after ServerConnection is initialized. -+ * -+ * @param key key -+ * @param listener initialization listeners -+ */ -+ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) { -+ LISTENERS.put(key, listener); -+ } -+ -+ /** -+ * Removes and returns an initialization listener registered by the given key if present. -+ * -+ * @param key key -+ * @return removed initialization listener if present -+ */ -+ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) { -+ return LISTENERS.remove(key); -+ } -+ -+ /** -+ * Returns an immutable map of registered initialization listeners. -+ * -+ * @return immutable map of registered initialization listeners -+ */ -+ public static @NonNull Map getListeners() { -+ return IMMUTABLE_VIEW; -+ } -+ -+ /** -+ * Calls the registered listeners with the given channel. -+ * -+ * @param channel channel -+ */ -+ public static void callListeners(@NonNull Channel channel) { -+ for (ChannelInitializeListener listener : LISTENERS.values()) { -+ listener.afterInitChannel(channel); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/network/ConnectionEvent.java b/src/main/java/io/papermc/paper/network/ConnectionEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d7e7db9e37ef0183c32b217bd944fb4f41ab83a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/network/ConnectionEvent.java -@@ -0,0 +1,10 @@ -+package io.papermc.paper.network; -+ -+/** -+ * Internal connection pipeline events. -+ */ -+public enum ConnectionEvent { -+ -+ COMPRESSION_THRESHOLD_SET, -+ COMPRESSION_DISABLED -+} -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index dd81751f64695180331b82225ac878913afe4513..86cc291b5b14523d57c84f8ebd6ba9b9c3b0d1a6 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -567,6 +567,7 @@ public class Connection extends SimpleChannelInboundHandler> { - } else { - this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); - } -+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - } else { - if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { - this.channel.pipeline().remove("decompress"); -@@ -575,6 +576,7 @@ public class Connection extends SimpleChannelInboundHandler> { - if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { - this.channel.pipeline().remove("compress"); - } -+ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - } - - } -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 1b38326c9a709536dc4cccf9af93aede98a1a782..83af90fb0dcb4b1a5a68f655cf66d101b472e8e7 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -114,6 +114,7 @@ public class ServerConnectionListener { - pending.add((Connection) object); // Paper - channel.pipeline().addLast("packet_handler", (ChannelHandler) object); - ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); -+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - } - }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit - } diff --git a/patches/server/0633-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0633-Send-empty-commands-if-tab-completion-is-disabled.patch new file mode 100644 index 0000000000..e31efa98f9 --- /dev/null +++ b/patches/server/0633-Send-empty-commands-if-tab-completion-is-disabled.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 26 Apr 2021 01:27:08 +0100 +Subject: [PATCH] Send empty commands if tab completion is disabled + + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 6ad3fe4718a0db17ad6115753e533bf069ce57c6..9c0b2679964f864671ff4041163d1065c8d9cf84 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -358,7 +358,12 @@ public class Commands { + } + + public void sendCommands(ServerPlayer player) { +- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot ++ // Paper start - Send empty commands if tab completion is disabled ++ if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) { //return; // Spigot ++ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); ++ return; ++ } ++ // Paper end + // CraftBukkit start + // Register Vanilla commands into builtRoot as before + // Paper start - Async command map building diff --git a/patches/server/0634-Add-more-WanderingTrader-API.patch b/patches/server/0634-Add-more-WanderingTrader-API.patch new file mode 100644 index 0000000000..65d351d6df --- /dev/null +++ b/patches/server/0634-Add-more-WanderingTrader-API.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Thu, 6 May 2021 14:56:43 +0100 +Subject: [PATCH] Add more WanderingTrader API + + +diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +index abb2c5c4ac481c7529aa29322babb1929235e15a..86e1ba898d6b92735258419fa74352e5116226dc 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java ++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +@@ -56,6 +56,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + @Nullable + private BlockPos wanderTarget; + private int despawnDelay; ++ // Paper start - Add more WanderingTrader API ++ public boolean canDrinkPotion = true; ++ public boolean canDrinkMilk = true; ++ // Paper end + + public WanderingTrader(EntityType type, Level world) { + super(type, world); +@@ -66,10 +70,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill + protected void registerGoals() { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { +- return this.level.isNight() && !entityvillagertrader.isInvisible(); ++ return this.canDrinkPotion && this.level.isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { +- return this.level.isDay() && entityvillagertrader.isInvisible(); ++ return canDrinkMilk && this.level.isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + })); + this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); + this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +index 65b052567d1d855021d7273672b4354aba0a42a4..fa7107593b20e0151d8d67104e4a92dcc697d461 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +@@ -34,4 +34,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande + public void setDespawnDelay(int despawnDelay) { + this.getHandle().setDespawnDelay(despawnDelay); + } ++ ++ // Paper start - Add more WanderingTrader API ++ @Override ++ public void setCanDrinkPotion(boolean bool) { ++ getHandle().canDrinkPotion = bool; ++ } ++ ++ @Override ++ public boolean canDrinkPotion() { ++ return getHandle().canDrinkPotion; ++ } ++ ++ @Override ++ public void setCanDrinkMilk(boolean bool) { ++ getHandle().canDrinkMilk = bool; ++ } ++ ++ @Override ++ public boolean canDrinkMilk() { ++ return getHandle().canDrinkMilk; ++ } ++ // Paper end + } diff --git a/patches/server/0634-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0634-Send-empty-commands-if-tab-completion-is-disabled.patch deleted file mode 100644 index e31efa98f9..0000000000 --- a/patches/server/0634-Send-empty-commands-if-tab-completion-is-disabled.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 26 Apr 2021 01:27:08 +0100 -Subject: [PATCH] Send empty commands if tab completion is disabled - - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 6ad3fe4718a0db17ad6115753e533bf069ce57c6..9c0b2679964f864671ff4041163d1065c8d9cf84 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -358,7 +358,12 @@ public class Commands { - } - - public void sendCommands(ServerPlayer player) { -- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot -+ // Paper start - Send empty commands if tab completion is disabled -+ if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) { //return; // Spigot -+ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>())); -+ return; -+ } -+ // Paper end - // CraftBukkit start - // Register Vanilla commands into builtRoot as before - // Paper start - Async command map building diff --git a/patches/server/0635-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0635-Add-EntityBlockStorage-clearEntities.patch new file mode 100644 index 0000000000..2bd0ddfa38 --- /dev/null +++ b/patches/server/0635-Add-EntityBlockStorage-clearEntities.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 5 Apr 2021 18:12:29 -0400 +Subject: [PATCH] Add EntityBlockStorage#clearEntities() + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index ea63802f2644bc2b5b3b0c72d7d09813cb68139d..82ad97800cb115cc4830337a59cc4608c1d4a7a0 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -139,6 +139,11 @@ public class BeehiveBlockEntity extends BlockEntity { + return this.stored.size(); + } + ++ // Paper start - Add EntityBlockStorage clearEntities ++ public void clearBees() { ++ this.stored.clear(); ++ } ++ // Paper end + public static int getHoneyLevel(BlockState state) { + return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +index ee4d7f5558ed76e8d8b56133da729a5303d1d823..e00210950cbf005b0e65aed68dfbb7c281aefd87 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +@@ -80,4 +80,10 @@ public class CraftBeehive extends CraftBlockEntityState impl + + getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); + } ++ // Paper start - Add EntityBlockStorage clearEntities ++ @Override ++ public void clearEntities() { ++ getSnapshot().clearBees(); ++ } ++ // Paper end + } diff --git a/patches/server/0635-Add-more-WanderingTrader-API.patch b/patches/server/0635-Add-more-WanderingTrader-API.patch deleted file mode 100644 index 65d351d6df..0000000000 --- a/patches/server/0635-Add-more-WanderingTrader-API.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Thu, 6 May 2021 14:56:43 +0100 -Subject: [PATCH] Add more WanderingTrader API - - -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index abb2c5c4ac481c7529aa29322babb1929235e15a..86e1ba898d6b92735258419fa74352e5116226dc 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -56,6 +56,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - @Nullable - private BlockPos wanderTarget; - private int despawnDelay; -+ // Paper start - Add more WanderingTrader API -+ public boolean canDrinkPotion = true; -+ public boolean canDrinkMilk = true; -+ // Paper end - - public WanderingTrader(EntityType type, Level world) { - super(type, world); -@@ -66,10 +70,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> { -- return this.level.isNight() && !entityvillagertrader.isInvisible(); -+ return this.canDrinkPotion && this.level.isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { -- return this.level.isDay() && entityvillagertrader.isInvisible(); -+ return canDrinkMilk && this.level.isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -index 65b052567d1d855021d7273672b4354aba0a42a4..fa7107593b20e0151d8d67104e4a92dcc697d461 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -@@ -34,4 +34,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande - public void setDespawnDelay(int despawnDelay) { - this.getHandle().setDespawnDelay(despawnDelay); - } -+ -+ // Paper start - Add more WanderingTrader API -+ @Override -+ public void setCanDrinkPotion(boolean bool) { -+ getHandle().canDrinkPotion = bool; -+ } -+ -+ @Override -+ public boolean canDrinkPotion() { -+ return getHandle().canDrinkPotion; -+ } -+ -+ @Override -+ public void setCanDrinkMilk(boolean bool) { -+ getHandle().canDrinkMilk = bool; -+ } -+ -+ @Override -+ public boolean canDrinkMilk() { -+ return getHandle().canDrinkMilk; -+ } -+ // Paper end - } diff --git a/patches/server/0636-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0636-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch new file mode 100644 index 0000000000..4169ad0754 --- /dev/null +++ b/patches/server/0636-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Alvinn8 <42838560+Alvinn8@users.noreply.github.com> +Date: Fri, 8 Jan 2021 20:31:13 +0100 +Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent + + +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index e7ec5e1144c1596b035f97fb1fb86d18e61be3c9..a0c19503aabab5378d672a30163d35a5ba05b6c1 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -287,10 +287,18 @@ public class PlayerAdvancements { + this.progressChanged.add(advancement); + flag = true; + if (!flag1 && advancementprogress.isDone()) { +- this.player.level.getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit)); // CraftBukkit ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ boolean announceToChat = advancement.getDisplay() != null && advancement.getDisplay().shouldAnnounceChat(); ++ net.kyori.adventure.text.Component message = announceToChat ? io.papermc.paper.adventure.PaperAdventure.asAdventure(Component.translatable("chat.type.advancement." + advancement.getDisplay().getFrame().getName(), this.player.getDisplayName(), advancement.getChatComponent())) : null; ++ org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit, message); ++ this.player.level.getCraftServer().getPluginManager().callEvent(event); ++ message = event.message(); ++ // Paper end + advancement.getRewards().grant(this.player); +- if (advancement.getDisplay() != null && advancement.getDisplay().shouldAnnounceChat() && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { +- this.playerList.broadcastSystemMessage(Component.translatable("chat.type.advancement." + advancement.getDisplay().getFrame().getName(), this.player.getDisplayName(), advancement.getChatComponent()), false); ++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent ++ if (message != null && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { ++ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), false); ++ // Paper end + } + } + } diff --git a/patches/server/0636-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0636-Add-EntityBlockStorage-clearEntities.patch deleted file mode 100644 index 2bd0ddfa38..0000000000 --- a/patches/server/0636-Add-EntityBlockStorage-clearEntities.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 5 Apr 2021 18:12:29 -0400 -Subject: [PATCH] Add EntityBlockStorage#clearEntities() - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index ea63802f2644bc2b5b3b0c72d7d09813cb68139d..82ad97800cb115cc4830337a59cc4608c1d4a7a0 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -139,6 +139,11 @@ public class BeehiveBlockEntity extends BlockEntity { - return this.stored.size(); - } - -+ // Paper start - Add EntityBlockStorage clearEntities -+ public void clearBees() { -+ this.stored.clear(); -+ } -+ // Paper end - public static int getHoneyLevel(BlockState state) { - return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -index ee4d7f5558ed76e8d8b56133da729a5303d1d823..e00210950cbf005b0e65aed68dfbb7c281aefd87 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -@@ -80,4 +80,10 @@ public class CraftBeehive extends CraftBlockEntityState impl - - getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); - } -+ // Paper start - Add EntityBlockStorage clearEntities -+ @Override -+ public void clearEntities() { -+ getSnapshot().clearBees(); -+ } -+ // Paper end - } diff --git a/patches/server/0637-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0637-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch deleted file mode 100644 index 4169ad0754..0000000000 --- a/patches/server/0637-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alvinn8 <42838560+Alvinn8@users.noreply.github.com> -Date: Fri, 8 Jan 2021 20:31:13 +0100 -Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent - - -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index e7ec5e1144c1596b035f97fb1fb86d18e61be3c9..a0c19503aabab5378d672a30163d35a5ba05b6c1 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -287,10 +287,18 @@ public class PlayerAdvancements { - this.progressChanged.add(advancement); - flag = true; - if (!flag1 && advancementprogress.isDone()) { -- this.player.level.getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit)); // CraftBukkit -+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent -+ boolean announceToChat = advancement.getDisplay() != null && advancement.getDisplay().shouldAnnounceChat(); -+ net.kyori.adventure.text.Component message = announceToChat ? io.papermc.paper.adventure.PaperAdventure.asAdventure(Component.translatable("chat.type.advancement." + advancement.getDisplay().getFrame().getName(), this.player.getDisplayName(), advancement.getChatComponent())) : null; -+ org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.bukkit, message); -+ this.player.level.getCraftServer().getPluginManager().callEvent(event); -+ message = event.message(); -+ // Paper end - advancement.getRewards().grant(this.player); -- if (advancement.getDisplay() != null && advancement.getDisplay().shouldAnnounceChat() && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { -- this.playerList.broadcastSystemMessage(Component.translatable("chat.type.advancement." + advancement.getDisplay().getFrame().getName(), this.player.getDisplayName(), advancement.getChatComponent()), false); -+ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent -+ if (message != null && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { -+ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), false); -+ // Paper end - } - } - } diff --git a/patches/server/0637-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch b/patches/server/0637-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch new file mode 100644 index 0000000000..8402d0763d --- /dev/null +++ b/patches/server/0637-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Wed, 12 May 2021 08:09:19 +0100 +Subject: [PATCH] Add raw address to AsyncPlayerPreLoginEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 2f0a70bc9cc8cda9e9beef00421078c036d6287c..f6efd220d7f78f3f763bf1983d20c636eb4923b6 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -381,12 +381,13 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + public void fireEvents() throws Exception { + String playerName = ServerLoginPacketListenerImpl.this.gameProfile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) ServerLoginPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(); ++ java.net.InetAddress rawAddress = ((java.net.InetSocketAddress) connection.getRawAddress()).getAddress(); // Paper + java.util.UUID uniqueId = ServerLoginPacketListenerImpl.this.gameProfile.getId(); + final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; + + // Paper start + com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(ServerLoginPacketListenerImpl.this.gameProfile); +- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile); // Paper - add rawAddress + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); + profile.complete(true); // Paper - setPlayerProfileAPI diff --git a/patches/server/0638-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch b/patches/server/0638-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch deleted file mode 100644 index 8402d0763d..0000000000 --- a/patches/server/0638-Add-raw-address-to-AsyncPlayerPreLoginEvent.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Connor Linfoot -Date: Wed, 12 May 2021 08:09:19 +0100 -Subject: [PATCH] Add raw address to AsyncPlayerPreLoginEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 2f0a70bc9cc8cda9e9beef00421078c036d6287c..f6efd220d7f78f3f763bf1983d20c636eb4923b6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -381,12 +381,13 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - public void fireEvents() throws Exception { - String playerName = ServerLoginPacketListenerImpl.this.gameProfile.getName(); - java.net.InetAddress address = ((java.net.InetSocketAddress) ServerLoginPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(); -+ java.net.InetAddress rawAddress = ((java.net.InetSocketAddress) connection.getRawAddress()).getAddress(); // Paper - java.util.UUID uniqueId = ServerLoginPacketListenerImpl.this.gameProfile.getId(); - final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; - - // Paper start - com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(ServerLoginPacketListenerImpl.this.gameProfile); -- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); -+ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile); // Paper - add rawAddress - server.getPluginManager().callEvent(asyncEvent); - profile = asyncEvent.getPlayerProfile(); - profile.complete(true); // Paper - setPlayerProfileAPI diff --git a/patches/server/0638-Inventory-close.patch b/patches/server/0638-Inventory-close.patch new file mode 100644 index 0000000000..e7f478324a --- /dev/null +++ b/patches/server/0638-Inventory-close.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 11 May 2021 14:54:56 -0700 +Subject: [PATCH] Inventory#close + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +index b17dab9e5c06d8789553b104602d7da35d926dd1..30ac442049088200e9ab77a561c59cbc58aaa28f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +@@ -448,6 +448,14 @@ public class CraftInventory implements Inventory { + this.clear(i); + } + } ++ // Paper start ++ @Override ++ public int close() { ++ int count = this.inventory.getViewers().size(); ++ com.google.common.collect.Lists.newArrayList(this.inventory.getViewers()).forEach(HumanEntity::closeInventory); ++ return count; ++ } ++ // Paper end + + @Override + public ListIterator iterator() { diff --git a/patches/server/0639-Inventory-close.patch b/patches/server/0639-Inventory-close.patch deleted file mode 100644 index e7f478324a..0000000000 --- a/patches/server/0639-Inventory-close.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 11 May 2021 14:54:56 -0700 -Subject: [PATCH] Inventory#close - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index b17dab9e5c06d8789553b104602d7da35d926dd1..30ac442049088200e9ab77a561c59cbc58aaa28f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -448,6 +448,14 @@ public class CraftInventory implements Inventory { - this.clear(i); - } - } -+ // Paper start -+ @Override -+ public int close() { -+ int count = this.inventory.getViewers().size(); -+ com.google.common.collect.Lists.newArrayList(this.inventory.getViewers()).forEach(HumanEntity::closeInventory); -+ return count; -+ } -+ // Paper end - - @Override - public ListIterator iterator() { diff --git a/patches/server/0639-call-PortalCreateEvent-players-and-end-platform.patch b/patches/server/0639-call-PortalCreateEvent-players-and-end-platform.patch new file mode 100644 index 0000000000..11bc717f06 --- /dev/null +++ b/patches/server/0639-call-PortalCreateEvent-players-and-end-platform.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 May 2021 03:21:22 -0700 +Subject: [PATCH] call PortalCreateEvent players and end platform + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index d8ba103fd52c3c540fe386c9ff8264fcb3dbc136..66a3148985f864c2e4238cd3b27469d59ab3f354 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1208,15 +1208,21 @@ public class ServerPlayer extends Player { + private void createEndPlatform(ServerLevel world, BlockPos centerPos) { + BlockPos.MutableBlockPos blockposition_mutableblockposition = centerPos.mutable(); + ++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(world); // Paper + for (int i = -2; i <= 2; ++i) { + for (int j = -2; j <= 2; ++j) { + for (int k = -1; k < 3; ++k) { + BlockState iblockdata = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState(); + +- world.setBlockAndUpdate(blockposition_mutableblockposition.set(centerPos).move(j, k, i), iblockdata); ++ blockList.setBlock(blockposition_mutableblockposition.set(centerPos).move(j, k, i), iblockdata, 3); // Paper + } + } + } ++ // Paper start ++ if (new org.bukkit.event.world.PortalCreateEvent((List< org.bukkit.block.BlockState>) (List) blockList.getList(), world.getWorld(), this.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM).callEvent()) { ++ blockList.updateList(); ++ } ++ // Paper end + + } + diff --git a/patches/server/0640-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0640-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch new file mode 100644 index 0000000000..15c8339c99 --- /dev/null +++ b/patches/server/0640-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch @@ -0,0 +1,131 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MeFisto94 +Date: Tue, 11 May 2021 00:48:33 +0200 +Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and + Skeletons + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +index c2a26b91d9065fdb52a1ded6c3295093c244d7eb..b8abee145fc92faddef98da913eca7715b6bfc03 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +@@ -97,9 +97,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + return MobType.UNDEAD; + } + ++ // Paper start ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } ++ // Paper end ++ + @Override + public void aiStep() { +- boolean flag = this.isSunBurnTick(); ++ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning + + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); +@@ -225,7 +231,20 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo + public void readAdditionalSaveData(CompoundTag nbt) { + super.readAdditionalSaveData(nbt); + this.reassessWeaponGoal(); ++ // Paper start ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } ++ // Paper end ++ } ++ ++ // Paper start ++ @Override ++ public void addAdditionalSaveData(CompoundTag nbt) { ++ super.addAdditionalSaveData(nbt); ++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); + } ++ // Paper end + + @Override + public void setItemSlot(EquipmentSlot slot, ItemStack stack) { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +index 180bcdeb262d61c56193dbf99f1c11f3a6889145..cb81c5d37c72845133c4e59acaf8de56dcc9e62a 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java +@@ -144,7 +144,7 @@ public class Phantom extends FlyingMob implements Enemy { + + @Override + public void aiStep() { +- if (this.isAlive() && this.isSunBurnTick()) { ++ if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning + this.setSecondsOnFire(8); + } + +@@ -175,6 +175,9 @@ public class Phantom extends FlyingMob implements Enemy { + if (nbt.hasUUID("Paper.SpawningEntity")) { + this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); + } ++ if (nbt.contains("Paper.ShouldBurnInDay")) { ++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); ++ } + // Paper end + } + +@@ -189,6 +192,7 @@ public class Phantom extends FlyingMob implements Enemy { + if (this.spawningEntity != null) { + nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); + } ++ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + // Paper end + } + +@@ -248,6 +252,10 @@ public class Phantom extends FlyingMob implements Enemy { + return spawningEntity; + } + public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } ++ ++ private boolean shouldBurnInDay = true; ++ public boolean shouldBurnInDay() { return shouldBurnInDay; } ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } + // Paper end + private static enum AttackPhase { + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +index 5ff566186431440c25a26900aba14e4adb642031..5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java +@@ -20,4 +20,15 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst + return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); + } + // Paper end ++ // Paper start ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +index c9dab70b0b284fe1c1daafd3c1f5bd08b14fa35d..dce23f3878b1588c26b6116d80e597d08070edbc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +@@ -40,5 +40,15 @@ public class CraftPhantom extends CraftFlying implements Phantom { + public java.util.UUID getSpawningEntity() { + return getHandle().getSpawningEntity(); + } ++ ++ @Override ++ public boolean shouldBurnInDay() { ++ return getHandle().shouldBurnInDay(); ++ } ++ ++ @Override ++ public void setShouldBurnInDay(boolean shouldBurnInDay) { ++ getHandle().setShouldBurnInDay(shouldBurnInDay); ++ } + // Paper end + } diff --git a/patches/server/0640-call-PortalCreateEvent-players-and-end-platform.patch b/patches/server/0640-call-PortalCreateEvent-players-and-end-platform.patch deleted file mode 100644 index 11bc717f06..0000000000 --- a/patches/server/0640-call-PortalCreateEvent-players-and-end-platform.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 12 May 2021 03:21:22 -0700 -Subject: [PATCH] call PortalCreateEvent players and end platform - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d8ba103fd52c3c540fe386c9ff8264fcb3dbc136..66a3148985f864c2e4238cd3b27469d59ab3f354 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1208,15 +1208,21 @@ public class ServerPlayer extends Player { - private void createEndPlatform(ServerLevel world, BlockPos centerPos) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = centerPos.mutable(); - -+ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(world); // Paper - for (int i = -2; i <= 2; ++i) { - for (int j = -2; j <= 2; ++j) { - for (int k = -1; k < 3; ++k) { - BlockState iblockdata = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState(); - -- world.setBlockAndUpdate(blockposition_mutableblockposition.set(centerPos).move(j, k, i), iblockdata); -+ blockList.setBlock(blockposition_mutableblockposition.set(centerPos).move(j, k, i), iblockdata, 3); // Paper - } - } - } -+ // Paper start -+ if (new org.bukkit.event.world.PortalCreateEvent((List< org.bukkit.block.BlockState>) (List) blockList.getList(), world.getWorld(), this.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM).callEvent()) { -+ blockList.updateList(); -+ } -+ // Paper end - - } - diff --git a/patches/server/0641-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0641-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch deleted file mode 100644 index 15c8339c99..0000000000 --- a/patches/server/0641-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MeFisto94 -Date: Tue, 11 May 2021 00:48:33 +0200 -Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and - Skeletons - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index c2a26b91d9065fdb52a1ded6c3295093c244d7eb..b8abee145fc92faddef98da913eca7715b6bfc03 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -97,9 +97,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - return MobType.UNDEAD; - } - -+ // Paper start -+ private boolean shouldBurnInDay = true; -+ public boolean shouldBurnInDay() { return shouldBurnInDay; } -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } -+ // Paper end -+ - @Override - public void aiStep() { -- boolean flag = this.isSunBurnTick(); -+ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -@@ -225,7 +231,20 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - public void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); - this.reassessWeaponGoal(); -+ // Paper start -+ if (nbt.contains("Paper.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); -+ } -+ // Paper end -+ } -+ -+ // Paper start -+ @Override -+ public void addAdditionalSaveData(CompoundTag nbt) { -+ super.addAdditionalSaveData(nbt); -+ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); - } -+ // Paper end - - @Override - public void setItemSlot(EquipmentSlot slot, ItemStack stack) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -index 180bcdeb262d61c56193dbf99f1c11f3a6889145..cb81c5d37c72845133c4e59acaf8de56dcc9e62a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java -@@ -144,7 +144,7 @@ public class Phantom extends FlyingMob implements Enemy { - - @Override - public void aiStep() { -- if (this.isAlive() && this.isSunBurnTick()) { -+ if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning - this.setSecondsOnFire(8); - } - -@@ -175,6 +175,9 @@ public class Phantom extends FlyingMob implements Enemy { - if (nbt.hasUUID("Paper.SpawningEntity")) { - this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); - } -+ if (nbt.contains("Paper.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); -+ } - // Paper end - } - -@@ -189,6 +192,7 @@ public class Phantom extends FlyingMob implements Enemy { - if (this.spawningEntity != null) { - nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); - } -+ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); - // Paper end - } - -@@ -248,6 +252,10 @@ public class Phantom extends FlyingMob implements Enemy { - return spawningEntity; - } - public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } -+ -+ private boolean shouldBurnInDay = true; -+ public boolean shouldBurnInDay() { return shouldBurnInDay; } -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - private static enum AttackPhase { - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -index 5ff566186431440c25a26900aba14e4adb642031..5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java -@@ -20,4 +20,15 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst - return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle(); - } - // Paper end -+ // Paper start -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index c9dab70b0b284fe1c1daafd3c1f5bd08b14fa35d..dce23f3878b1588c26b6116d80e597d08070edbc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -@@ -40,5 +40,15 @@ public class CraftPhantom extends CraftFlying implements Phantom { - public java.util.UUID getSpawningEntity() { - return getHandle().getSpawningEntity(); - } -+ -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } - // Paper end - } diff --git a/patches/server/0641-Fix-CraftPotionBrewer-cache.patch b/patches/server/0641-Fix-CraftPotionBrewer-cache.patch new file mode 100644 index 0000000000..2d8295382f --- /dev/null +++ b/patches/server/0641-Fix-CraftPotionBrewer-cache.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sceri +Date: Fri, 14 May 2021 19:06:51 +0500 +Subject: [PATCH] Fix CraftPotionBrewer cache + + +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +index 1e4bc0d9f9d2e45157929af685f116988cbb8c03..8fdc9a3bb2f1b6bdc6c2c96f8ade7e9cd88ea4e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +@@ -15,12 +15,18 @@ import org.bukkit.potion.PotionEffectType; + import org.bukkit.potion.PotionType; + + public class CraftPotionBrewer implements PotionBrewer { +- private static final Map> cache = Maps.newHashMap(); ++ private static final Map> cache = Maps.newHashMap(); // Paper + + @Override + public Collection getEffects(PotionType damage, boolean upgraded, boolean extended) { +- if (CraftPotionBrewer.cache.containsKey(damage)) +- return CraftPotionBrewer.cache.get(damage); ++ // Paper start ++ int key = damage.ordinal() << 2; ++ key |= (upgraded ? 1 : 0) << 1; ++ key |= extended ? 1 : 0; ++ ++ if (CraftPotionBrewer.cache.containsKey(key)) ++ return CraftPotionBrewer.cache.get(key); ++ // Paper end + + List mcEffects = Potion.byName(CraftPotionUtil.fromBukkit(new PotionData(damage, extended, upgraded))).getEffects(); + +@@ -29,9 +35,9 @@ public class CraftPotionBrewer implements PotionBrewer { + builder.add(CraftPotionUtil.toBukkit(effect)); + } + +- CraftPotionBrewer.cache.put(damage, builder.build()); ++ CraftPotionBrewer.cache.put(key, builder.build()); // Paper + +- return CraftPotionBrewer.cache.get(damage); ++ return CraftPotionBrewer.cache.get(key); // Paper + } + + @Override diff --git a/patches/server/0642-Add-basic-Datapack-API.patch b/patches/server/0642-Add-basic-Datapack-API.patch new file mode 100644 index 0000000000..1c6af9a618 --- /dev/null +++ b/patches/server/0642-Add-basic-Datapack-API.patch @@ -0,0 +1,125 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Connor Linfoot +Date: Sun, 16 May 2021 15:07:34 +0100 +Subject: [PATCH] Add basic Datapack API + + +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9b7dd8a0fba4547f5268b3f99e21ddbe6b5bf566 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java +@@ -0,0 +1,50 @@ ++package io.papermc.paper.datapack; ++ ++import io.papermc.paper.event.server.ServerResourcesReloadedEvent; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.packs.repository.Pack; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++public class PaperDatapack implements Datapack { ++ private final String name; ++ private final Compatibility compatibility; ++ private final boolean enabled; ++ ++ PaperDatapack(Pack loader, boolean enabled) { ++ this.name = loader.getId(); ++ this.compatibility = Compatibility.valueOf(loader.getCompatibility().name()); ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public String getName() { ++ return name; ++ } ++ ++ @Override ++ public Compatibility getCompatibility() { ++ return compatibility; ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ @Override ++ public void setEnabled(boolean enabled) { ++ if (enabled == this.enabled) { ++ return; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ List enabledKeys = server.getPackRepository().getSelectedPacks().stream().map(Pack::getId).collect(Collectors.toList()); ++ if (enabled) { ++ enabledKeys.add(this.name); ++ } else { ++ enabledKeys.remove(this.name); ++ } ++ server.reloadResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf4374493c11057451a62a655514415cf6b298e0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java +@@ -0,0 +1,25 @@ ++package io.papermc.paper.datapack; ++ ++import java.util.Collection; ++import java.util.stream.Collectors; ++import net.minecraft.server.packs.repository.Pack; ++import net.minecraft.server.packs.repository.PackRepository; ++ ++public class PaperDatapackManager implements DatapackManager { ++ private final PackRepository repository; ++ ++ public PaperDatapackManager(PackRepository repository) { ++ this.repository = repository; ++ } ++ ++ @Override ++ public Collection getPacks() { ++ Collection enabledPacks = repository.getSelectedPacks(); ++ return repository.getAvailablePacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); ++ } ++ ++ @Override ++ public Collection getEnabledPacks() { ++ return repository.getSelectedPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index cebc3c444681d6f422a8befff74476e7e0d8d22e..9447b7cf08401608b2b79e8c68f58fd977cf0d42 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -282,6 +282,7 @@ public final class CraftServer implements Server { + public boolean ignoreVanillaPermissions = false; + private final List playerView; + public int reloadCount; ++ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + + static { +@@ -368,6 +369,7 @@ public final class CraftServer implements Server { + if (this.configuration.getBoolean("settings.use-map-color-cache")) { + MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); + } ++ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper + } + + public boolean getCommandBlockOverride(String command) { +@@ -2770,5 +2772,11 @@ public final class CraftServer implements Server { + public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { + return mobGoals; + } ++ ++ @Override ++ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { ++ return datapackManager; ++ } ++ + // Paper end + } diff --git a/patches/server/0642-Fix-CraftPotionBrewer-cache.patch b/patches/server/0642-Fix-CraftPotionBrewer-cache.patch deleted file mode 100644 index 2d8295382f..0000000000 --- a/patches/server/0642-Fix-CraftPotionBrewer-cache.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Sceri -Date: Fri, 14 May 2021 19:06:51 +0500 -Subject: [PATCH] Fix CraftPotionBrewer cache - - -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -index 1e4bc0d9f9d2e45157929af685f116988cbb8c03..8fdc9a3bb2f1b6bdc6c2c96f8ade7e9cd88ea4e0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -@@ -15,12 +15,18 @@ import org.bukkit.potion.PotionEffectType; - import org.bukkit.potion.PotionType; - - public class CraftPotionBrewer implements PotionBrewer { -- private static final Map> cache = Maps.newHashMap(); -+ private static final Map> cache = Maps.newHashMap(); // Paper - - @Override - public Collection getEffects(PotionType damage, boolean upgraded, boolean extended) { -- if (CraftPotionBrewer.cache.containsKey(damage)) -- return CraftPotionBrewer.cache.get(damage); -+ // Paper start -+ int key = damage.ordinal() << 2; -+ key |= (upgraded ? 1 : 0) << 1; -+ key |= extended ? 1 : 0; -+ -+ if (CraftPotionBrewer.cache.containsKey(key)) -+ return CraftPotionBrewer.cache.get(key); -+ // Paper end - - List mcEffects = Potion.byName(CraftPotionUtil.fromBukkit(new PotionData(damage, extended, upgraded))).getEffects(); - -@@ -29,9 +35,9 @@ public class CraftPotionBrewer implements PotionBrewer { - builder.add(CraftPotionUtil.toBukkit(effect)); - } - -- CraftPotionBrewer.cache.put(damage, builder.build()); -+ CraftPotionBrewer.cache.put(key, builder.build()); // Paper - -- return CraftPotionBrewer.cache.get(damage); -+ return CraftPotionBrewer.cache.get(key); // Paper - } - - @Override diff --git a/patches/server/0643-Add-basic-Datapack-API.patch b/patches/server/0643-Add-basic-Datapack-API.patch deleted file mode 100644 index 1c6af9a618..0000000000 --- a/patches/server/0643-Add-basic-Datapack-API.patch +++ /dev/null @@ -1,125 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Connor Linfoot -Date: Sun, 16 May 2021 15:07:34 +0100 -Subject: [PATCH] Add basic Datapack API - - -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9b7dd8a0fba4547f5268b3f99e21ddbe6b5bf566 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java -@@ -0,0 +1,50 @@ -+package io.papermc.paper.datapack; -+ -+import io.papermc.paper.event.server.ServerResourcesReloadedEvent; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.packs.repository.Pack; -+import java.util.List; -+import java.util.stream.Collectors; -+ -+public class PaperDatapack implements Datapack { -+ private final String name; -+ private final Compatibility compatibility; -+ private final boolean enabled; -+ -+ PaperDatapack(Pack loader, boolean enabled) { -+ this.name = loader.getId(); -+ this.compatibility = Compatibility.valueOf(loader.getCompatibility().name()); -+ this.enabled = enabled; -+ } -+ -+ @Override -+ public String getName() { -+ return name; -+ } -+ -+ @Override -+ public Compatibility getCompatibility() { -+ return compatibility; -+ } -+ -+ @Override -+ public boolean isEnabled() { -+ return enabled; -+ } -+ -+ @Override -+ public void setEnabled(boolean enabled) { -+ if (enabled == this.enabled) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ List enabledKeys = server.getPackRepository().getSelectedPacks().stream().map(Pack::getId).collect(Collectors.toList()); -+ if (enabled) { -+ enabledKeys.add(this.name); -+ } else { -+ enabledKeys.remove(this.name); -+ } -+ server.reloadResources(enabledKeys, ServerResourcesReloadedEvent.Cause.PLUGIN); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf4374493c11057451a62a655514415cf6b298e0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java -@@ -0,0 +1,25 @@ -+package io.papermc.paper.datapack; -+ -+import java.util.Collection; -+import java.util.stream.Collectors; -+import net.minecraft.server.packs.repository.Pack; -+import net.minecraft.server.packs.repository.PackRepository; -+ -+public class PaperDatapackManager implements DatapackManager { -+ private final PackRepository repository; -+ -+ public PaperDatapackManager(PackRepository repository) { -+ this.repository = repository; -+ } -+ -+ @Override -+ public Collection getPacks() { -+ Collection enabledPacks = repository.getSelectedPacks(); -+ return repository.getAvailablePacks().stream().map(loader -> new PaperDatapack(loader, enabledPacks.contains(loader))).collect(Collectors.toList()); -+ } -+ -+ @Override -+ public Collection getEnabledPacks() { -+ return repository.getSelectedPacks().stream().map(loader -> new PaperDatapack(loader, true)).collect(Collectors.toList()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index cebc3c444681d6f422a8befff74476e7e0d8d22e..9447b7cf08401608b2b79e8c68f58fd977cf0d42 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -282,6 +282,7 @@ public final class CraftServer implements Server { - public boolean ignoreVanillaPermissions = false; - private final List playerView; - public int reloadCount; -+ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - - static { -@@ -368,6 +369,7 @@ public final class CraftServer implements Server { - if (this.configuration.getBoolean("settings.use-map-color-cache")) { - MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); - } -+ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper - } - - public boolean getCommandBlockOverride(String command) { -@@ -2770,5 +2772,11 @@ public final class CraftServer implements Server { - public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { - return mobGoals; - } -+ -+ @Override -+ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() { -+ return datapackManager; -+ } -+ - // Paper end - } diff --git a/patches/server/0643-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0643-Add-environment-variable-to-disable-server-gui.patch new file mode 100644 index 0000000000..49bb358278 --- /dev/null +++ b/patches/server/0643-Add-environment-variable-to-disable-server-gui.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Riley Park +Date: Mon, 17 May 2021 00:34:55 -0700 +Subject: [PATCH] Add environment variable to disable server gui + + +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 13ef3bb2b84fac9a1be72b09e7e3c022fa08221a..2f82002c52af7304ff6b2d6fe8f094314daf0bba 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -284,6 +284,7 @@ public class Main { + */ + boolean flag1 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); + ++ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper + if (flag1 && !GraphicsEnvironment.isHeadless()) { + dedicatedserver1.showGui(); + } diff --git a/patches/server/0644-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0644-Add-environment-variable-to-disable-server-gui.patch deleted file mode 100644 index 49bb358278..0000000000 --- a/patches/server/0644-Add-environment-variable-to-disable-server-gui.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Riley Park -Date: Mon, 17 May 2021 00:34:55 -0700 -Subject: [PATCH] Add environment variable to disable server gui - - -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 13ef3bb2b84fac9a1be72b09e7e3c022fa08221a..2f82002c52af7304ff6b2d6fe8f094314daf0bba 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -284,6 +284,7 @@ public class Main { - */ - boolean flag1 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui"); - -+ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - if (flag1 && !GraphicsEnvironment.isHeadless()) { - dedicatedserver1.showGui(); - } diff --git a/patches/server/0644-additions-to-PlayerGameModeChangeEvent.patch b/patches/server/0644-additions-to-PlayerGameModeChangeEvent.patch new file mode 100644 index 0000000000..5262ad7255 --- /dev/null +++ b/patches/server/0644-additions-to-PlayerGameModeChangeEvent.patch @@ -0,0 +1,153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 10:04:43 -0700 +Subject: [PATCH] additions to PlayerGameModeChangeEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +index d446e52aa0951af5f759fa7a91908f4d818bd4a5..3fcdc509649b3d67bfd74404bcdf8d7a65c1babf 100644 +--- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java ++++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +@@ -31,9 +31,13 @@ public class DefaultGameModeCommands { + GameType gameType = minecraftServer.getForcedGameType(); + if (gameType != null) { + for(ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { +- if (serverPlayer.setGameMode(gameType)) { +- ++i; ++ // Paper start - extend PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); ++ if (event != null && event.isCancelled()) { ++ source.sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + } ++ // Paper end ++ ++i; + } + } + +diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +index 65089c0e78c9913a92ae9c66d664f48e2112ad92..7882ee2b7813d437d3b7580f046f38e79fc9e7b6 100644 +--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java ++++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java +@@ -50,9 +50,14 @@ public class GameModeCommand { + int i = 0; + + for(ServerPlayer serverPlayer : targets) { +- if (serverPlayer.setGameMode(gameMode)) { ++ // Paper start - extend PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); ++ if (event != null && !event.isCancelled()) { + logGamemodeChange(context.getSource(), serverPlayer, gameMode); + ++i; ++ } else if (event != null && event.cancelMessage() != null) { ++ context.getSource().sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 66a3148985f864c2e4238cd3b27469d59ab3f354..4a35720430990b358ea5d7f2b6293e27e8d9f7ac 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1810,8 +1810,15 @@ public class ServerPlayer extends Player { + } + + public boolean setGameMode(GameType gameMode) { +- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { +- return false; ++ // Paper start - Add cause and nullable message to event ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, net.kyori.adventure.text.Component message) { ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); ++ if (event == null || event.isCancelled()) { ++ // Paper end ++ return null; + } else { + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { +@@ -1823,7 +1830,7 @@ public class ServerPlayer extends Player { + + this.onUpdateAbilities(); + this.updateEffectVisibility(); +- return true; ++ return event; // Paper + } + } + +@@ -2225,6 +2232,16 @@ public class ServerPlayer extends Player { + } + + public void loadGameTypes(@Nullable CompoundTag nbt) { ++ // Paper start ++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { ++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); ++ } else { ++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); ++ } ++ return; ++ } ++ // Paper end + this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 32746dfbc2fdfc150583676b1bf0762398b76d75..1ad1f958a9b6e1bc21f1c505aa7ea54950de6cad 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -73,18 +73,24 @@ public class ServerPlayerGameMode { + } + + public boolean changeGameModeForPlayer(GameType gameMode) { ++ // Paper end ++ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, net.kyori.adventure.text.Component cancelMessage) { ++ // Paper end + if (gameMode == this.gameModeForPlayer) { +- return false; ++ return null; // Paper + } else { + // CraftBukkit start +- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); ++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper + this.level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { +- return false; ++ return event; // Paper + } + // CraftBukkit end + this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); +- return true; ++ return event; // Paper + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 161b5d6f0d420ac7b6ed112d1b03d42c3aaec421..de4c3849cc60151de8f3a873adad2bc36e30fbc4 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2903,7 +2903,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + this.player = this.server.getPlayerList().respawn(this.player, false); + if (this.server.isHardcore()) { +- this.player.setGameMode(GameType.SPECTATOR); ++ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper + ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 0528489348dc12c22bdb306a4b6c5c6a138f5347..ca8267966307ed5e0cb2fc5e4cf61868a77f50ed 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1386,7 +1386,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + throw new IllegalArgumentException("Mode cannot be null"); + } + +- this.getHandle().setGameMode(GameType.byId(mode.getValue())); ++ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper + } + + @Override diff --git a/patches/server/0645-ItemStack-repair-check-API.patch b/patches/server/0645-ItemStack-repair-check-API.patch new file mode 100644 index 0000000000..136bbcb302 --- /dev/null +++ b/patches/server/0645-ItemStack-repair-check-API.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 22:11:11 -0700 +Subject: [PATCH] ItemStack repair check API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 7f747fbaa1da49ab930d2a9ff60200a445ca477c..0ebcadd6daf244cd9b6c943ca0a2baaafb3eba50 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -537,6 +537,14 @@ public final class CraftMagicNumbers implements UnsafeValues { + return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; + } + ++ @Override ++ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { ++ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { ++ return false; ++ } ++ return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); +diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8d9c9b3bd53d407391d4fcb7fc773153d1a7b402 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.util; ++ ++import org.bukkit.Material; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertThrows; ++import static org.junit.Assert.assertTrue; ++ ++public class ItemStackRepairCheckTest extends AbstractTestingBase { ++ ++ @Test ++ public void testIsRepariableBy() { ++ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); ++ ++ assertTrue("diamond pick isn't repairable by a diamond", diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++ ++ @Test ++ public void testCanRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertTrue("diamond can't repair a diamond axe", diamond.canRepair(new ItemStack(Material.DIAMOND_AXE))); ++ } ++ ++ @Test ++ public void testIsNotRepairableBy() { ++ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); ++ ++ assertFalse("acacia sapling is repairable by a diamond", notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++ ++ @Test ++ public void testCanNotRepair() { ++ ItemStack diamond = new ItemStack(Material.DIAMOND); ++ ++ assertFalse("diamond can repair oak button", diamond.canRepair(new ItemStack(Material.OAK_BUTTON))); ++ } ++ ++ @Test ++ public void testInvalidItem() { ++ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); ++ ++ assertFalse("acacia wall sign is repairable by diamond", badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND))); ++ } ++} diff --git a/patches/server/0645-additions-to-PlayerGameModeChangeEvent.patch b/patches/server/0645-additions-to-PlayerGameModeChangeEvent.patch deleted file mode 100644 index 22997bcc3b..0000000000 --- a/patches/server/0645-additions-to-PlayerGameModeChangeEvent.patch +++ /dev/null @@ -1,153 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 10:04:43 -0700 -Subject: [PATCH] additions to PlayerGameModeChangeEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -index d446e52aa0951af5f759fa7a91908f4d818bd4a5..3fcdc509649b3d67bfd74404bcdf8d7a65c1babf 100644 ---- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -+++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -@@ -31,9 +31,13 @@ public class DefaultGameModeCommands { - GameType gameType = minecraftServer.getForcedGameType(); - if (gameType != null) { - for(ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) { -- if (serverPlayer.setGameMode(gameType)) { -- ++i; -+ // Paper start - extend PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); -+ if (event != null && event.isCancelled()) { -+ source.sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); - } -+ // Paper end -+ ++i; - } - } - -diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -index 65089c0e78c9913a92ae9c66d664f48e2112ad92..7882ee2b7813d437d3b7580f046f38e79fc9e7b6 100644 ---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -50,9 +50,14 @@ public class GameModeCommand { - int i = 0; - - for(ServerPlayer serverPlayer : targets) { -- if (serverPlayer.setGameMode(gameMode)) { -+ // Paper start - extend PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty()); -+ if (event != null && !event.isCancelled()) { - logGamemodeChange(context.getSource(), serverPlayer, gameMode); - ++i; -+ } else if (event != null && event.cancelMessage() != null) { -+ context.getSource().sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 66a3148985f864c2e4238cd3b27469d59ab3f354..4a35720430990b358ea5d7f2b6293e27e8d9f7ac 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1810,8 +1810,15 @@ public class ServerPlayer extends Player { - } - - public boolean setGameMode(GameType gameMode) { -- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { -- return false; -+ // Paper start - Add cause and nullable message to event -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event == null ? false : event.isCancelled(); -+ } -+ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, net.kyori.adventure.text.Component message) { -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); -+ if (event == null || event.isCancelled()) { -+ // Paper end -+ return null; - } else { - this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); - if (gameMode == GameType.SPECTATOR) { -@@ -1823,7 +1830,7 @@ public class ServerPlayer extends Player { - - this.onUpdateAbilities(); - this.updateEffectVisibility(); -- return true; -+ return event; // Paper - } - } - -@@ -2225,6 +2232,16 @@ public class ServerPlayer extends Player { - } - - public void loadGameTypes(@Nullable CompoundTag nbt) { -+ // Paper start -+ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { -+ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { -+ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); -+ } else { -+ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); -+ } -+ return; -+ } -+ // Paper end - this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 32746dfbc2fdfc150583676b1bf0762398b76d75..1ad1f958a9b6e1bc21f1c505aa7ea54950de6cad 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -73,18 +73,24 @@ public class ServerPlayerGameMode { - } - - public boolean changeGameModeForPlayer(GameType gameMode) { -+ // Paper end -+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event == null ? false : event.isCancelled(); -+ } -+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, net.kyori.adventure.text.Component cancelMessage) { -+ // Paper end - if (gameMode == this.gameModeForPlayer) { -- return false; -+ return null; // Paper - } else { - // CraftBukkit start -- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId())); -+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper - this.level.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { -- return false; -+ return event; // Paper - } - // CraftBukkit end - this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); -- return true; -+ return event; // Paper - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c78d2aa1d4c2066716e274a26496ddd8eaf1be0a..a10d09f2129a3e3a8dd72f32494e80bce14905b9 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2899,7 +2899,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - this.player = this.server.getPlayerList().respawn(this.player, false); - if (this.server.isHardcore()) { -- this.player.setGameMode(GameType.SPECTATOR); -+ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 0528489348dc12c22bdb306a4b6c5c6a138f5347..ca8267966307ed5e0cb2fc5e4cf61868a77f50ed 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1386,7 +1386,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - throw new IllegalArgumentException("Mode cannot be null"); - } - -- this.getHandle().setGameMode(GameType.byId(mode.getValue())); -+ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - } - - @Override diff --git a/patches/server/0646-ItemStack-repair-check-API.patch b/patches/server/0646-ItemStack-repair-check-API.patch deleted file mode 100644 index 136bbcb302..0000000000 --- a/patches/server/0646-ItemStack-repair-check-API.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 22:11:11 -0700 -Subject: [PATCH] ItemStack repair check API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 7f747fbaa1da49ab930d2a9ff60200a445ca477c..0ebcadd6daf244cd9b6c943ca0a2baaafb3eba50 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -537,6 +537,14 @@ public final class CraftMagicNumbers implements UnsafeValues { - return io.papermc.paper.inventory.ItemRarity.values()[getItem(itemStack.getType()).getRarity(CraftItemStack.asNMSCopy(itemStack)).ordinal()]; - } - -+ @Override -+ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) { -+ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) { -+ return false; -+ } -+ return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); -+ } -+ - @Override - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); -diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8d9c9b3bd53d407391d4fcb7fc773153d1a7b402 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java -@@ -0,0 +1,48 @@ -+package io.papermc.paper.util; -+ -+import org.bukkit.Material; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertFalse; -+import static org.junit.Assert.assertThrows; -+import static org.junit.Assert.assertTrue; -+ -+public class ItemStackRepairCheckTest extends AbstractTestingBase { -+ -+ @Test -+ public void testIsRepariableBy() { -+ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE); -+ -+ assertTrue("diamond pick isn't repairable by a diamond", diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); -+ } -+ -+ @Test -+ public void testCanRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertTrue("diamond can't repair a diamond axe", diamond.canRepair(new ItemStack(Material.DIAMOND_AXE))); -+ } -+ -+ @Test -+ public void testIsNotRepairableBy() { -+ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING); -+ -+ assertFalse("acacia sapling is repairable by a diamond", notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND))); -+ } -+ -+ @Test -+ public void testCanNotRepair() { -+ ItemStack diamond = new ItemStack(Material.DIAMOND); -+ -+ assertFalse("diamond can repair oak button", diamond.canRepair(new ItemStack(Material.OAK_BUTTON))); -+ } -+ -+ @Test -+ public void testInvalidItem() { -+ ItemStack badItemStack = new ItemStack(Material.ACACIA_WALL_SIGN); -+ -+ assertFalse("acacia wall sign is repairable by diamond", badItemStack.isRepairableBy(new ItemStack(Material.DIAMOND))); -+ } -+} diff --git a/patches/server/0646-More-Enchantment-API.patch b/patches/server/0646-More-Enchantment-API.patch new file mode 100644 index 0000000000..538c746f7e --- /dev/null +++ b/patches/server/0646-More-Enchantment-API.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 6 May 2021 19:57:58 -0700 +Subject: [PATCH] More Enchantment API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +index 31a22f26070059e5379730c1940ff1c5fb109be1..873185fd4d4c994130f2e7c271b3e03cefb2278c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java ++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +@@ -71,7 +71,7 @@ public class CraftEnchantment extends Enchantment { + + @Override + public boolean isCursed() { +- return this.target instanceof BindingCurseEnchantment || this.target instanceof VanishingCurseEnchantment; ++ return this.target.isCurse(); // Paper + } + + @Override +@@ -199,6 +199,45 @@ public class CraftEnchantment extends Enchantment { + public String translationKey() { + return this.target.getDescriptionId(); + } ++ ++ @Override ++ public boolean isTradeable() { ++ return target.isTradeable(); ++ } ++ ++ @Override ++ public boolean isDiscoverable() { ++ return target.isDiscoverable(); ++ } ++ ++ @Override ++ public io.papermc.paper.enchantments.EnchantmentRarity getRarity() { ++ return fromNMSRarity(target.getRarity()); ++ } ++ ++ @Override ++ public float getDamageIncrease(int level, org.bukkit.entity.EntityCategory entityCategory) { ++ return target.getDamageBonus(level, org.bukkit.craftbukkit.entity.CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); ++ } ++ ++ @Override ++ public java.util.Set getActiveSlots() { ++ return java.util.stream.Stream.of(target.slots).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet()); ++ } ++ ++ public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) { ++ if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.COMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.RARE; ++ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) { ++ return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE; ++ } ++ ++ throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class)); ++ } + // Paper end + + public net.minecraft.world.item.enchantment.Enchantment getHandle() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 1322f5a059743e7e2245ef2e25e9bffda138aa7c..3ba00e72d9760ec0dcde93d54cd7868eea23ec8a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -913,5 +913,21 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + public void setHurtDirection(float hurtDirection) { + getHandle().hurtDir = hurtDirection; + } ++ ++ public static MobType fromBukkitEntityCategory(EntityCategory entityCategory) { ++ switch (entityCategory) { ++ case NONE: ++ return MobType.UNDEFINED; ++ case UNDEAD: ++ return MobType.UNDEAD; ++ case ARTHROPOD: ++ return MobType.ARTHROPOD; ++ case ILLAGER: ++ return MobType.ILLAGER; ++ case WATER: ++ return MobType.WATER; ++ } ++ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); ++ } + // Paper end + } +diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62b56b5b43696b03fc72cac59f986d006edc3f76 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java +@@ -0,0 +1,18 @@ ++package io.papermc.paper.enchantments; ++ ++import net.minecraft.world.item.enchantment.Enchantment.Rarity; ++import org.bukkit.craftbukkit.enchantments.CraftEnchantment; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertNotNull; ++ ++public class EnchantmentRarityTest { ++ ++ @Test ++ public void test() { ++ for (Rarity nmsRarity : Rarity.values()) { ++ // Will throw exception if a bukkit counterpart is not found ++ CraftEnchantment.fromNMSRarity(nmsRarity); ++ } ++ } ++} +diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b9824b1f9491304ceb91be18f4f3b3068526bb33 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper.entity; ++ ++import com.google.common.base.Joiner; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Sets; ++import net.minecraft.world.entity.MobType; ++import org.bukkit.craftbukkit.entity.CraftLivingEntity; ++import org.bukkit.entity.EntityCategory; ++import org.junit.Test; ++ ++import java.lang.reflect.Field; ++import java.util.Map; ++import java.util.Set; ++ ++import static org.junit.Assert.assertTrue; ++ ++public class EntityCategoryTest { ++ ++ @Test ++ public void test() throws IllegalAccessException { ++ ++ Map enumMonsterTypeFieldMap = Maps.newHashMap(); ++ for (Field field : MobType.class.getDeclaredFields()) { ++ if (field.getType() == MobType.class) { ++ enumMonsterTypeFieldMap.put( (MobType) field.get(null), field.getName()); ++ } ++ } ++ ++ for (EntityCategory entityCategory : EntityCategory.values()) { ++ enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); ++ } ++ assertTrue(MobType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents", enumMonsterTypeFieldMap.size() == 0); ++ } ++} diff --git a/patches/server/0647-More-Enchantment-API.patch b/patches/server/0647-More-Enchantment-API.patch deleted file mode 100644 index 2f72e0f251..0000000000 --- a/patches/server/0647-More-Enchantment-API.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 6 May 2021 19:57:58 -0700 -Subject: [PATCH] More Enchantment API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -index 31a22f26070059e5379730c1940ff1c5fb109be1..873185fd4d4c994130f2e7c271b3e03cefb2278c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -@@ -71,7 +71,7 @@ public class CraftEnchantment extends Enchantment { - - @Override - public boolean isCursed() { -- return this.target instanceof BindingCurseEnchantment || this.target instanceof VanishingCurseEnchantment; -+ return this.target.isCurse(); // Paper - } - - @Override -@@ -199,6 +199,45 @@ public class CraftEnchantment extends Enchantment { - public String translationKey() { - return this.target.getDescriptionId(); - } -+ -+ @Override -+ public boolean isTradeable() { -+ return target.isTradeable(); -+ } -+ -+ @Override -+ public boolean isDiscoverable() { -+ return target.isDiscoverable(); -+ } -+ -+ @Override -+ public io.papermc.paper.enchantments.EnchantmentRarity getRarity() { -+ return fromNMSRarity(target.getRarity()); -+ } -+ -+ @Override -+ public float getDamageIncrease(int level, org.bukkit.entity.EntityCategory entityCategory) { -+ return target.getDamageBonus(level, org.bukkit.craftbukkit.entity.CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); -+ } -+ -+ @Override -+ public java.util.Set getActiveSlots() { -+ return java.util.stream.Stream.of(target.slots).map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot).collect(java.util.stream.Collectors.toSet()); -+ } -+ -+ public static io.papermc.paper.enchantments.EnchantmentRarity fromNMSRarity(net.minecraft.world.item.enchantment.Enchantment.Rarity nmsRarity) { -+ if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.COMMON; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.UNCOMMON; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.RARE; -+ } else if (nmsRarity == net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE) { -+ return io.papermc.paper.enchantments.EnchantmentRarity.VERY_RARE; -+ } -+ -+ throw new IllegalArgumentException(String.format("Unable to convert %s to a enum value of %s.", nmsRarity, io.papermc.paper.enchantments.EnchantmentRarity.class)); -+ } - // Paper end - - public net.minecraft.world.item.enchantment.Enchantment getHandle() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index a0ed78ffbfe8a236da273f796a016fe06875e10a..c33e33d2eba4630113a4399a0883af4b24ad943a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -872,5 +872,21 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public void setHurtDirection(float hurtDirection) { - getHandle().hurtDir = hurtDirection; - } -+ -+ public static MobType fromBukkitEntityCategory(EntityCategory entityCategory) { -+ switch (entityCategory) { -+ case NONE: -+ return MobType.UNDEFINED; -+ case UNDEAD: -+ return MobType.UNDEAD; -+ case ARTHROPOD: -+ return MobType.ARTHROPOD; -+ case ILLAGER: -+ return MobType.ILLAGER; -+ case WATER: -+ return MobType.WATER; -+ } -+ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category"); -+ } - // Paper end - } -diff --git a/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..62b56b5b43696b03fc72cac59f986d006edc3f76 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/enchantments/EnchantmentRarityTest.java -@@ -0,0 +1,18 @@ -+package io.papermc.paper.enchantments; -+ -+import net.minecraft.world.item.enchantment.Enchantment.Rarity; -+import org.bukkit.craftbukkit.enchantments.CraftEnchantment; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertNotNull; -+ -+public class EnchantmentRarityTest { -+ -+ @Test -+ public void test() { -+ for (Rarity nmsRarity : Rarity.values()) { -+ // Will throw exception if a bukkit counterpart is not found -+ CraftEnchantment.fromNMSRarity(nmsRarity); -+ } -+ } -+} -diff --git a/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b9824b1f9491304ceb91be18f4f3b3068526bb33 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/entity/EntityCategoryTest.java -@@ -0,0 +1,34 @@ -+package io.papermc.paper.entity; -+ -+import com.google.common.base.Joiner; -+import com.google.common.collect.Maps; -+import com.google.common.collect.Sets; -+import net.minecraft.world.entity.MobType; -+import org.bukkit.craftbukkit.entity.CraftLivingEntity; -+import org.bukkit.entity.EntityCategory; -+import org.junit.Test; -+ -+import java.lang.reflect.Field; -+import java.util.Map; -+import java.util.Set; -+ -+import static org.junit.Assert.assertTrue; -+ -+public class EntityCategoryTest { -+ -+ @Test -+ public void test() throws IllegalAccessException { -+ -+ Map enumMonsterTypeFieldMap = Maps.newHashMap(); -+ for (Field field : MobType.class.getDeclaredFields()) { -+ if (field.getType() == MobType.class) { -+ enumMonsterTypeFieldMap.put( (MobType) field.get(null), field.getName()); -+ } -+ } -+ -+ for (EntityCategory entityCategory : EntityCategory.values()) { -+ enumMonsterTypeFieldMap.remove(CraftLivingEntity.fromBukkitEntityCategory(entityCategory)); -+ } -+ assertTrue(MobType.class.getName() + " instance(s): " + Joiner.on(", ").join(enumMonsterTypeFieldMap.values()) + " do not have bukkit equivalents", enumMonsterTypeFieldMap.size() == 0); -+ } -+} diff --git a/patches/server/0647-Move-range-check-for-block-placing-up.patch b/patches/server/0647-Move-range-check-for-block-placing-up.patch new file mode 100644 index 0000000000..f4a82e33e1 --- /dev/null +++ b/patches/server/0647-Move-range-check-for-block-placing-up.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 8 Jun 2022 10:52:18 +0200 +Subject: [PATCH] Move range check for block placing up + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index de4c3849cc60151de8f3a873adad2bc36e30fbc4..3ef39ebeb76f43d521266402e170bd1af6af2348 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1830,6 +1830,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + ItemStack itemstack = this.player.getItemInHand(enumhand); + BlockHitResult movingobjectpositionblock = packet.getHitResult(); + Vec3 vec3d = movingobjectpositionblock.getLocation(); ++ // Paper start - improve distance check ++ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { ++ return; ++ } ++ // Paper end + BlockPos blockposition = movingobjectpositionblock.getBlockPos(); + Vec3 vec3d1 = Vec3.atCenterOf(blockposition); + diff --git a/patches/server/0648-Fix-and-optimise-world-force-upgrading.patch b/patches/server/0648-Fix-and-optimise-world-force-upgrading.patch new file mode 100644 index 0000000000..95adf64b12 --- /dev/null +++ b/patches/server/0648-Fix-and-optimise-world-force-upgrading.patch @@ -0,0 +1,389 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 20 May 2021 07:02:22 -0700 +Subject: [PATCH] Fix and optimise world force upgrading + +The WorldUpgrader class was incorrectly modified by +CB. It will store an IChunkLoader instance for all +dimension types in the world, but obviously with how +CB shifts around worlds only one dimension type exists +per world. But this would be OK if CB did this +change correctly. All IChunkLoader instances +will point to the same regionfiles. And all +IChunkLoader instances are going to be read from. + +This problem hasn't really been reported because +it relies on the persistent legacy data to be converted +as well to cause corruption. Why? Because the legacy +data is also shared, it will result in different +outputs from conversion (as once conversion for legacy +persistent data takes place, it is REMOVED - so the next +convert will _not_ have the data). Which means different +sizes on disk. Which means different regionfile sector +allocations. Which means there are 3 different possible +regionfile sector allocations in memory, and none of them +are going to be correct. + +I've fixed this by writing a world upgrader suited to +CB's changes to world folder format. It was brain dead +easy to add threading, so I did. + +diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..95cac7edae8ac64811fc6a2f6b97dd4a0fceb0b0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java +@@ -0,0 +1,209 @@ ++package io.papermc.paper.world; ++ ++import com.mojang.datafixers.DataFixer; ++import com.mojang.serialization.Codec; ++import net.minecraft.SharedConstants; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.util.worldupdate.WorldUpgrader; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkGenerator; ++import net.minecraft.world.level.chunk.storage.ChunkStorage; ++import net.minecraft.world.level.chunk.storage.RegionFileStorage; ++import net.minecraft.world.level.dimension.DimensionType; ++import net.minecraft.world.level.dimension.LevelStem; ++import net.minecraft.world.level.levelgen.WorldGenSettings; ++import net.minecraft.world.level.storage.DimensionDataStorage; ++import net.minecraft.world.level.storage.LevelStorageSource; ++import org.apache.logging.log4j.LogManager; ++import org.apache.logging.log4j.Logger; ++import java.io.File; ++import java.io.IOException; ++import java.text.DecimalFormat; ++import java.util.Optional; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.ThreadFactory; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.atomic.AtomicLong; ++import java.util.function.Supplier; ++ ++public class ThreadedWorldUpgrader { ++ ++ private static final Logger LOGGER = LogManager.getLogger(); ++ ++ private final ResourceKey dimensionType; ++ private final String worldName; ++ private final File worldDir; ++ private final ExecutorService threadPool; ++ private final DataFixer dataFixer; ++ private final Optional>> generatorKey; ++ private final boolean removeCaches; ++ ++ public ThreadedWorldUpgrader(final ResourceKey dimensionType, final String worldName, final File worldDir, final int threads, ++ final DataFixer dataFixer, final Optional>> generatorKey, final boolean removeCaches) { ++ this.dimensionType = dimensionType; ++ this.worldName = worldName; ++ this.worldDir = worldDir; ++ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() { ++ private final AtomicInteger threadCounter = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(final Runnable run) { ++ final Thread ret = new Thread(run); ++ ++ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement()); ++ ret.setUncaughtExceptionHandler((thread, throwable) -> { ++ LOGGER.fatal("Error upgrading world", throwable); ++ }); ++ ++ return ret; ++ } ++ }); ++ this.dataFixer = dataFixer; ++ this.generatorKey = generatorKey; ++ this.removeCaches = removeCaches; ++ } ++ ++ public void convert() { ++ final File worldFolder = LevelStorageSource.getStorageFolder(this.worldDir.toPath(), this.dimensionType).toFile(); ++ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer); ++ ++ final File regionFolder = new File(worldFolder, "region"); ++ ++ LOGGER.info("Force upgrading " + this.worldName); ++ LOGGER.info("Counting regionfiles for " + this.worldName); ++ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> { ++ return WorldUpgrader.REGEX.matcher(name).matches(); ++ }); ++ if (regionFiles == null) { ++ LOGGER.info("Found no regionfiles to convert for world " + this.worldName); ++ return; ++ } ++ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert"); ++ LOGGER.info("Starting conversion now for world " + this.worldName); ++ ++ final WorldInfo info = new WorldInfo(() -> worldPersistentData, ++ new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); ++ ++ long expectedChunks = (long)regionFiles.length * (32L * 32L); ++ ++ for (final File regionFile : regionFiles) { ++ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile.toPath()); ++ if (regionPos == null) { ++ expectedChunks -= (32L * 32L); ++ continue; ++ } ++ ++ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); ++ } ++ this.threadPool.shutdown(); ++ ++ final DecimalFormat format = new DecimalFormat("#0.00"); ++ ++ final long start = System.nanoTime(); ++ ++ while (!this.threadPool.isTerminated()) { ++ final long current = info.convertedChunks.get(); ++ ++ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks); ++ ++ try { ++ Thread.sleep(1000L); ++ } catch (final InterruptedException ignore) {} ++ } ++ ++ final long end = System.nanoTime(); ++ ++ try { ++ info.loader.close(); ++ } catch (final IOException ex) { ++ LOGGER.fatal("Failed to close chunk loader", ex); ++ } ++ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)", ++ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0)); ++ } ++ ++ private static final class WorldInfo { ++ ++ public final Supplier persistentDataSupplier; ++ public final ChunkStorage loader; ++ public final boolean removeCaches; ++ public final ResourceKey worldKey; ++ public final Optional>> generatorKey; ++ public final AtomicLong convertedChunks = new AtomicLong(); ++ public final AtomicLong modifiedChunks = new AtomicLong(); ++ ++ private WorldInfo(final Supplier persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches, ++ final ResourceKey worldKey, Optional>> generatorKey) { ++ this.persistentDataSupplier = persistentDataSupplier; ++ this.loader = loader; ++ this.removeCaches = removeCaches; ++ this.worldKey = worldKey; ++ this.generatorKey = generatorKey; ++ } ++ } ++ ++ private static final class ConvertTask implements Runnable { ++ ++ private final WorldInfo worldInfo; ++ private final int regionX; ++ private final int regionZ; ++ ++ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) { ++ this.worldInfo = worldInfo; ++ this.regionX = regionX; ++ this.regionZ = regionZ; ++ } ++ ++ @Override ++ public void run() { ++ final int regionCX = this.regionX << 5; ++ final int regionCZ = this.regionZ << 5; ++ ++ final Supplier persistentDataSupplier = this.worldInfo.persistentDataSupplier; ++ final ChunkStorage loader = this.worldInfo.loader; ++ final boolean removeCaches = this.worldInfo.removeCaches; ++ final ResourceKey worldKey = this.worldInfo.worldKey; ++ ++ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) { ++ for (int cx = regionCX; cx < (regionCX + 32); ++cx) { ++ final ChunkPos chunkPos = new ChunkPos(cx, cz); ++ try { ++ // no need to check the coordinate of the chunk, the regionfilecache does that for us ++ ++ CompoundTag chunkNBT = (loader.read(chunkPos).join()).orElse(null); ++ ++ if (chunkNBT == null) { ++ continue; ++ } ++ ++ final int versionBefore = ChunkStorage.getVersion(chunkNBT); ++ ++ chunkNBT = loader.upgradeChunkTag(worldKey, persistentDataSupplier, chunkNBT, this.worldInfo.generatorKey, chunkPos, null); ++ ++ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getWorldVersion(); ++ ++ if (removeCaches) { ++ final CompoundTag level = chunkNBT.getCompound("Level"); ++ modified |= level.contains("Heightmaps"); ++ level.remove("Heightmaps"); ++ modified |= level.contains("isLightOn"); ++ level.remove("isLightOn"); ++ } ++ ++ if (modified) { ++ this.worldInfo.modifiedChunks.getAndIncrement(); ++ loader.write(chunkPos, chunkNBT); ++ } ++ } catch (final Exception ex) { ++ LOGGER.error("Error upgrading chunk {}", chunkPos, ex); ++ } finally { ++ this.worldInfo.convertedChunks.getAndIncrement(); ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 2f82002c52af7304ff6b2d6fe8f094314daf0bba..5962f7a2b185d7d54a0f9e341a4fdf6e6f1c1ec5 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -16,6 +16,7 @@ import java.util.Objects; + import java.util.Optional; + import java.util.UUID; + import java.util.function.BooleanSupplier; ++import io.papermc.paper.world.ThreadedWorldUpgrader; + import joptsimple.NonOptionArgumentSpec; + import joptsimple.OptionParser; + import joptsimple.OptionSet; +@@ -314,6 +315,15 @@ public class Main { + + } + ++ // Paper start - fix and optimise world upgrading ++ public static void convertWorldButItWorks(net.minecraft.resources.ResourceKey dimensionType, net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess worldSession, ++ DataFixer dataFixer, Optional>> generatorKey, boolean removeCaches) { ++ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8; ++ final ThreadedWorldUpgrader worldUpgrader = new ThreadedWorldUpgrader(dimensionType, worldSession.getLevelId(), worldSession.levelDirectory.path().toFile(), threads, dataFixer, generatorKey, removeCaches); ++ worldUpgrader.convert(); ++ } ++ // Paper end - fix and optimise world upgrading ++ + public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, WorldGenSettings generatorOptions) { + Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit + WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, generatorOptions, eraseCache); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 3c5b7f4b2db421d56e5832e283bd60702b2d67de..84e76fbe3eca77b112c9dff936e21cba1c83e5aa 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -545,11 +545,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { +- return true; +- }, worlddata.worldGenSettings()); +- } ++ // Paper - move down + + PrimaryLevelData iworlddataserver = worlddata; + WorldGenSettings generatorsettings = worlddata.worldGenSettings(); +@@ -564,6 +560,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop worldKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, dimensionKey.location()); + + if (dimensionKey == LevelStem.OVERWORLD) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 525df1e5515fff204f790edcd0a051e851c03d33..ea2e75b227673f8b0016254f8c52a6721c8adf33 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -182,6 +182,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here + ++ // Paper start - fix and optimise world upgrading ++ // copied from below ++ public static ResourceKey getDimensionKey(DimensionType manager) { ++ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryHolder.ownedRegistryOrThrow(net.minecraft.core.Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(manager).orElseThrow(() -> { ++ return new IllegalStateException("Unregistered dimension type: " + manager); ++ }); ++ } ++ // Paper end - fix and optimise world upgrading ++ + public CraftWorld getWorld() { + return this.world; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 2a6a4a62feb1c02bef850b0cda578f6f9d46a5e3..666286f94f09299f95538ef2f19b588eb74d9dda 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -32,6 +32,28 @@ public class RegionFileStorage implements AutoCloseable { + } + + // Paper start ++ public static @Nullable ChunkPos getRegionFileCoordinates(Path file) { ++ String fileName = file.getFileName().toString(); ++ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkPos(x << 5, z << 5); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ + public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 9447b7cf08401608b2b79e8c68f58fd977cf0d42..dd43414dde55a5b447f9f51e4e5eef12fbbcfbde 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1204,12 +1204,7 @@ public final class CraftServer implements Server { + } + worlddata.checkName(name); + worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified()); +- +- if (console.options.has("forceUpgrade")) { +- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), console.options.has("eraseCache"), () -> { +- return true; +- }, worlddata.worldGenSettings()); +- } ++ // Paper - move down + + long j = BiomeManager.obfuscateSeed(creator.seed()); + List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); +@@ -1221,6 +1216,13 @@ public final class CraftServer implements Server { + biomeProvider = generator.getDefaultBiomeProvider(worldInfo); + } + ++ // Paper start - fix and optimise world upgrading ++ if (console.options.has("forceUpgrade")) { ++ net.minecraft.server.Main.convertWorldButItWorks( ++ actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), console.options.has("eraseCache") ++ ); ++ } ++ // Paper end - fix and optimise world upgrading + ResourceKey worldKey; + String levelName = this.getServer().getProperties().levelName; + if (name.equals(levelName + "_nether")) { diff --git a/patches/server/0648-Move-range-check-for-block-placing-up.patch b/patches/server/0648-Move-range-check-for-block-placing-up.patch deleted file mode 100644 index bb8c5f2bad..0000000000 --- a/patches/server/0648-Move-range-check-for-block-placing-up.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 8 Jun 2022 10:52:18 +0200 -Subject: [PATCH] Move range check for block placing up - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 66d5b3d44fb56aa6142f730e4742bb8f04f4d1c8..da9001a29b2ec2f715336c8187e6c918dd32db5e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1830,6 +1830,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - ItemStack itemstack = this.player.getItemInHand(enumhand); - BlockHitResult movingobjectpositionblock = packet.getHitResult(); - Vec3 vec3d = movingobjectpositionblock.getLocation(); -+ // Paper start - improve distance check -+ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { -+ return; -+ } -+ // Paper end - BlockPos blockposition = movingobjectpositionblock.getBlockPos(); - Vec3 vec3d1 = Vec3.atCenterOf(blockposition); - diff --git a/patches/server/0649-Add-Mob-lookAt-API.patch b/patches/server/0649-Add-Mob-lookAt-API.patch new file mode 100644 index 0000000000..34a4f03085 --- /dev/null +++ b/patches/server/0649-Add-Mob-lookAt-API.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Fri, 14 May 2021 13:42:17 -0500 +Subject: [PATCH] Add Mob#lookAt API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index 206b4d187a486e2c8a3a36eacb2d33f9d2555fe8..e19014a6e0d293973574c40c90c556aca17e0b0d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -91,5 +91,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public boolean isInDaylight() { + return getHandle().isSunBurnTick(); + } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); ++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle()); ++ } ++ ++ @Override ++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) { ++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); ++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); ++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z) { ++ getHandle().getLookControl().setLookAt(x, y, z); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) { ++ getHandle().getLookControl().setLookAt(x, y, z, headRotationSpeed, maxHeadPitch); ++ } ++ ++ @Override ++ public int getHeadRotationSpeed() { ++ return getHandle().getHeadRotSpeed(); ++ } ++ ++ @Override ++ public int getMaxHeadPitch() { ++ return getHandle().getMaxHeadXRot(); ++ } + // Paper end + } diff --git a/patches/server/0649-Fix-and-optimise-world-force-upgrading.patch b/patches/server/0649-Fix-and-optimise-world-force-upgrading.patch deleted file mode 100644 index a7d3f97242..0000000000 --- a/patches/server/0649-Fix-and-optimise-world-force-upgrading.patch +++ /dev/null @@ -1,389 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 20 May 2021 07:02:22 -0700 -Subject: [PATCH] Fix and optimise world force upgrading - -The WorldUpgrader class was incorrectly modified by -CB. It will store an IChunkLoader instance for all -dimension types in the world, but obviously with how -CB shifts around worlds only one dimension type exists -per world. But this would be OK if CB did this -change correctly. All IChunkLoader instances -will point to the same regionfiles. And all -IChunkLoader instances are going to be read from. - -This problem hasn't really been reported because -it relies on the persistent legacy data to be converted -as well to cause corruption. Why? Because the legacy -data is also shared, it will result in different -outputs from conversion (as once conversion for legacy -persistent data takes place, it is REMOVED - so the next -convert will _not_ have the data). Which means different -sizes on disk. Which means different regionfile sector -allocations. Which means there are 3 different possible -regionfile sector allocations in memory, and none of them -are going to be correct. - -I've fixed this by writing a world upgrader suited to -CB's changes to world folder format. It was brain dead -easy to add threading, so I did. - -diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..95cac7edae8ac64811fc6a2f6b97dd4a0fceb0b0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java -@@ -0,0 +1,209 @@ -+package io.papermc.paper.world; -+ -+import com.mojang.datafixers.DataFixer; -+import com.mojang.serialization.Codec; -+import net.minecraft.SharedConstants; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.util.worldupdate.WorldUpgrader; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkGenerator; -+import net.minecraft.world.level.chunk.storage.ChunkStorage; -+import net.minecraft.world.level.chunk.storage.RegionFileStorage; -+import net.minecraft.world.level.dimension.DimensionType; -+import net.minecraft.world.level.dimension.LevelStem; -+import net.minecraft.world.level.levelgen.WorldGenSettings; -+import net.minecraft.world.level.storage.DimensionDataStorage; -+import net.minecraft.world.level.storage.LevelStorageSource; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import java.io.File; -+import java.io.IOException; -+import java.text.DecimalFormat; -+import java.util.Optional; -+import java.util.concurrent.ExecutorService; -+import java.util.concurrent.Executors; -+import java.util.concurrent.ThreadFactory; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; -+import java.util.function.Supplier; -+ -+public class ThreadedWorldUpgrader { -+ -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ private final ResourceKey dimensionType; -+ private final String worldName; -+ private final File worldDir; -+ private final ExecutorService threadPool; -+ private final DataFixer dataFixer; -+ private final Optional>> generatorKey; -+ private final boolean removeCaches; -+ -+ public ThreadedWorldUpgrader(final ResourceKey dimensionType, final String worldName, final File worldDir, final int threads, -+ final DataFixer dataFixer, final Optional>> generatorKey, final boolean removeCaches) { -+ this.dimensionType = dimensionType; -+ this.worldName = worldName; -+ this.worldDir = worldDir; -+ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() { -+ private final AtomicInteger threadCounter = new AtomicInteger(); -+ -+ @Override -+ public Thread newThread(final Runnable run) { -+ final Thread ret = new Thread(run); -+ -+ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement()); -+ ret.setUncaughtExceptionHandler((thread, throwable) -> { -+ LOGGER.fatal("Error upgrading world", throwable); -+ }); -+ -+ return ret; -+ } -+ }); -+ this.dataFixer = dataFixer; -+ this.generatorKey = generatorKey; -+ this.removeCaches = removeCaches; -+ } -+ -+ public void convert() { -+ final File worldFolder = LevelStorageSource.getStorageFolder(this.worldDir.toPath(), this.dimensionType).toFile(); -+ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer); -+ -+ final File regionFolder = new File(worldFolder, "region"); -+ -+ LOGGER.info("Force upgrading " + this.worldName); -+ LOGGER.info("Counting regionfiles for " + this.worldName); -+ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> { -+ return WorldUpgrader.REGEX.matcher(name).matches(); -+ }); -+ if (regionFiles == null) { -+ LOGGER.info("Found no regionfiles to convert for world " + this.worldName); -+ return; -+ } -+ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert"); -+ LOGGER.info("Starting conversion now for world " + this.worldName); -+ -+ final WorldInfo info = new WorldInfo(() -> worldPersistentData, -+ new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); -+ -+ long expectedChunks = (long)regionFiles.length * (32L * 32L); -+ -+ for (final File regionFile : regionFiles) { -+ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile.toPath()); -+ if (regionPos == null) { -+ expectedChunks -= (32L * 32L); -+ continue; -+ } -+ -+ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5)); -+ } -+ this.threadPool.shutdown(); -+ -+ final DecimalFormat format = new DecimalFormat("#0.00"); -+ -+ final long start = System.nanoTime(); -+ -+ while (!this.threadPool.isTerminated()) { -+ final long current = info.convertedChunks.get(); -+ -+ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks); -+ -+ try { -+ Thread.sleep(1000L); -+ } catch (final InterruptedException ignore) {} -+ } -+ -+ final long end = System.nanoTime(); -+ -+ try { -+ info.loader.close(); -+ } catch (final IOException ex) { -+ LOGGER.fatal("Failed to close chunk loader", ex); -+ } -+ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)", -+ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0)); -+ } -+ -+ private static final class WorldInfo { -+ -+ public final Supplier persistentDataSupplier; -+ public final ChunkStorage loader; -+ public final boolean removeCaches; -+ public final ResourceKey worldKey; -+ public final Optional>> generatorKey; -+ public final AtomicLong convertedChunks = new AtomicLong(); -+ public final AtomicLong modifiedChunks = new AtomicLong(); -+ -+ private WorldInfo(final Supplier persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches, -+ final ResourceKey worldKey, Optional>> generatorKey) { -+ this.persistentDataSupplier = persistentDataSupplier; -+ this.loader = loader; -+ this.removeCaches = removeCaches; -+ this.worldKey = worldKey; -+ this.generatorKey = generatorKey; -+ } -+ } -+ -+ private static final class ConvertTask implements Runnable { -+ -+ private final WorldInfo worldInfo; -+ private final int regionX; -+ private final int regionZ; -+ -+ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) { -+ this.worldInfo = worldInfo; -+ this.regionX = regionX; -+ this.regionZ = regionZ; -+ } -+ -+ @Override -+ public void run() { -+ final int regionCX = this.regionX << 5; -+ final int regionCZ = this.regionZ << 5; -+ -+ final Supplier persistentDataSupplier = this.worldInfo.persistentDataSupplier; -+ final ChunkStorage loader = this.worldInfo.loader; -+ final boolean removeCaches = this.worldInfo.removeCaches; -+ final ResourceKey worldKey = this.worldInfo.worldKey; -+ -+ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) { -+ for (int cx = regionCX; cx < (regionCX + 32); ++cx) { -+ final ChunkPos chunkPos = new ChunkPos(cx, cz); -+ try { -+ // no need to check the coordinate of the chunk, the regionfilecache does that for us -+ -+ CompoundTag chunkNBT = (loader.read(chunkPos).join()).orElse(null); -+ -+ if (chunkNBT == null) { -+ continue; -+ } -+ -+ final int versionBefore = ChunkStorage.getVersion(chunkNBT); -+ -+ chunkNBT = loader.upgradeChunkTag(worldKey, persistentDataSupplier, chunkNBT, this.worldInfo.generatorKey, chunkPos, null); -+ -+ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getWorldVersion(); -+ -+ if (removeCaches) { -+ final CompoundTag level = chunkNBT.getCompound("Level"); -+ modified |= level.contains("Heightmaps"); -+ level.remove("Heightmaps"); -+ modified |= level.contains("isLightOn"); -+ level.remove("isLightOn"); -+ } -+ -+ if (modified) { -+ this.worldInfo.modifiedChunks.getAndIncrement(); -+ loader.write(chunkPos, chunkNBT); -+ } -+ } catch (final Exception ex) { -+ LOGGER.error("Error upgrading chunk {}", chunkPos, ex); -+ } finally { -+ this.worldInfo.convertedChunks.getAndIncrement(); -+ } -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 2f82002c52af7304ff6b2d6fe8f094314daf0bba..5962f7a2b185d7d54a0f9e341a4fdf6e6f1c1ec5 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -16,6 +16,7 @@ import java.util.Objects; - import java.util.Optional; - import java.util.UUID; - import java.util.function.BooleanSupplier; -+import io.papermc.paper.world.ThreadedWorldUpgrader; - import joptsimple.NonOptionArgumentSpec; - import joptsimple.OptionParser; - import joptsimple.OptionSet; -@@ -314,6 +315,15 @@ public class Main { - - } - -+ // Paper start - fix and optimise world upgrading -+ public static void convertWorldButItWorks(net.minecraft.resources.ResourceKey dimensionType, net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess worldSession, -+ DataFixer dataFixer, Optional>> generatorKey, boolean removeCaches) { -+ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8; -+ final ThreadedWorldUpgrader worldUpgrader = new ThreadedWorldUpgrader(dimensionType, worldSession.getLevelId(), worldSession.levelDirectory.path().toFile(), threads, dataFixer, generatorKey, removeCaches); -+ worldUpgrader.convert(); -+ } -+ // Paper end - fix and optimise world upgrading -+ - public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, WorldGenSettings generatorOptions) { - Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit - WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, generatorOptions, eraseCache); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3c5b7f4b2db421d56e5832e283bd60702b2d67de..84e76fbe3eca77b112c9dff936e21cba1c83e5aa 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -545,11 +545,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- return true; -- }, worlddata.worldGenSettings()); -- } -+ // Paper - move down - - PrimaryLevelData iworlddataserver = worlddata; - WorldGenSettings generatorsettings = worlddata.worldGenSettings(); -@@ -564,6 +560,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop worldKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, dimensionKey.location()); - - if (dimensionKey == LevelStem.OVERWORLD) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d9a88b29cfefcdbce7bfc477b6c1af0e51079102..c21274a72dca31c9160ecbcfa7eb42de64e91454 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -182,6 +182,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions - public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here - -+ // Paper start - fix and optimise world upgrading -+ // copied from below -+ public static ResourceKey getDimensionKey(DimensionType manager) { -+ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryHolder.ownedRegistryOrThrow(net.minecraft.core.Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(manager).orElseThrow(() -> { -+ return new IllegalStateException("Unregistered dimension type: " + manager); -+ }); -+ } -+ // Paper end - fix and optimise world upgrading -+ - public CraftWorld getWorld() { - return this.world; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 2a6a4a62feb1c02bef850b0cda578f6f9d46a5e3..666286f94f09299f95538ef2f19b588eb74d9dda 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -32,6 +32,28 @@ public class RegionFileStorage implements AutoCloseable { - } - - // Paper start -+ public static @Nullable ChunkPos getRegionFileCoordinates(Path file) { -+ String fileName = file.getFileName().toString(); -+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { -+ return null; -+ } -+ -+ String[] split = fileName.split("\\."); -+ -+ if (split.length != 4) { -+ return null; -+ } -+ -+ try { -+ int x = Integer.parseInt(split[1]); -+ int z = Integer.parseInt(split[2]); -+ -+ return new ChunkPos(x << 5, z << 5); -+ } catch (NumberFormatException ex) { -+ return null; -+ } -+ } -+ - public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) { - return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ())); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 9447b7cf08401608b2b79e8c68f58fd977cf0d42..dd43414dde55a5b447f9f51e4e5eef12fbbcfbde 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1204,12 +1204,7 @@ public final class CraftServer implements Server { - } - worlddata.checkName(name); - worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified()); -- -- if (console.options.has("forceUpgrade")) { -- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), console.options.has("eraseCache"), () -> { -- return true; -- }, worlddata.worldGenSettings()); -- } -+ // Paper - move down - - long j = BiomeManager.obfuscateSeed(creator.seed()); - List list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata)); -@@ -1221,6 +1216,13 @@ public final class CraftServer implements Server { - biomeProvider = generator.getDefaultBiomeProvider(worldInfo); - } - -+ // Paper start - fix and optimise world upgrading -+ if (console.options.has("forceUpgrade")) { -+ net.minecraft.server.Main.convertWorldButItWorks( -+ actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), console.options.has("eraseCache") -+ ); -+ } -+ // Paper end - fix and optimise world upgrading - ResourceKey worldKey; - String levelName = this.getServer().getProperties().levelName; - if (name.equals(levelName + "_nether")) { diff --git a/patches/server/0650-Add-Mob-lookAt-API.patch b/patches/server/0650-Add-Mob-lookAt-API.patch deleted file mode 100644 index 14378270ee..0000000000 --- a/patches/server/0650-Add-Mob-lookAt-API.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Fri, 14 May 2021 13:42:17 -0500 -Subject: [PATCH] Add Mob#lookAt API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index 881bb11507eafe87522ad4131ea7859f42918b3e..d9008049188c1933f2b6b39b9219983ff947b4bf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -82,5 +82,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public boolean isInDaylight() { - return getHandle().isSunBurnTick(); - } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) { -+ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); -+ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ()); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) { -+ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world"); -+ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) { -+ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); -+ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle()); -+ } -+ -+ @Override -+ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) { -+ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null"); -+ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world"); -+ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z) { -+ getHandle().getLookControl().setLookAt(x, y, z); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) { -+ getHandle().getLookControl().setLookAt(x, y, z, headRotationSpeed, maxHeadPitch); -+ } -+ -+ @Override -+ public int getHeadRotationSpeed() { -+ return getHandle().getHeadRotSpeed(); -+ } -+ -+ @Override -+ public int getMaxHeadPitch() { -+ return getHandle().getMaxHeadXRot(); -+ } - // Paper end - } diff --git a/patches/server/0650-Add-Unix-domain-socket-support.patch b/patches/server/0650-Add-Unix-domain-socket-support.patch new file mode 100644 index 0000000000..7873f07d98 --- /dev/null +++ b/patches/server/0650-Add-Unix-domain-socket-support.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Tue, 11 May 2021 17:39:22 -0400 +Subject: [PATCH] Add Unix domain socket support + +For Windows and ARM support, JEP-380 is required: +https://inside.java/2021/02/03/jep380-unix-domain-sockets-channels/ +This will be possible as of the Minecraft 1.17 Java version bump. + +Tested-by: Mariell Hoversholm +Reviewed-by: Mariell Hoversholm + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 86cc291b5b14523d57c84f8ebd6ba9b9c3b0d1a6..b5f884d6671823085a2ab0e8da2d30afd2928f32 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -641,6 +641,11 @@ public class Connection extends SimpleChannelInboundHandler> { + // Spigot Start + public SocketAddress getRawAddress() + { ++ // Paper start - this can be nullable in the case of a Unix domain socket, so if it is, fake something ++ if (this.channel.remoteAddress() == null) { ++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); ++ } ++ // Paper end + return this.channel.remoteAddress(); + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 2c215e2080f00d6c875fbde92fd2c1c051d0cf98..2d01a1d4b2f7fdd38a6b1022f2476ba68b663171 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -224,6 +224,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); + // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading + DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress bindAddress; ++ if (this.getLocalIp().startsWith("unix:")) { ++ if (!io.netty.channel.epoll.Epoll.isAvailable()) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); ++ return false; ++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { ++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); ++ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); ++ return false; ++ } ++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); ++ } else { + InetAddress inetaddress = null; + + if (!this.getLocalIp().isEmpty()) { +@@ -233,12 +247,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + if (this.getPort() < 0) { + this.setPort(dedicatedserverproperties.serverPort); + } ++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); ++ } ++ // Paper end + + this.initializeKeyPair(); + DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); + + try { +- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); ++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support + } catch (IOException ioexception) { + DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); + DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 83af90fb0dcb4b1a5a68f655cf66d101b472e8e7..b80aedd2002959b4026c27ce76b3ed17f0acfb5b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -80,7 +80,12 @@ public class ServerConnectionListener { + this.running = true; + } + ++ // Paper start + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ bind(new java.net.InetSocketAddress(address, port)); ++ } ++ public void bind(java.net.SocketAddress address) throws IOException { ++ // Paper end + List list = this.channels; + + synchronized (this.channels) { +@@ -88,7 +93,11 @@ public class ServerConnectionListener { + LazyLoadedValue lazyinitvar; + + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + oclass = EpollServerSocketChannel.class; ++ } + lazyinitvar = ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP; + ServerConnectionListener.LOGGER.info("Using epoll channel type"); + } else { +@@ -116,7 +125,7 @@ public class ServerConnectionListener { + ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper + } +- }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit ++ }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper + } + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index 53833fdd748098b662d4420a254401c0d3982e56..b02cbf6bcc57167a1373925f652950e0212dfa4f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -43,6 +43,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + this.connection.setProtocol(ConnectionProtocol.LOGIN); + // CraftBukkit start - Connection throttle + try { ++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - the connection throttle is useless when you have a Unix domain socket + long currentTime = System.currentTimeMillis(); + long connectionThrottle = this.server.server.getConnectionThrottle(); + InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); +@@ -71,6 +72,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + } + } + } ++ } // Paper - add closing bracket for if check above + } catch (Throwable t) { + org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); + } +@@ -119,8 +121,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + // Paper end + // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! + if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper ++ // Paper start - Unix domain socket support ++ java.net.SocketAddress socketAddress = connection.getRemoteAddress(); + packet.hostName = split[0]; +- connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); ++ connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); ++ // Paper end + connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); + } else + { diff --git a/patches/server/0651-Add-EntityInsideBlockEvent.patch b/patches/server/0651-Add-EntityInsideBlockEvent.patch new file mode 100644 index 0000000000..0ca02bfa9a --- /dev/null +++ b/patches/server/0651-Add-EntityInsideBlockEvent.patch @@ -0,0 +1,258 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 18:02:36 -0700 +Subject: [PATCH] Add EntityInsideBlockEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +index 922b5b22a4ccfeead9d6d2b9a2a2b3cc8a1e6c55..a76c452dc5c2069a3071aec31bfb3e977867161e 100644 +--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java +@@ -121,6 +121,7 @@ public abstract class BaseFireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!entity.fireImmune()) { + entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); + if (entity.getRemainingFireTicks() == 0) { +diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +index af808ddea455b2df9e551ce32dcd5bb472623dd9..f9aaec28be3e7a191981d30b361e369d7fea2c9e 100644 +--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java +@@ -67,6 +67,7 @@ public abstract class BasePressurePlateBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + int i = this.getSignalForState(state); + +diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +index ce32a0582c6d86e754710daa1413ff46da05dc56..63aa6b82ba21ec8e8f362b390063e4e275a979a5 100644 +--- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -170,6 +170,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { + // CraftBukkit start - tilt dripleaf +diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +index 3da8d49f7e36d8f1c0873bec32123971e53d2a31..461288cb56793f11e8dac80720b36cb9b42da518 100644 +--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +@@ -38,6 +38,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + BlockState blockState = world.getBlockState(pos.above()); + if (blockState.isAir()) { + entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); +diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +index c187e9df237ee71562343bbb4b577b2dcd9b4f1c..a0194e78913017693df7d92516dfbacb1153a1c2 100644 +--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java +@@ -186,6 +186,7 @@ public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide && this.sensitive && !(Boolean) state.getValue(ButtonBlock.POWERED)) { + this.checkPressed(state, world, pos); + } +diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +index cb3d26af146859b87fc471174f8f63dfe7caa5fd..0fbabb84ef13e68b12212d9bfeb885c78893c116 100644 +--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java +@@ -116,6 +116,7 @@ public class CactusBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit + entity.hurt(DamageSource.CACTUS, 1.0F); + CraftEventFactory.blockDamage = null; // CraftBukkit +diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +index 930421c72326fabfa3f2e3ab37c4dd6f416d6d44..a4c44cb59dee29cf227dbb51bfc1576d89dfb2e3 100644 +--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +@@ -93,6 +93,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit + entity.hurt(DamageSource.IN_FIRE, (float) this.fireDamage); +diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java +index 76fc886013b6c53f7888292f8fda50abe72e43bf..275e5334b1206a2dcafc3772c7e2ad0ebe3693f9 100644 +--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java +@@ -163,6 +163,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (entity instanceof Ravager && !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // CraftBukkit + world.destroyBlock(pos, true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +index 932a2c279f46c951182d2604b525b473b6945895..05dfb1790a292f9f85b641377c2ca3675726c127 100644 +--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java +@@ -44,6 +44,7 @@ public class DetectorRailBlock extends BaseRailBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { + this.checkPressed(world, pos, state); +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 3b54eb4962a0cd39e6ff7a934f814de864a16a3d..150c16da7caa655cfc2c371d3336a8d7345438c6 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -44,6 +44,7 @@ public class EndPortalBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (world instanceof ServerLevel && !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { + ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends + ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); +diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +index 0549256cbd7028c82bf82ccc4ff64219df7e0906..683f24251baf8ef3bef8f32ba83dc7f0e8ed7d70 100644 +--- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +@@ -55,6 +55,7 @@ public class HoneyBlock extends HalfTransparentBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (this.isSlidingDown(pos, entity)) { + this.maybeDoSlideAchievement(entity, pos); + this.doSlideMovement(entity); +diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +index c89bce01302348115791732fb31ce48aec7239d4..45224b264c7500a9d4342864cf67e7d1550c8103 100644 +--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java +@@ -200,6 +200,7 @@ public class HopperBlock extends BaseEntityBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof HopperBlockEntity) { + HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); +diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +index f0a3ef0529951e7732602d358ddea1782001db7e..6588b207d93d96934e72176874ba60c81e9a098c 100644 +--- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java +@@ -24,6 +24,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (this.isEntityInsideContent(state, pos, entity)) { + entity.lavaHurt(); + } +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 2d0191eeb746d496a481d66cdfa77078313a13ec..24d2da792bc498adf4251555a538df4cafe2e827 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -60,6 +60,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start + if (entity.mayInteract(world, pos)) { +diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +index 8c97cae63b4b373f1d67e797b9fe1064b5205da5..08a84e8fdb242f467fb20eec73764ef71691ad42 100644 +--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +@@ -85,6 +85,7 @@ public class NetherPortalBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions()) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +index 47f54002654d198a56a85884de34e305e545eb4b..518d3832c36c9ecf1ed9267ffc1f926dc84b7989 100644 +--- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +@@ -55,6 +55,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!(entity instanceof LivingEntity) || entity.getFeetBlockState().is((Block) this)) { + entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); + if (world.isClientSide) { +diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +index 272ec85b1bde4b7a9439ab8fbb2711f3adb65b55..1d28810f697565e34d59ffc8dbf55173c2a671ea 100644 +--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -77,6 +77,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { + entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); + if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index 5e1133bf2cba55a6ec5559d8db41e1a3db582d06..4e2fb4ee8e46b3c363992ff23e26f5a648c5f003 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -121,6 +121,7 @@ public class TripWireBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide) { + if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { + this.checkPressed(world, pos); +diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +index 2b2a28d0383ccc8c0e7debd90331570b02b5e65f..bd4295f8d24ca9fd8c3af31abcd13da24db1c5d5 100644 +--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java +@@ -25,6 +25,7 @@ public class WaterlilyBlock extends BushBlock { + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { + super.entityInside(state, world, pos, entity); ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (world instanceof ServerLevel && entity instanceof Boat && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit + world.destroyBlock(new BlockPos(pos), true, entity); + } +diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java +index 6964308822ebf8a7027ce426062ba43a70c20c15..763fa221c562e96c2abd09c7055e91a86ac03d43 100644 +--- a/src/main/java/net/minecraft/world/level/block/WebBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java +@@ -14,6 +14,7 @@ public class WebBlock extends Block { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + entity.makeStuckInBlock(state, new Vec3(0.25D, (double)0.05F, 0.25D)); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +index 7acac8e59d6d46d03f6a15f0657b6028a63f752a..7d5f7983bbbcb004a1334f22dbe47b477ea5b750 100644 +--- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java +@@ -46,6 +46,7 @@ public class WitherRoseBlock extends FlowerBlock { + + @Override + public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { ++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper + if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { + if (entity instanceof LivingEntity) { + LivingEntity entityliving = (LivingEntity) entity; diff --git a/patches/server/0651-Add-Unix-domain-socket-support.patch b/patches/server/0651-Add-Unix-domain-socket-support.patch deleted file mode 100644 index 7873f07d98..0000000000 --- a/patches/server/0651-Add-Unix-domain-socket-support.patch +++ /dev/null @@ -1,141 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Tue, 11 May 2021 17:39:22 -0400 -Subject: [PATCH] Add Unix domain socket support - -For Windows and ARM support, JEP-380 is required: -https://inside.java/2021/02/03/jep380-unix-domain-sockets-channels/ -This will be possible as of the Minecraft 1.17 Java version bump. - -Tested-by: Mariell Hoversholm -Reviewed-by: Mariell Hoversholm - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 86cc291b5b14523d57c84f8ebd6ba9b9c3b0d1a6..b5f884d6671823085a2ab0e8da2d30afd2928f32 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -641,6 +641,11 @@ public class Connection extends SimpleChannelInboundHandler> { - // Spigot Start - public SocketAddress getRawAddress() - { -+ // Paper start - this can be nullable in the case of a Unix domain socket, so if it is, fake something -+ if (this.channel.remoteAddress() == null) { -+ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0); -+ } -+ // Paper end - return this.channel.remoteAddress(); - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 2c215e2080f00d6c875fbde92fd2c1c051d0cf98..2d01a1d4b2f7fdd38a6b1022f2476ba68b663171 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -224,6 +224,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist); - // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading - DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress bindAddress; -+ if (this.getLocalIp().startsWith("unix:")) { -+ if (!io.netty.channel.epoll.Epoll.isAvailable()) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS."); -+ return false; -+ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) { -+ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!"); -+ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy."); -+ return false; -+ } -+ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length())); -+ } else { - InetAddress inetaddress = null; - - if (!this.getLocalIp().isEmpty()) { -@@ -233,12 +247,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (this.getPort() < 0) { - this.setPort(dedicatedserverproperties.serverPort); - } -+ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort()); -+ } -+ // Paper end - - this.initializeKeyPair(); - DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); - - try { -- this.getConnection().startTcpServerListener(inetaddress, this.getPort()); -+ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support - } catch (IOException ioexception) { - DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!"); - DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString()); -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 83af90fb0dcb4b1a5a68f655cf66d101b472e8e7..b80aedd2002959b4026c27ce76b3ed17f0acfb5b 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -80,7 +80,12 @@ public class ServerConnectionListener { - this.running = true; - } - -+ // Paper start - public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { -+ bind(new java.net.InetSocketAddress(address, port)); -+ } -+ public void bind(java.net.SocketAddress address) throws IOException { -+ // Paper end - List list = this.channels; - - synchronized (this.channels) { -@@ -88,7 +93,11 @@ public class ServerConnectionListener { - LazyLoadedValue lazyinitvar; - - if (Epoll.isAvailable() && this.server.isEpollEnabled()) { -+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { -+ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; -+ } else { - oclass = EpollServerSocketChannel.class; -+ } - lazyinitvar = ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP; - ServerConnectionListener.LOGGER.info("Using epoll channel type"); - } else { -@@ -116,7 +125,7 @@ public class ServerConnectionListener { - ((Connection) object).setListener(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); - io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - } -- }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit -+ }).group((EventLoopGroup) lazyinitvar.get()).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - } - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index 53833fdd748098b662d4420a254401c0d3982e56..b02cbf6bcc57167a1373925f652950e0212dfa4f 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -43,6 +43,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - this.connection.setProtocol(ConnectionProtocol.LOGIN); - // CraftBukkit start - Connection throttle - try { -+ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - the connection throttle is useless when you have a Unix domain socket - long currentTime = System.currentTimeMillis(); - long connectionThrottle = this.server.server.getConnectionThrottle(); - InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); -@@ -71,6 +72,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - } - } - } -+ } // Paper - add closing bracket for if check above - } catch (Throwable t) { - org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); - } -@@ -119,8 +121,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - // Paper end - // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! - if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper -+ // Paper start - Unix domain socket support -+ java.net.SocketAddress socketAddress = connection.getRemoteAddress(); - packet.hostName = split[0]; -- connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort()); -+ connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); -+ // Paper end - connection.spoofedUUID = com.mojang.util.UUIDTypeAdapter.fromString( split[2] ); - } else - { diff --git a/patches/server/0652-Add-EntityInsideBlockEvent.patch b/patches/server/0652-Add-EntityInsideBlockEvent.patch deleted file mode 100644 index 0ca02bfa9a..0000000000 --- a/patches/server/0652-Add-EntityInsideBlockEvent.patch +++ /dev/null @@ -1,258 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 18:02:36 -0700 -Subject: [PATCH] Add EntityInsideBlockEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -index 922b5b22a4ccfeead9d6d2b9a2a2b3cc8a1e6c55..a76c452dc5c2069a3071aec31bfb3e977867161e 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java -@@ -121,6 +121,7 @@ public abstract class BaseFireBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!entity.fireImmune()) { - entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1); - if (entity.getRemainingFireTicks() == 0) { -diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -index af808ddea455b2df9e551ce32dcd5bb472623dd9..f9aaec28be3e7a191981d30b361e369d7fea2c9e 100644 ---- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java -@@ -67,6 +67,7 @@ public abstract class BasePressurePlateBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide) { - int i = this.getSignalForState(state); - -diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -index ce32a0582c6d86e754710daa1413ff46da05dc56..63aa6b82ba21ec8e8f362b390063e4e275a979a5 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -170,6 +170,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide) { - if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) { - // CraftBukkit start - tilt dripleaf -diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -index 3da8d49f7e36d8f1c0873bec32123971e53d2a31..461288cb56793f11e8dac80720b36cb9b42da518 100644 ---- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -@@ -38,6 +38,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - BlockState blockState = world.getBlockState(pos.above()); - if (blockState.isAir()) { - entity.onAboveBubbleCol(state.getValue(DRAG_DOWN)); -diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -index c187e9df237ee71562343bbb4b577b2dcd9b4f1c..a0194e78913017693df7d92516dfbacb1153a1c2 100644 ---- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java -@@ -186,6 +186,7 @@ public abstract class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide && this.sensitive && !(Boolean) state.getValue(ButtonBlock.POWERED)) { - this.checkPressed(state, world, pos); - } -diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index cb3d26af146859b87fc471174f8f63dfe7caa5fd..0fbabb84ef13e68b12212d9bfeb885c78893c116 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -116,6 +116,7 @@ public class CactusBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit - entity.hurt(DamageSource.CACTUS, 1.0F); - CraftEventFactory.blockDamage = null; // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -index 930421c72326fabfa3f2e3ab37c4dd6f416d6d44..a4c44cb59dee29cf227dbb51bfc1576d89dfb2e3 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -93,6 +93,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit - entity.hurt(DamageSource.IN_FIRE, (float) this.fireDamage); -diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java -index 76fc886013b6c53f7888292f8fda50abe72e43bf..275e5334b1206a2dcafc3772c7e2ad0ebe3693f9 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -163,6 +163,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (entity instanceof Ravager && !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // CraftBukkit - world.destroyBlock(pos, true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -index 932a2c279f46c951182d2604b525b473b6945895..05dfb1790a292f9f85b641377c2ca3675726c127 100644 ---- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java -@@ -44,6 +44,7 @@ public class DetectorRailBlock extends BaseRailBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide) { - if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) { - this.checkPressed(world, pos, state); -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 3b54eb4962a0cd39e6ff7a934f814de864a16a3d..150c16da7caa655cfc2c371d3336a8d7345438c6 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -44,6 +44,7 @@ public class EndPortalBlock extends BaseEntityBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (world instanceof ServerLevel && !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { - ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends - ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); -diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -index 0549256cbd7028c82bf82ccc4ff64219df7e0906..683f24251baf8ef3bef8f32ba83dc7f0e8ed7d70 100644 ---- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -@@ -55,6 +55,7 @@ public class HoneyBlock extends HalfTransparentBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (this.isSlidingDown(pos, entity)) { - this.maybeDoSlideAchievement(entity, pos); - this.doSlideMovement(entity); -diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -index c89bce01302348115791732fb31ce48aec7239d4..45224b264c7500a9d4342864cf67e7d1550c8103 100644 ---- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java -@@ -200,6 +200,7 @@ public class HopperBlock extends BaseEntityBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof HopperBlockEntity) { - HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity); -diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -index f0a3ef0529951e7732602d358ddea1782001db7e..6588b207d93d96934e72176874ba60c81e9a098c 100644 ---- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java -@@ -24,6 +24,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (this.isEntityInsideContent(state, pos, entity)) { - entity.lavaHurt(); - } -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 2d0191eeb746d496a481d66cdfa77078313a13ec..24d2da792bc498adf4251555a538df4cafe2e827 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -60,6 +60,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start - if (entity.mayInteract(world, pos)) { -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index 8c97cae63b4b373f1d67e797b9fe1064b5205da5..08a84e8fdb242f467fb20eec73764ef71691ad42 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -85,6 +85,7 @@ public class NetherPortalBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions()) { - // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -index 47f54002654d198a56a85884de34e305e545eb4b..518d3832c36c9ecf1ed9267ffc1f926dc84b7989 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -55,6 +55,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!(entity instanceof LivingEntity) || entity.getFeetBlockState().is((Block) this)) { - entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D)); - if (world.isClientSide) { -diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -index 272ec85b1bde4b7a9439ab8fbb2711f3adb65b55..1d28810f697565e34d59ffc8dbf55173c2a671ea 100644 ---- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java -@@ -77,6 +77,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) { - entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D)); - if (!world.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) { -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -index 5e1133bf2cba55a6ec5559d8db41e1a3db582d06..4e2fb4ee8e46b3c363992ff23e26f5a648c5f003 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -@@ -121,6 +121,7 @@ public class TripWireBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide) { - if (!(Boolean) state.getValue(TripWireBlock.POWERED)) { - this.checkPressed(world, pos); -diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -index 2b2a28d0383ccc8c0e7debd90331570b02b5e65f..bd4295f8d24ca9fd8c3af31abcd13da24db1c5d5 100644 ---- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java -@@ -25,6 +25,7 @@ public class WaterlilyBlock extends BushBlock { - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - super.entityInside(state, world, pos, entity); -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (world instanceof ServerLevel && entity instanceof Boat && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit - world.destroyBlock(new BlockPos(pos), true, entity); - } -diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java -index 6964308822ebf8a7027ce426062ba43a70c20c15..763fa221c562e96c2abd09c7055e91a86ac03d43 100644 ---- a/src/main/java/net/minecraft/world/level/block/WebBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java -@@ -14,6 +14,7 @@ public class WebBlock extends Block { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - entity.makeStuckInBlock(state, new Vec3(0.25D, (double)0.05F, 0.25D)); - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -index 7acac8e59d6d46d03f6a15f0657b6028a63f752a..7d5f7983bbbcb004a1334f22dbe47b477ea5b750 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java -@@ -46,6 +46,7 @@ public class WitherRoseBlock extends FlowerBlock { - - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { -+ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (!world.isClientSide && world.getDifficulty() != Difficulty.PEACEFUL) { - if (entity instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) entity; diff --git a/patches/server/0652-Attributes-API-for-item-defaults.patch b/patches/server/0652-Attributes-API-for-item-defaults.patch new file mode 100644 index 0000000000..882921f7c0 --- /dev/null +++ b/patches/server/0652-Attributes-API-for-item-defaults.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 8 May 2021 15:01:54 -0700 +Subject: [PATCH] Attributes API for item defaults + + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 0ebcadd6daf244cd9b6c943ca0a2baaafb3eba50..6fd3bbc36cb6e270a10f778fe2764823f90cca9c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -545,6 +545,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); + } + ++ @Override ++ public Multimap getItemAttributes(Material material, EquipmentSlot equipmentSlot) { ++ Item item = CraftMagicNumbers.getItem(material); ++ if (item == null) { ++ throw new IllegalArgumentException(material + " is not an item and therefore does not have attributes"); ++ } ++ ImmutableMultimap.Builder attributeMapBuilder = ImmutableMultimap.builder(); ++ item.getDefaultAttributeModifiers(CraftEquipmentSlot.getNMS(equipmentSlot)).forEach((attributeBase, attributeModifier) -> { ++ attributeMapBuilder.put(CraftAttributeMap.fromMinecraft(net.minecraft.core.Registry.ATTRIBUTE.getKey(attributeBase).toString()), CraftAttributeInstance.convert(attributeModifier, equipmentSlot)); ++ }); ++ return attributeMapBuilder.build(); ++ } ++ + @Override + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); diff --git a/patches/server/0653-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0653-Add-cause-to-Weather-ThunderChangeEvents.patch new file mode 100644 index 0000000000..ebde715e98 --- /dev/null +++ b/patches/server/0653-Add-cause-to-Weather-ThunderChangeEvents.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 18:23:26 -0800 +Subject: [PATCH] Add cause to Weather/ThunderChangeEvents + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 7f121ce977fd5779032450443c94634bc919009d..6e9f1f01227a94480043ba3120c77f1ae080ec02 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -498,8 +498,8 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.serverLevelData.setClearWeatherTime(clearDuration); + this.serverLevelData.setRainTime(rainDuration); + this.serverLevelData.setThunderTime(rainDuration); +- this.serverLevelData.setRaining(raining); +- this.serverLevelData.setThundering(thundering); ++ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper ++ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper + } + + @Override +@@ -891,8 +891,8 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.serverLevelData.setThunderTime(j); + this.serverLevelData.setRainTime(k); + this.serverLevelData.setClearWeatherTime(i); +- this.serverLevelData.setThundering(flag1); +- this.serverLevelData.setRaining(flag2); ++ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper ++ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper + } + + this.oThunderLevel = this.thunderLevel; +@@ -958,14 +958,14 @@ public class ServerLevel extends Level implements WorldGenLevel { + + private void resetWeatherCycle() { + // CraftBukkit start +- this.serverLevelData.setRaining(false); ++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... + if (!this.serverLevelData.isRaining()) { + this.serverLevelData.setRainTime(0); + } + // CraftBukkit end +- this.serverLevelData.setThundering(false); ++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night + // CraftBukkit start + // If we stop due to everyone sleeping we should reset the weather duration to some other random value. + // Not that everyone ever manages to get the whole server to sleep at the same time.... +diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +index 401787a5b55384b9ab7755e822b3b881dc45ac45..e537a8df45c31efa80cb898cbef9c3a09fac3bf9 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java ++++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +@@ -351,6 +351,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setThundering(boolean thundering) { ++ // Paper start ++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); ++ } ++ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { ++ // Paper end + // CraftBukkit start + if (this.thundering == thundering) { + return; +@@ -358,7 +363,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); ++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper + Bukkit.getServer().getPluginManager().callEvent(thunder); + if (thunder.isCancelled()) { + return; +@@ -385,6 +390,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + @Override + public void setRaining(boolean raining) { ++ // Paper start ++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); ++ } ++ ++ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { ++ // Paper end + // CraftBukkit start + if (this.raining == raining) { + return; +@@ -392,7 +403,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { + + org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); + if (world != null) { +- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); ++ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper + Bukkit.getServer().getPluginManager().callEvent(weather); + if (weather.isCancelled()) { + return; +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9ca188959484041f53a078963cb79d68fd2a4f48..5fbc8fabf1c94798c21035049f6585e915da0536 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1194,7 +1194,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setStorm(boolean hasStorm) { +- world.levelData.setRaining(hasStorm); ++ world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper + this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } +@@ -1216,7 +1216,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setThundering(boolean thundering) { +- world.serverLevelData.setThundering(thundering); ++ world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper + this.setThunderDuration(0); // Reset weather duration (legacy behaviour) + this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) + } diff --git a/patches/server/0653-Attributes-API-for-item-defaults.patch b/patches/server/0653-Attributes-API-for-item-defaults.patch deleted file mode 100644 index 882921f7c0..0000000000 --- a/patches/server/0653-Attributes-API-for-item-defaults.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 8 May 2021 15:01:54 -0700 -Subject: [PATCH] Attributes API for item defaults - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 0ebcadd6daf244cd9b6c943ca0a2baaafb3eba50..6fd3bbc36cb6e270a10f778fe2764823f90cca9c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -545,6 +545,19 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftMagicNumbers.getItem(itemToBeRepaired.getType()).isValidRepairItem(CraftItemStack.asNMSCopy(itemToBeRepaired), CraftItemStack.asNMSCopy(repairMaterial)); - } - -+ @Override -+ public Multimap getItemAttributes(Material material, EquipmentSlot equipmentSlot) { -+ Item item = CraftMagicNumbers.getItem(material); -+ if (item == null) { -+ throw new IllegalArgumentException(material + " is not an item and therefore does not have attributes"); -+ } -+ ImmutableMultimap.Builder attributeMapBuilder = ImmutableMultimap.builder(); -+ item.getDefaultAttributeModifiers(CraftEquipmentSlot.getNMS(equipmentSlot)).forEach((attributeBase, attributeModifier) -> { -+ attributeMapBuilder.put(CraftAttributeMap.fromMinecraft(net.minecraft.core.Registry.ATTRIBUTE.getKey(attributeBase).toString()), CraftAttributeInstance.convert(attributeModifier, equipmentSlot)); -+ }); -+ return attributeMapBuilder.build(); -+ } -+ - @Override - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); diff --git a/patches/server/0654-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0654-Add-cause-to-Weather-ThunderChangeEvents.patch deleted file mode 100644 index ebde715e98..0000000000 --- a/patches/server/0654-Add-cause-to-Weather-ThunderChangeEvents.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 18:23:26 -0800 -Subject: [PATCH] Add cause to Weather/ThunderChangeEvents - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7f121ce977fd5779032450443c94634bc919009d..6e9f1f01227a94480043ba3120c77f1ae080ec02 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -498,8 +498,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setClearWeatherTime(clearDuration); - this.serverLevelData.setRainTime(rainDuration); - this.serverLevelData.setThunderTime(rainDuration); -- this.serverLevelData.setRaining(raining); -- this.serverLevelData.setThundering(thundering); -+ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper -+ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - } - - @Override -@@ -891,8 +891,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.serverLevelData.setThunderTime(j); - this.serverLevelData.setRainTime(k); - this.serverLevelData.setClearWeatherTime(i); -- this.serverLevelData.setThundering(flag1); -- this.serverLevelData.setRaining(flag2); -+ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper -+ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - } - - this.oThunderLevel = this.thunderLevel; -@@ -958,14 +958,14 @@ public class ServerLevel extends Level implements WorldGenLevel { - - private void resetWeatherCycle() { - // CraftBukkit start -- this.serverLevelData.setRaining(false); -+ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... - if (!this.serverLevelData.isRaining()) { - this.serverLevelData.setRainTime(0); - } - // CraftBukkit end -- this.serverLevelData.setThundering(false); -+ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night - // CraftBukkit start - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. - // Not that everyone ever manages to get the whole server to sleep at the same time.... -diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -index 401787a5b55384b9ab7755e822b3b881dc45ac45..e537a8df45c31efa80cb898cbef9c3a09fac3bf9 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -+++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java -@@ -351,6 +351,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setThundering(boolean thundering) { -+ // Paper start -+ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN); -+ } -+ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) { -+ // Paper end - // CraftBukkit start - if (this.thundering == thundering) { - return; -@@ -358,7 +363,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering); -+ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Bukkit.getServer().getPluginManager().callEvent(thunder); - if (thunder.isCancelled()) { - return; -@@ -385,6 +390,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - @Override - public void setRaining(boolean raining) { -+ // Paper start -+ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN); -+ } -+ -+ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) { -+ // Paper end - // CraftBukkit start - if (this.raining == raining) { - return; -@@ -392,7 +403,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { - - org.bukkit.World world = Bukkit.getWorld(this.getLevelName()); - if (world != null) { -- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining); -+ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Bukkit.getServer().getPluginManager().callEvent(weather); - if (weather.isCancelled()) { - return; -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 9ca188959484041f53a078963cb79d68fd2a4f48..5fbc8fabf1c94798c21035049f6585e915da0536 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1194,7 +1194,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setStorm(boolean hasStorm) { -- world.levelData.setRaining(hasStorm); -+ world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } -@@ -1216,7 +1216,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setThundering(boolean thundering) { -- world.serverLevelData.setThundering(thundering); -+ world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - this.setThunderDuration(0); // Reset weather duration (legacy behaviour) - this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) - } diff --git a/patches/server/0654-More-Lidded-Block-API.patch b/patches/server/0654-More-Lidded-Block-API.patch new file mode 100644 index 0000000000..135fd68698 --- /dev/null +++ b/patches/server/0654-More-Lidded-Block-API.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Sun, 23 May 2021 17:49:51 +0900 +Subject: [PATCH] More Lidded Block API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java +index 9d0c272b1d89a96b0b63603fa8e4649f11fb6c51..d5fdf4504a0ca76fb0483f4ae5861c93fb622b2d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java +@@ -58,4 +58,11 @@ public class CraftBarrel extends CraftLootable implements Bar + } + getTileEntity().openersCounter.opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().openersCounter.opened; ++ } ++ // Paper end - More Lidded Block API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +index 82b3f3b3aced73ce136b6b94fe212972ac6090ef..b4bc5cbb71007b4d1a27bb841ff787a95e9ecbdc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java +@@ -78,4 +78,11 @@ public class CraftChest extends CraftLootable implements Chest + } + getTileEntity().openersCounter.opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().openersCounter.opened; ++ } ++ // Paper end - More Lidded Block API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +index 6513acb46591b2903d1baf18c23ed1fc8c2a731f..b8ab67fd1820613520203f708f2f267587ace67b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java +@@ -36,4 +36,11 @@ public class CraftEnderChest extends CraftBlockEntityState implem + if (getTileEntity().opened && getWorldHandle() instanceof net.minecraft.world.level.Level) { + net.minecraft.world.level.Level world = getTileEntity().getLevel(); + world.blockEvent(getPosition(), getTileEntity().getBlockState().getBlock(), 1, 0); +- world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); ++ world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Paper - More Lidded Block API (Wrong sound) + } + getTileEntity().opened = false; + } ++ ++ // Paper start - More Lidded Block API ++ @Override ++ public boolean isOpen() { ++ return getTileEntity().opened; ++ } ++ // Paper end - More Lidded Block API + } diff --git a/patches/server/0655-Limit-item-frame-cursors-on-maps.patch b/patches/server/0655-Limit-item-frame-cursors-on-maps.patch new file mode 100644 index 0000000000..4e2cd3bfa2 --- /dev/null +++ b/patches/server/0655-Limit-item-frame-cursors-on-maps.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive +Date: Wed, 26 May 2021 15:09:33 -0700 +Subject: [PATCH] Limit item frame cursors on maps + + +diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 4acbcafc158cf11af51d9518ba5b83aaa75f52a1..67b88da702b780f79c0496cb17f1e6f1f8dd6c2b 100644 +--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -295,8 +295,12 @@ public class MapItemSavedData extends SavedData { + + MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); + ++ // Paper start ++ if (this.decorations.size() < player.level.paperConfig().maps.itemFrameCursorLimit) { + this.addDecoration(MapDecoration.Type.FRAME, player.level, "frame-" + entityitemframe.getId(), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); + this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); ++ } ++ // Paper end + } + + CompoundTag nbttagcompound = stack.getTag(); diff --git a/patches/server/0655-More-Lidded-Block-API.patch b/patches/server/0655-More-Lidded-Block-API.patch deleted file mode 100644 index 135fd68698..0000000000 --- a/patches/server/0655-More-Lidded-Block-API.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Sun, 23 May 2021 17:49:51 +0900 -Subject: [PATCH] More Lidded Block API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java -index 9d0c272b1d89a96b0b63603fa8e4649f11fb6c51..d5fdf4504a0ca76fb0483f4ae5861c93fb622b2d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java -@@ -58,4 +58,11 @@ public class CraftBarrel extends CraftLootable implements Bar - } - getTileEntity().openersCounter.opened = false; - } -+ -+ // Paper start - More Lidded Block API -+ @Override -+ public boolean isOpen() { -+ return getTileEntity().openersCounter.opened; -+ } -+ // Paper end - More Lidded Block API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java -index 82b3f3b3aced73ce136b6b94fe212972ac6090ef..b4bc5cbb71007b4d1a27bb841ff787a95e9ecbdc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java -@@ -78,4 +78,11 @@ public class CraftChest extends CraftLootable implements Chest - } - getTileEntity().openersCounter.opened = false; - } -+ -+ // Paper start - More Lidded Block API -+ @Override -+ public boolean isOpen() { -+ return getTileEntity().openersCounter.opened; -+ } -+ // Paper end - More Lidded Block API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java -index 6513acb46591b2903d1baf18c23ed1fc8c2a731f..b8ab67fd1820613520203f708f2f267587ace67b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java -@@ -36,4 +36,11 @@ public class CraftEnderChest extends CraftBlockEntityState implem - if (getTileEntity().opened && getWorldHandle() instanceof net.minecraft.world.level.Level) { - net.minecraft.world.level.Level world = getTileEntity().getLevel(); - world.blockEvent(getPosition(), getTileEntity().getBlockState().getBlock(), 1, 0); -- world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); -+ world.playSound(null, getPosition(), SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Paper - More Lidded Block API (Wrong sound) - } - getTileEntity().opened = false; - } -+ -+ // Paper start - More Lidded Block API -+ @Override -+ public boolean isOpen() { -+ return getTileEntity().opened; -+ } -+ // Paper end - More Lidded Block API - } diff --git a/patches/server/0656-Add-PlayerKickEvent-causes.patch b/patches/server/0656-Add-PlayerKickEvent-causes.patch new file mode 100644 index 0000000000..aadf1414fe --- /dev/null +++ b/patches/server/0656-Add-PlayerKickEvent-causes.patch @@ -0,0 +1,435 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 15 May 2021 20:30:45 -0700 +Subject: [PATCH] Add PlayerKickEvent causes + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 84e76fbe3eca77b112c9dff936e21cba1c83e5aa..98fe4165d291b47a39ce741884353c87dd0a4789 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2118,7 +2118,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop targets, Component reason) { + for(ServerPlayer serverPlayer : targets) { +- serverPlayer.connection.disconnect(reason); ++ serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause + source.sendSuccess(Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 3ef39ebeb76f43d521266402e170bd1af6af2348..55c5348e793fa560d7042cf90076a8f7a3b76547 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -369,7 +369,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger()) { + if (++this.aboveGroundTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -388,7 +388,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { + if (++this.aboveGroundVehicleTickCount > 80) { + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message ++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause + return; + } + } else { +@@ -410,7 +410,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + if (this.keepAlivePending) { + if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected + ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info +- this.disconnect(Component.translatable("disconnect.timeout", new Object[0])); ++ this.disconnect(Component.translatable("disconnect.timeout", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } + } else { + if (elapsedTime >= 15000L) { // 15 seconds +@@ -440,7 +440,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 +- this.disconnect(Component.translatable("multiplayer.disconnect.idling")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } + + this.chatPreviewThrottler.tick(); +@@ -464,16 +464,26 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + return this.server.isSingleplayerOwner(this.player.getGameProfile()); + } + ++ @io.papermc.paper.annotation.DoNotUse // Paper + public void disconnect(String s) { + // Paper start +- this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s)); ++ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); + } + ++ public void disconnect(String s, PlayerKickEvent.Cause cause) { ++ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), cause); ++ } ++ ++ @io.papermc.paper.annotation.DoNotUse // Paper + public void disconnect(final Component reason) { +- this.disconnect(PaperAdventure.asAdventure(reason)); ++ this.disconnect(PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { ++ this.disconnect(PaperAdventure.asAdventure(reason), cause); + } + +- public void disconnect(net.kyori.adventure.text.Component reason) { ++ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + // Paper end + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { +@@ -502,7 +512,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure + +- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage); // Paper - Adventure ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage, cause); // Paper - Adventure & kick event reason + + if (this.cserver.getServer().isRunning()) { + this.cserver.getPluginManager().callEvent(event); +@@ -572,7 +582,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + } else { + Entity entity = this.player.getRootVehicle(); + +@@ -770,7 +780,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (packet.getId() == this.awaitingTeleport) { + if (this.awaitingPositionFromClient == null) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + return; + } + +@@ -827,13 +837,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async + // CraftBukkit start + if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable +- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper start + String str = packet.getCommand(); int index = -1; + if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { +- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + // Paper end +@@ -986,7 +996,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // Paper start - validate pick item position + if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed +@@ -1173,7 +1183,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; + if (byteLength > 256 * 4) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + byteTotal += byteLength; +@@ -1196,14 +1206,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + if (byteTotal > byteAllowed) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); +- server.scheduleOnMain(() -> this.disconnect("Book too large!")); ++ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause + return; + } + } + // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!"); ++ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + this.lastBookTick = MinecraftServer.currentTick; +@@ -1327,7 +1337,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + public void handleMovePlayer(ServerboundMovePlayerPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + } else { + ServerLevel worldserver = this.player.getLevel(); + +@@ -1754,7 +1764,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.dropCount++; + if (this.dropCount >= 20) { + ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); +- this.disconnect("You dropped your items too quickly (Hacking?)"); ++ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause + return; + } + } +@@ -1963,7 +1973,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { + ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName()); +- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); ++ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause + } + // Paper start + PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()]; +@@ -2076,7 +2086,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); +- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit ++ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause + } + } + +@@ -2089,7 +2099,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // CraftBukkit end + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause + } else { + if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) { + // this.server.submit(() -> { // CraftBukkit - async chat +@@ -2117,7 +2127,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper + } else { + if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) { + this.server.submit(() -> { +@@ -2203,7 +2213,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { + if (!this.updateChatOrder(timestamp)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); +- this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause + return false; + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); +@@ -2465,7 +2475,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + if (!playerchatmessage.verify(chatsender)) { +- this.disconnect(Component.translatable("multiplayer.disconnect.unsigned_chat")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.unsigned_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.UNSIGNED_CHAT); // Paper - kick event cause + return false; + } + } +@@ -2493,7 +2503,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // this.chatSpamTickCount += 20; + if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { + // CraftBukkit end +- this.disconnect(Component.translatable("disconnect.spam")); ++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } + + } +@@ -2596,7 +2606,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + private void handleValidationFailure(Set reasons) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message from {}, reasons: {}", this.player.getName().getString(), reasons.stream().map(LastSeenMessagesValidator.ErrorCondition::message).collect(Collectors.joining(","))); +- this.disconnect(Component.translatable("multiplayer.disconnect.chat_validation_failed")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.chat_validation_failed"), org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event cause + } + + @Override +@@ -2737,7 +2747,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + if (i > 4096) { +- this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause + } + + } +@@ -2752,7 +2762,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // Spigot Start + if ( entity == this.player && !this.player.isSpectator() ) + { +- this.disconnect( "Cannot interact with self!" ); ++ this.disconnect( "Cannot interact with self!", org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - add cause + return; + } + // Spigot End +@@ -2850,7 +2860,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // CraftBukkit end + } else { +- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); ++ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause + ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); + } + } +@@ -3258,7 +3268,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // Paper start + if (!org.bukkit.Bukkit.isPrimaryThread()) { + if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { +- server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + return; + } + } +@@ -3461,7 +3471,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } else if (!this.isSingleplayerOwner()) { + // Paper start - This needs to be handled on the main thread for plugins + server.submit(() -> { +- this.disconnect(Component.translatable("disconnect.timeout")); ++ this.disconnect(Component.translatable("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + }); + // Paper end + } +@@ -3507,7 +3517,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); +- this.disconnect("Invalid payload REGISTER!"); ++ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (packet.identifier.equals(CUSTOM_UNREGISTER)) { + try { +@@ -3517,7 +3527,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); +- this.disconnect("Invalid payload UNREGISTER!"); ++ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { + try { +@@ -3535,7 +3545,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), packet.identifier.toString(), data); + } catch (Exception ex) { + ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); +- this.disconnect("Invalid custom payload!"); ++ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 977a23684b061d7390f70f8754a1d879d7d7075a..cd7fdf629ac1dd17e83445afdf352c33823cbd25 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -726,7 +726,7 @@ public abstract class PlayerList { + while (iterator.hasNext()) { + entityplayer = (ServerPlayer) iterator.next(); + this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved +- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); ++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause + } + + // Instead of kicking then returning, we need to store the kick reason +@@ -1357,8 +1357,8 @@ public abstract class PlayerList { + // Paper end + // CraftBukkit start - disconnect safely + for (ServerPlayer player : this.players) { +- if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper +- player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure ++ if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) ++ player.connection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) + } + // CraftBukkit end + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index ca8267966307ed5e0cb2fc5e4cf61868a77f50ed..a7a0b892b50302ac7af4588bb65206834134dba2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -516,7 +516,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot + if (this.getHandle().connection == null) return; + +- this.getHandle().connection.disconnect(message == null ? "" : message); ++ this.getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + } + + // Paper start +@@ -528,10 +528,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void kick(final net.kyori.adventure.text.Component message) { ++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); ++ } ++ ++ @Override ++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { + org.spigotmc.AsyncCatcher.catchOp("player kick"); + final ServerGamePacketListenerImpl connection = this.getHandle().connection; + if (connection != null) { +- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); ++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); + } + } + +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +index 92d97a5810a379b427a99b4c63fb9844d823a84f..160115bf8a153ff981ba308599d22c4c08026fb6 100644 +--- a/src/main/java/org/spigotmc/RestartCommand.java ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -74,7 +74,7 @@ public class RestartCommand extends Command + // Kick all players + for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) + { +- p.connection.disconnect(SpigotConfig.restartMessage); ++ p.connection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) + } + // Give the socket a chance to send the packets + try diff --git a/patches/server/0656-Limit-item-frame-cursors-on-maps.patch b/patches/server/0656-Limit-item-frame-cursors-on-maps.patch deleted file mode 100644 index 4e2cd3bfa2..0000000000 --- a/patches/server/0656-Limit-item-frame-cursors-on-maps.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Yive -Date: Wed, 26 May 2021 15:09:33 -0700 -Subject: [PATCH] Limit item frame cursors on maps - - -diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 4acbcafc158cf11af51d9518ba5b83aaa75f52a1..67b88da702b780f79c0496cb17f1e6f1f8dd6c2b 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -295,8 +295,12 @@ public class MapItemSavedData extends SavedData { - - MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId()); - -+ // Paper start -+ if (this.decorations.size() < player.level.paperConfig().maps.itemFrameCursorLimit) { - this.addDecoration(MapDecoration.Type.FRAME, player.level, "frame-" + entityitemframe.getId(), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null); - this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1); -+ } -+ // Paper end - } - - CompoundTag nbttagcompound = stack.getTag(); diff --git a/patches/server/0657-Add-PlayerKickEvent-causes.patch b/patches/server/0657-Add-PlayerKickEvent-causes.patch deleted file mode 100644 index 87c2e68e7d..0000000000 --- a/patches/server/0657-Add-PlayerKickEvent-causes.patch +++ /dev/null @@ -1,435 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 15 May 2021 20:30:45 -0700 -Subject: [PATCH] Add PlayerKickEvent causes - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 84e76fbe3eca77b112c9dff936e21cba1c83e5aa..98fe4165d291b47a39ce741884353c87dd0a4789 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2118,7 +2118,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop targets, Component reason) { - for(ServerPlayer serverPlayer : targets) { -- serverPlayer.connection.disconnect(reason); -+ serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause - source.sendSuccess(Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true); - } - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0b9365e60b36550e356cd2e43bad63951d2e315d..7fe9e406a865abb256e02a697c0412c856d4c987 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -369,7 +369,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger()) { - if (++this.aboveGroundTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -388,7 +388,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - if (this.clientVehicleIsFloating && this.player.getRootVehicle().getControllingPassenger() == this.player) { - if (++this.aboveGroundVehicleTickCount > 80) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); -- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message -+ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause - return; - } - } else { -@@ -410,7 +410,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - if (this.keepAlivePending) { - if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info -- this.disconnect(Component.translatable("disconnect.timeout", new Object[0])); -+ this.disconnect(Component.translatable("disconnect.timeout", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - } - } else { - if (elapsedTime >= 15000L) { // 15 seconds -@@ -440,7 +440,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 -- this.disconnect(Component.translatable("multiplayer.disconnect.idling")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - - this.chatPreviewThrottler.tick(); -@@ -464,16 +464,26 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - return this.server.isSingleplayerOwner(this.player.getGameProfile()); - } - -+ @io.papermc.paper.annotation.DoNotUse // Paper - public void disconnect(String s) { - // Paper start -- this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s)); -+ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); - } - -+ public void disconnect(String s, PlayerKickEvent.Cause cause) { -+ this.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s), cause); -+ } -+ -+ @io.papermc.paper.annotation.DoNotUse // Paper - public void disconnect(final Component reason) { -- this.disconnect(PaperAdventure.asAdventure(reason)); -+ this.disconnect(PaperAdventure.asAdventure(reason), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ -+ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { -+ this.disconnect(PaperAdventure.asAdventure(reason), cause); - } - -- public void disconnect(net.kyori.adventure.text.Component reason) { -+ public void disconnect(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { - // Paper end - // CraftBukkit start - fire PlayerKickEvent - if (this.processedDisconnect) { -@@ -502,7 +512,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure - -- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage); // Paper - Adventure -+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), reason, leaveMessage, cause); // Paper - Adventure & kick event reason - - if (this.cserver.getServer().isRunning()) { - this.cserver.getPluginManager().callEvent(event); -@@ -572,7 +582,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause - } else { - Entity entity = this.player.getRootVehicle(); - -@@ -770,7 +780,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (packet.getId() == this.awaitingTeleport) { - if (this.awaitingPositionFromClient == null) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - return; - } - -@@ -827,13 +837,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async - // CraftBukkit start - if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable -- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper -+ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause - return; - } - // Paper start - String str = packet.getCommand(); int index = -1; - if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { -- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper -+ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause - return; - } - // Paper end -@@ -986,7 +996,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // Paper start - validate pick item position - if (!(packet.getSlot() >= 0 && packet.getSlot() < this.player.getInventory().items.size())) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect("Invalid hotbar selection (Hacking?)"); -+ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.player.getInventory().pickSlot(packet.getSlot()); // Paper - Diff above if changed -@@ -1173,7 +1183,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; - if (byteLength > 256 * 4) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); -- server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause - return; - } - byteTotal += byteLength; -@@ -1196,14 +1206,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - if (byteTotal > byteAllowed) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); -- server.scheduleOnMain(() -> this.disconnect("Book too large!")); -+ server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause - return; - } - } - // Paper end - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -- this.disconnect("Book edited too quickly!"); -+ this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - this.lastBookTick = MinecraftServer.currentTick; -@@ -1327,7 +1337,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - public void handleMovePlayer(ServerboundMovePlayerPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { -- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - } else { - ServerLevel worldserver = this.player.getLevel(); - -@@ -1754,7 +1764,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.dropCount++; - if (this.dropCount >= 20) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); -- this.disconnect("You dropped your items too quickly (Hacking?)"); -+ this.disconnect("You dropped your items too quickly (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause - return; - } - } -@@ -1962,7 +1972,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { - ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName()); -- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); -+ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause - } - // Paper start - PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()]; -@@ -2075,7 +2085,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.player.resetLastActionTime(); - } else { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); -- this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit -+ this.disconnect("Invalid hotbar selection (Hacking?)", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause - } - } - -@@ -2088,7 +2098,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // CraftBukkit end - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause - } else { - if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) { - // this.server.submit(() -> { // CraftBukkit - async chat -@@ -2116,7 +2126,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - @Override - public void handleChatCommand(ServerboundChatCommandPacket packet) { - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { -- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - } else { - if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) { - this.server.submit(() -> { -@@ -2202,7 +2212,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { - if (!this.updateChatOrder(timestamp)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); -- this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause - return false; - } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales - this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); -@@ -2461,7 +2471,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - if (!playerchatmessage.verify(chatsender)) { -- this.disconnect(Component.translatable("multiplayer.disconnect.unsigned_chat")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.unsigned_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.UNSIGNED_CHAT); // Paper - kick event cause - return false; - } - } -@@ -2489,7 +2499,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // this.chatSpamTickCount += 20; - if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { - // CraftBukkit end -- this.disconnect(Component.translatable("disconnect.spam")); -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } - - } -@@ -2592,7 +2602,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - private void handleValidationFailure(Set reasons) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message from {}, reasons: {}", this.player.getName().getString(), reasons.stream().map(LastSeenMessagesValidator.ErrorCondition::message).collect(Collectors.joining(","))); -- this.disconnect(Component.translatable("multiplayer.disconnect.chat_validation_failed")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.chat_validation_failed"), org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event cause - } - - @Override -@@ -2733,7 +2743,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - if (i > 4096) { -- this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats")); -+ this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause - } - - } -@@ -2748,7 +2758,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // Spigot Start - if ( entity == this.player && !this.player.isSpectator() ) - { -- this.disconnect( "Cannot interact with self!" ); -+ this.disconnect( "Cannot interact with self!", org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - add cause - return; - } - // Spigot End -@@ -2846,7 +2856,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // CraftBukkit end - } else { -- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); -+ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause - ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); - } - } -@@ -3254,7 +3264,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // Paper start - if (!org.bukkit.Bukkit.isPrimaryThread()) { - if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -- server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]))); // Paper -+ server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause - return; - } - } -@@ -3457,7 +3467,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } else if (!this.isSingleplayerOwner()) { - // Paper start - This needs to be handled on the main thread for plugins - server.submit(() -> { -- this.disconnect(Component.translatable("disconnect.timeout")); -+ this.disconnect(Component.translatable("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause - }); - // Paper end - } -@@ -3503,7 +3513,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); -- this.disconnect("Invalid payload REGISTER!"); -+ this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else if (packet.identifier.equals(CUSTOM_UNREGISTER)) { - try { -@@ -3513,7 +3523,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); -- this.disconnect("Invalid payload UNREGISTER!"); -+ this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } else { - try { -@@ -3531,7 +3541,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), packet.identifier.toString(), data); - } catch (Exception ex) { - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); -- this.disconnect("Invalid custom payload!"); -+ this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } - } - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 977a23684b061d7390f70f8754a1d879d7d7075a..cd7fdf629ac1dd17e83445afdf352c33823cbd25 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -726,7 +726,7 @@ public abstract class PlayerList { - while (iterator.hasNext()) { - entityplayer = (ServerPlayer) iterator.next(); - this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved -- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login")); -+ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause - } - - // Instead of kicking then returning, we need to store the kick reason -@@ -1357,8 +1357,8 @@ public abstract class PlayerList { - // Paper end - // CraftBukkit start - disconnect safely - for (ServerPlayer player : this.players) { -- if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage); else // Paper -- player.connection.disconnect(this.server.server.shutdownMessage()); // CraftBukkit - add custom shutdown message // Paper - Adventure -+ if (isRestarting) player.connection.disconnect(org.spigotmc.SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here) -+ player.connection.disconnect(this.server.server.shutdownMessage(), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // CraftBukkit - add custom shutdown message // Paper - Adventure & KickEventCause (cause is never used here) - } - // CraftBukkit end - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index ca8267966307ed5e0cb2fc5e4cf61868a77f50ed..a7a0b892b50302ac7af4588bb65206834134dba2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -516,7 +516,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot - if (this.getHandle().connection == null) return; - -- this.getHandle().connection.disconnect(message == null ? "" : message); -+ this.getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause - } - - // Paper start -@@ -528,10 +528,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void kick(final net.kyori.adventure.text.Component message) { -+ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); -+ } -+ -+ @Override -+ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) { - org.spigotmc.AsyncCatcher.catchOp("player kick"); - final ServerGamePacketListenerImpl connection = this.getHandle().connection; - if (connection != null) { -- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message); -+ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); - } - } - -diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java -index 92d97a5810a379b427a99b4c63fb9844d823a84f..160115bf8a153ff981ba308599d22c4c08026fb6 100644 ---- a/src/main/java/org/spigotmc/RestartCommand.java -+++ b/src/main/java/org/spigotmc/RestartCommand.java -@@ -74,7 +74,7 @@ public class RestartCommand extends Command - // Kick all players - for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) ) - { -- p.connection.disconnect(SpigotConfig.restartMessage); -+ p.connection.disconnect(SpigotConfig.restartMessage, org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used)) - } - // Give the socket a chance to send the packets - try diff --git a/patches/server/0657-Add-PufferFishStateChangeEvent.patch b/patches/server/0657-Add-PufferFishStateChangeEvent.patch new file mode 100644 index 0000000000..e15126884a --- /dev/null +++ b/patches/server/0657-Add-PufferFishStateChangeEvent.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Mon, 10 May 2021 16:59:05 +0100 +Subject: [PATCH] Add PufferFishStateChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +index 9169e0c76b1801aad760691a6090199fdb7f6585..ce02552c1b3c62cf9f48425838a129a3ec40a049 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +@@ -95,25 +95,39 @@ public class Pufferfish extends AbstractFish { + public void tick() { + if (!this.level.isClientSide && this.isAlive() && this.isEffectiveAi()) { + if (this.inflateCounter > 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.getPuffState() == 0) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(2); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.inflateCounter; ++ } // Paper - Add PufferFishStateChangeEvent + } else if (this.getPuffState() != 0) { ++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent + if (this.deflateTimer > 60 && this.getPuffState() == 2) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(1); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { ++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent + this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); + this.setPuffState(0); ++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent + } + ++ if (increase) { // Paper - Add PufferFishStateChangeEvent + ++this.deflateTimer; ++ } // Paper - Add PufferFishStateChangeEvent + } + } + diff --git a/patches/server/0658-Add-PufferFishStateChangeEvent.patch b/patches/server/0658-Add-PufferFishStateChangeEvent.patch deleted file mode 100644 index e15126884a..0000000000 --- a/patches/server/0658-Add-PufferFishStateChangeEvent.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Mon, 10 May 2021 16:59:05 +0100 -Subject: [PATCH] Add PufferFishStateChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -index 9169e0c76b1801aad760691a6090199fdb7f6585..ce02552c1b3c62cf9f48425838a129a3ec40a049 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -@@ -95,25 +95,39 @@ public class Pufferfish extends AbstractFish { - public void tick() { - if (!this.level.isClientSide && this.isAlive() && this.isEffectiveAi()) { - if (this.inflateCounter > 0) { -+ boolean increase = true; // Paper - Add PufferFishStateChangeEvent - if (this.getPuffState() == 0) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); - this.setPuffState(1); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } else if (this.inflateCounter > 40 && this.getPuffState() == 1) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.playSound(SoundEvents.PUFFER_FISH_BLOW_UP, this.getSoundVolume(), this.getVoicePitch()); - this.setPuffState(2); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } - -+ if (increase) { // Paper - Add PufferFishStateChangeEvent - ++this.inflateCounter; -+ } // Paper - Add PufferFishStateChangeEvent - } else if (this.getPuffState() != 0) { -+ boolean increase = true; // Paper - Add PufferFishStateChangeEvent - if (this.deflateTimer > 60 && this.getPuffState() == 2) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); - this.setPuffState(1); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } else if (this.deflateTimer > 100 && this.getPuffState() == 1) { -+ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent - this.playSound(SoundEvents.PUFFER_FISH_BLOW_OUT, this.getSoundVolume(), this.getVoicePitch()); - this.setPuffState(0); -+ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent - } - -+ if (increase) { // Paper - Add PufferFishStateChangeEvent - ++this.deflateTimer; -+ } // Paper - Add PufferFishStateChangeEvent - } - } - diff --git a/patches/server/0658-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0658-Fix-PlayerBucketEmptyEvent-result-itemstack.patch new file mode 100644 index 0000000000..0ebe5f6ad9 --- /dev/null +++ b/patches/server/0658-Fix-PlayerBucketEmptyEvent-result-itemstack.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 22:16:37 -0700 +Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack + +Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560 + +diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java +index 7c6c928da4e0f3ac54fa9b9ddce7b0e0bf3cce4b..73f1211470d9626c82c8345037da19aed9db3f23 100644 +--- a/src/main/java/net/minecraft/world/item/BucketItem.java ++++ b/src/main/java/net/minecraft/world/item/BucketItem.java +@@ -40,6 +40,8 @@ import org.bukkit.event.player.PlayerBucketFillEvent; + + public class BucketItem extends Item implements DispensibleContainerItem { + ++ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper ++ + public final Fluid content; + + public BucketItem(Fluid fluid, Item.Properties settings) { +@@ -121,6 +123,13 @@ public class BucketItem extends Item implements DispensibleContainerItem { + } + + public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) { ++ // Paper start ++ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { ++ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; ++ itemLeftInHandAfterPlayerBucketEmptyEvent = null; ++ return itemInHand; ++ } ++ // Paper end + return !player.getAbilities().instabuild ? new ItemStack(Items.BUCKET) : stack; + } + +@@ -153,6 +162,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + return false; + } ++ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - fix empty event result itemstack + } + // CraftBukkit end + if (!flag1) { diff --git a/patches/server/0659-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0659-Fix-PlayerBucketEmptyEvent-result-itemstack.patch deleted file mode 100644 index 0ebe5f6ad9..0000000000 --- a/patches/server/0659-Fix-PlayerBucketEmptyEvent-result-itemstack.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 May 2021 22:16:37 -0700 -Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack - -Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560 - -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 7c6c928da4e0f3ac54fa9b9ddce7b0e0bf3cce4b..73f1211470d9626c82c8345037da19aed9db3f23 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -40,6 +40,8 @@ import org.bukkit.event.player.PlayerBucketFillEvent; - - public class BucketItem extends Item implements DispensibleContainerItem { - -+ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper -+ - public final Fluid content; - - public BucketItem(Fluid fluid, Item.Properties settings) { -@@ -121,6 +123,13 @@ public class BucketItem extends Item implements DispensibleContainerItem { - } - - public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) { -+ // Paper start -+ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) { -+ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent; -+ itemLeftInHandAfterPlayerBucketEmptyEvent = null; -+ return itemInHand; -+ } -+ // Paper end - return !player.getAbilities().instabuild ? new ItemStack(Items.BUCKET) : stack; - } - -@@ -153,6 +162,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - return false; - } -+ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - fix empty event result itemstack - } - // CraftBukkit end - if (!flag1) { diff --git a/patches/server/0659-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0659-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch new file mode 100644 index 0000000000..af97198c95 --- /dev/null +++ b/patches/server/0659-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 29 May 2020 20:29:02 -0400 +Subject: [PATCH] Synchronize PalettedContainer instead of + ThreadingDetector/Semaphore + +Mojang has flaws in their logic about chunks being concurrently +wrote to. So we constantly see crashes around multiple threads writing. + +Additionally, java has optimized synchronization so well that its +in many times faster than trying to manage read write locks for low +contention situations. + +And this is extremely a low contention situation. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index b688d239ff11b315f60cd980d8f6780b982a865b..d93118b7a602ceb6ef11ddabbce1d13fb8029a44 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -32,14 +32,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values + private volatile PalettedContainer.Data data; + private final PalettedContainer.Strategy strategy; +- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); ++ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + + public void acquire() { +- this.threadingDetector.checkAndLock(); ++ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization + } + + public void release() { +- this.threadingDetector.checkAndUnlock(); ++ // this.threadingDetector.checkAndUnlock(); // Paper - disable this + } + + // Paper start - Anti-Xray - Add preset values +@@ -129,7 +129,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public int onResize(int newBits, T object) { ++ public synchronized int onResize(int newBits, T object) { // Paper - synchronize + PalettedContainer.Data data = this.data; + + // Paper start - Anti-Xray - Add preset values +@@ -176,7 +176,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.getAndSet(this.strategy.getIndex(x, y, z), value); + } + +- private T getAndSet(int index, T value) { ++ private synchronized T getAndSet(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + int j = this.data.storage.getAndSet(index, i); + return this.data.palette.valueFor(j); +@@ -193,7 +193,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + + } + +- private void set(int index, T value) { ++ private synchronized void set(int index, T value) { // Paper - synchronize + int i = this.data.palette.idFor(value); + this.data.storage.set(index, i); + } +@@ -218,7 +218,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + }); + } + +- public void read(FriendlyByteBuf buf) { ++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize + this.acquire(); + + try { +@@ -238,7 +238,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + @Override + @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } + @Override +- public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { ++ public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { // Paper - synchronize + this.acquire(); + + try { +@@ -296,7 +296,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + + @Override +- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { ++ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize + this.acquire(); + + PalettedContainerRO.PackedData var12; diff --git a/patches/server/0660-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0660-Add-option-to-fix-items-merging-through-walls.patch new file mode 100644 index 0000000000..0c6f1b92fc --- /dev/null +++ b/patches/server/0660-Add-option-to-fix-items-merging-through-walls.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: GioSDA +Date: Wed, 10 Mar 2021 10:06:45 -0800 +Subject: [PATCH] Add option to fix items merging through walls + + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 7e293167e73238f42fc213ee29d89aa775cf9e60..30c417c3169c1df43662fd77ac6816db64a42802 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -245,6 +245,14 @@ public class ItemEntity extends Entity { + ItemEntity entityitem = (ItemEntity) iterator.next(); + + if (entityitem.isMergable()) { ++ // Paper Start - Fix items merging through walls ++ if (this.level.paperConfig().fixes.fixItemsMergingThroughWalls) { ++ net.minecraft.world.level.ClipContext rayTrace = new net.minecraft.world.level.ClipContext(this.position(), entityitem.position(), ++ net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, this); ++ net.minecraft.world.phys.BlockHitResult rayTraceResult = level.clip(rayTrace); ++ if (rayTraceResult.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) continue; ++ } ++ // Paper End + this.tryToMerge(entityitem); + if (this.isRemoved()) { + break; diff --git a/patches/server/0660-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0660-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch deleted file mode 100644 index af97198c95..0000000000 --- a/patches/server/0660-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Fri, 29 May 2020 20:29:02 -0400 -Subject: [PATCH] Synchronize PalettedContainer instead of - ThreadingDetector/Semaphore - -Mojang has flaws in their logic about chunks being concurrently -wrote to. So we constantly see crashes around multiple threads writing. - -Additionally, java has optimized synchronization so well that its -in many times faster than trying to manage read write locks for low -contention situations. - -And this is extremely a low contention situation. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index b688d239ff11b315f60cd980d8f6780b982a865b..d93118b7a602ceb6ef11ddabbce1d13fb8029a44 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -32,14 +32,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values - private volatile PalettedContainer.Data data; - private final PalettedContainer.Strategy strategy; -- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); -+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused - - public void acquire() { -- this.threadingDetector.checkAndLock(); -+ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization - } - - public void release() { -- this.threadingDetector.checkAndUnlock(); -+ // this.threadingDetector.checkAndUnlock(); // Paper - disable this - } - - // Paper start - Anti-Xray - Add preset values -@@ -129,7 +129,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public int onResize(int newBits, T object) { -+ public synchronized int onResize(int newBits, T object) { // Paper - synchronize - PalettedContainer.Data data = this.data; - - // Paper start - Anti-Xray - Add preset values -@@ -176,7 +176,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.getAndSet(this.strategy.getIndex(x, y, z), value); - } - -- private T getAndSet(int index, T value) { -+ private synchronized T getAndSet(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - int j = this.data.storage.getAndSet(index, i); - return this.data.palette.valueFor(j); -@@ -193,7 +193,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - - } - -- private void set(int index, T value) { -+ private synchronized void set(int index, T value) { // Paper - synchronize - int i = this.data.palette.idFor(value); - this.data.storage.set(index, i); - } -@@ -218,7 +218,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - }); - } - -- public void read(FriendlyByteBuf buf) { -+ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize - this.acquire(); - - try { -@@ -238,7 +238,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - @Override - @Deprecated @io.papermc.paper.annotation.DoNotUse public void write(FriendlyByteBuf buf) { this.write(buf, null, 0); } - @Override -- public void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { -+ public synchronized void write(FriendlyByteBuf buf, @Nullable com.destroystokyo.paper.antixray.ChunkPacketInfo chunkPacketInfo, int bottomBlockY) { // Paper - synchronize - this.acquire(); - - try { -@@ -296,7 +296,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - - @Override -- public PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { -+ public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize - this.acquire(); - - PalettedContainerRO.PackedData var12; diff --git a/patches/server/0661-Add-BellRevealRaiderEvent.patch b/patches/server/0661-Add-BellRevealRaiderEvent.patch new file mode 100644 index 0000000000..587e0f0400 --- /dev/null +++ b/patches/server/0661-Add-BellRevealRaiderEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 26 May 2021 17:09:07 -0400 +Subject: [PATCH] Add BellRevealRaiderEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +index feaad48e9bbc1e658324ef9e1e7e73aca0b3bf48..b9d2c38e80924f52dcf76ec1042d2d746e77ffc6 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java +@@ -138,7 +138,7 @@ public class BellBlockEntity extends BlockEntity { + private static void makeRaidersGlow(Level world, BlockPos pos, List hearingEntities) { + hearingEntities.stream().filter((entity) -> { + return isRaiderWithinRange(pos, entity); +- }).forEach(BellBlockEntity::glow); ++ }).forEach(entity -> glow(entity, pos)); // Paper - pass BlockPos + } + + private static void showBellParticles(Level world, BlockPos pos, List hearingEntities) { +@@ -170,7 +170,11 @@ public class BellBlockEntity extends BlockEntity { + return entity.isAlive() && !entity.isRemoved() && pos.closerToCenterThan(entity.position(), 48.0D) && entity.getType().is(EntityTypeTags.RAIDERS); + } + +- private static void glow(LivingEntity entity) { ++ // Paper start ++ private static void glow(LivingEntity entity) { glow(entity, null); } ++ private static void glow(LivingEntity entity, @javax.annotation.Nullable BlockPos pos) { ++ if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(entity.level.getWorld().getBlockAt(net.minecraft.server.MCUtil.toLocation(entity.level, pos)), entity.getBukkitEntity()).callEvent()) return; ++ // Paper end + entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60)); + } + diff --git a/patches/server/0661-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0661-Add-option-to-fix-items-merging-through-walls.patch deleted file mode 100644 index 0c6f1b92fc..0000000000 --- a/patches/server/0661-Add-option-to-fix-items-merging-through-walls.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: GioSDA -Date: Wed, 10 Mar 2021 10:06:45 -0800 -Subject: [PATCH] Add option to fix items merging through walls - - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 7e293167e73238f42fc213ee29d89aa775cf9e60..30c417c3169c1df43662fd77ac6816db64a42802 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -245,6 +245,14 @@ public class ItemEntity extends Entity { - ItemEntity entityitem = (ItemEntity) iterator.next(); - - if (entityitem.isMergable()) { -+ // Paper Start - Fix items merging through walls -+ if (this.level.paperConfig().fixes.fixItemsMergingThroughWalls) { -+ net.minecraft.world.level.ClipContext rayTrace = new net.minecraft.world.level.ClipContext(this.position(), entityitem.position(), -+ net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, this); -+ net.minecraft.world.phys.BlockHitResult rayTraceResult = level.clip(rayTrace); -+ if (rayTraceResult.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) continue; -+ } -+ // Paper End - this.tryToMerge(entityitem); - if (this.isRemoved()) { - break; diff --git a/patches/server/0662-Add-BellRevealRaiderEvent.patch b/patches/server/0662-Add-BellRevealRaiderEvent.patch deleted file mode 100644 index 587e0f0400..0000000000 --- a/patches/server/0662-Add-BellRevealRaiderEvent.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 26 May 2021 17:09:07 -0400 -Subject: [PATCH] Add BellRevealRaiderEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -index feaad48e9bbc1e658324ef9e1e7e73aca0b3bf48..b9d2c38e80924f52dcf76ec1042d2d746e77ffc6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java -@@ -138,7 +138,7 @@ public class BellBlockEntity extends BlockEntity { - private static void makeRaidersGlow(Level world, BlockPos pos, List hearingEntities) { - hearingEntities.stream().filter((entity) -> { - return isRaiderWithinRange(pos, entity); -- }).forEach(BellBlockEntity::glow); -+ }).forEach(entity -> glow(entity, pos)); // Paper - pass BlockPos - } - - private static void showBellParticles(Level world, BlockPos pos, List hearingEntities) { -@@ -170,7 +170,11 @@ public class BellBlockEntity extends BlockEntity { - return entity.isAlive() && !entity.isRemoved() && pos.closerToCenterThan(entity.position(), 48.0D) && entity.getType().is(EntityTypeTags.RAIDERS); - } - -- private static void glow(LivingEntity entity) { -+ // Paper start -+ private static void glow(LivingEntity entity) { glow(entity, null); } -+ private static void glow(LivingEntity entity, @javax.annotation.Nullable BlockPos pos) { -+ if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(entity.level.getWorld().getBlockAt(net.minecraft.server.MCUtil.toLocation(entity.level, pos)), entity.getBukkitEntity()).callEvent()) return; -+ // Paper end - entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60)); - } - diff --git a/patches/server/0662-Fix-invulnerable-end-crystals.patch b/patches/server/0662-Fix-invulnerable-end-crystals.patch new file mode 100644 index 0000000000..e5ce326afa --- /dev/null +++ b/patches/server/0662-Fix-invulnerable-end-crystals.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Max Lee +Date: Thu, 27 May 2021 14:52:30 -0700 +Subject: [PATCH] Fix invulnerable end crystals + +MC-108513 + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +index 78b0456a3f9e3f66d467386c3e5f68d07adf1977..6a08ab40e60b80f8f5c4d3f02da121be9da05111 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +@@ -30,6 +30,7 @@ public class EndCrystal extends Entity { + private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); + private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); + public int time; ++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + + public EndCrystal(EntityType type, Level world) { + super(type, world); +@@ -66,6 +67,17 @@ public class EndCrystal extends Entity { + } + // CraftBukkit end + } ++ // Paper start - Fix invulnerable end crystals ++ if (this.level.paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { ++ if (!java.util.Objects.equals(((ServerLevel) this.level).uuid, this.getOriginWorld()) ++ || ((ServerLevel) this.level).dragonFight() == null ++ || ((ServerLevel) this.level).dragonFight().respawnStage == null ++ || ((ServerLevel) this.level).dragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { ++ this.setInvulnerable(false); ++ this.setBeamTarget(null); ++ } ++ } ++ // Paper end + } + + } +@@ -77,6 +89,7 @@ public class EndCrystal extends Entity { + } + + nbt.putBoolean("ShowBottom", this.showsBottom()); ++ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals + } + + @Override +@@ -88,6 +101,7 @@ public class EndCrystal extends Entity { + if (nbt.contains("ShowBottom", 1)) { + this.setShowBottom(nbt.getBoolean("ShowBottom")); + } ++ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals + + } + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +index 8e51c2bc6dc6483b2c670348daf793ca4d59e5b9..94209f8c02656c113b537093cbbfaba1b9871045 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java +@@ -99,6 +99,7 @@ public class SpikeFeature extends Feature { + endCrystal.setBeamTarget(config.getCrystalBeamTarget()); + endCrystal.setInvulnerable(config.isCrystalInvulnerable()); + endCrystal.moveTo((double)spike.getCenterX() + 0.5D, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5D, random.nextFloat() * 360.0F, 0.0F); ++ endCrystal.generatedByDragonFight = true; // Paper + world.addFreshEntity(endCrystal); + this.setBlock(world, new BlockPos(spike.getCenterX(), spike.getHeight(), spike.getCenterZ()), Blocks.BEDROCK.defaultBlockState()); + } diff --git a/patches/server/0663-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0663-Add-ElderGuardianAppearanceEvent.patch new file mode 100644 index 0000000000..13c35cf8b6 --- /dev/null +++ b/patches/server/0663-Add-ElderGuardianAppearanceEvent.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 19 Mar 2021 23:39:09 -0400 +Subject: [PATCH] Add ElderGuardianAppearanceEvent + + +diff --git a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java +index f6c22fbad828afa29b9374b13d7bc8ac8cac7891..aefc65a81c91dac715df6d9a4eaaacd94f918cb5 100644 +--- a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java ++++ b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java +@@ -53,10 +53,23 @@ public final class MobEffectUtil { + } + + public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { ++ // Paper start ++ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null); ++ } ++ ++ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate playerPredicate) { ++ // Paper end + // CraftBukkit end + MobEffect mobeffectlist = mobeffect.getEffect(); + List list = worldserver.getPlayers((entityplayer) -> { +- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(mobeffectlist).getDuration() < i); ++ // Paper start ++ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(mobeffectlist).getDuration() < i); ++ if (condition) { ++ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true ++ } else { ++ return false; ++ } ++ // Paper ned + }); + + list.forEach((entityplayer) -> { +diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +index 0b6c6740e1411a558d224589b3786f3ba8e0a1bc..d02286d553c600fe7e75f48e278e380d21c5b868 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +@@ -67,7 +67,7 @@ public class ElderGuardian extends Guardian { + super.customServerAiStep(); + if ((this.tickCount + this.getId()) % 1200 == 0) { + MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2); +- List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit ++ List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent(getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper + + list.forEach((entityplayer) -> { + entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); diff --git a/patches/server/0663-Fix-invulnerable-end-crystals.patch b/patches/server/0663-Fix-invulnerable-end-crystals.patch deleted file mode 100644 index e5ce326afa..0000000000 --- a/patches/server/0663-Fix-invulnerable-end-crystals.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Max Lee -Date: Thu, 27 May 2021 14:52:30 -0700 -Subject: [PATCH] Fix invulnerable end crystals - -MC-108513 - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -index 78b0456a3f9e3f66d467386c3e5f68d07adf1977..6a08ab40e60b80f8f5c4d3f02da121be9da05111 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java -@@ -30,6 +30,7 @@ public class EndCrystal extends Entity { - private static final EntityDataAccessor> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS); - private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); - public int time; -+ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals - - public EndCrystal(EntityType type, Level world) { - super(type, world); -@@ -66,6 +67,17 @@ public class EndCrystal extends Entity { - } - // CraftBukkit end - } -+ // Paper start - Fix invulnerable end crystals -+ if (this.level.paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) { -+ if (!java.util.Objects.equals(((ServerLevel) this.level).uuid, this.getOriginWorld()) -+ || ((ServerLevel) this.level).dragonFight() == null -+ || ((ServerLevel) this.level).dragonFight().respawnStage == null -+ || ((ServerLevel) this.level).dragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) { -+ this.setInvulnerable(false); -+ this.setBeamTarget(null); -+ } -+ } -+ // Paper end - } - - } -@@ -77,6 +89,7 @@ public class EndCrystal extends Entity { - } - - nbt.putBoolean("ShowBottom", this.showsBottom()); -+ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals - } - - @Override -@@ -88,6 +101,7 @@ public class EndCrystal extends Entity { - if (nbt.contains("ShowBottom", 1)) { - this.setShowBottom(nbt.getBoolean("ShowBottom")); - } -+ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals - - } - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -index 8e51c2bc6dc6483b2c670348daf793ca4d59e5b9..94209f8c02656c113b537093cbbfaba1b9871045 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java -@@ -99,6 +99,7 @@ public class SpikeFeature extends Feature { - endCrystal.setBeamTarget(config.getCrystalBeamTarget()); - endCrystal.setInvulnerable(config.isCrystalInvulnerable()); - endCrystal.moveTo((double)spike.getCenterX() + 0.5D, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5D, random.nextFloat() * 360.0F, 0.0F); -+ endCrystal.generatedByDragonFight = true; // Paper - world.addFreshEntity(endCrystal); - this.setBlock(world, new BlockPos(spike.getCenterX(), spike.getHeight(), spike.getCenterZ()), Blocks.BEDROCK.defaultBlockState()); - } diff --git a/patches/server/0664-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0664-Add-ElderGuardianAppearanceEvent.patch deleted file mode 100644 index 13c35cf8b6..0000000000 --- a/patches/server/0664-Add-ElderGuardianAppearanceEvent.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 19 Mar 2021 23:39:09 -0400 -Subject: [PATCH] Add ElderGuardianAppearanceEvent - - -diff --git a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -index f6c22fbad828afa29b9374b13d7bc8ac8cac7891..aefc65a81c91dac715df6d9a4eaaacd94f918cb5 100644 ---- a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -+++ b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java -@@ -53,10 +53,23 @@ public final class MobEffectUtil { - } - - public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) { -+ // Paper start -+ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null); -+ } -+ -+ public static List addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate playerPredicate) { -+ // Paper end - // CraftBukkit end - MobEffect mobeffectlist = mobeffect.getEffect(); - List list = worldserver.getPlayers((entityplayer) -> { -- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(mobeffectlist).getDuration() < i); -+ // Paper start -+ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(mobeffectlist).getDuration() < i); -+ if (condition) { -+ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true -+ } else { -+ return false; -+ } -+ // Paper ned - }); - - list.forEach((entityplayer) -> { -diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -index 0b6c6740e1411a558d224589b3786f3ba8e0a1bc..d02286d553c600fe7e75f48e278e380d21c5b868 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -@@ -67,7 +67,7 @@ public class ElderGuardian extends Guardian { - super.customServerAiStep(); - if ((this.tickCount + this.getId()) % 1200 == 0) { - MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2); -- List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit -+ List list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent(getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - - list.forEach((entityplayer) -> { - entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F)); diff --git a/patches/server/0664-Fix-dangerous-end-portal-logic.patch b/patches/server/0664-Fix-dangerous-end-portal-logic.patch new file mode 100644 index 0000000000..bb45e6156c --- /dev/null +++ b/patches/server/0664-Fix-dangerous-end-portal-logic.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 4 Jun 2021 17:06:52 -0400 +Subject: [PATCH] Fix dangerous end portal logic + +End portals could teleport entities during move calls. Stupid +logic given the caller will never expect that kind of thing, +and will result in all kinds of dupes. + +Move the tick logic into the post tick, where portaling was +designed to happen in the first place. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 525e712c5715f1fa323fae94d4158c4e66068fe3..0b524fb57af63ad3b9e11a30e7b2d0fd219664ad 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -459,6 +459,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); + } + // Paper end - optimise entity tracking ++ // Paper start - make end portalling safe ++ public BlockPos portalBlock; ++ public ServerLevel portalWorld; ++ public void tickEndPortal() { ++ BlockPos pos = this.portalBlock; ++ ServerLevel world = this.portalWorld; ++ this.portalBlock = null; ++ this.portalWorld = null; ++ ++ if (pos == null || world == null || world != this.level) { ++ return; ++ } ++ ++ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.isRemoved() || !this.valid || !this.isAlive()) { ++ return; ++ } ++ ++ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends ++ ServerLevel worldserver = world.getServer().getLevel(resourcekey); ++ ++ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); ++ event.callEvent(); ++ ++ if (this instanceof ServerPlayer) { ++ ((ServerPlayer)this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); ++ return; ++ } ++ this.teleportTo(worldserver, null); ++ } ++ // Paper end - make end portalling safe + + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); +@@ -2635,6 +2665,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + this.processPortalCooldown(); ++ this.tickEndPortal(); // Paper - make end portalling safe + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +index 150c16da7caa655cfc2c371d3336a8d7345438c6..15c5cccfe02c924c02f605eb47dd0b420b189891 100644 +--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +@@ -53,16 +53,10 @@ public class EndPortalBlock extends BaseEntityBlock { + // return; // CraftBukkit - always fire event in case plugins wish to change it + } + +- // CraftBukkit start - Entity in portal +- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); +- world.getCraftServer().getPluginManager().callEvent(event); +- +- if (entity instanceof ServerPlayer) { +- ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); +- return; +- } +- // CraftBukkit end +- entity.changeDimension(worldserver); ++ // Paper start - move all of this logic into portal tick ++ entity.portalWorld = ((ServerLevel)world); ++ entity.portalBlock = pos.immutable(); ++ // Paper end - move all of this logic into portal tick + } + + } diff --git a/patches/server/0665-Fix-dangerous-end-portal-logic.patch b/patches/server/0665-Fix-dangerous-end-portal-logic.patch deleted file mode 100644 index f8757be01f..0000000000 --- a/patches/server/0665-Fix-dangerous-end-portal-logic.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 4 Jun 2021 17:06:52 -0400 -Subject: [PATCH] Fix dangerous end portal logic - -End portals could teleport entities during move calls. Stupid -logic given the caller will never expect that kind of thing, -and will result in all kinds of dupes. - -Move the tick logic into the post tick, where portaling was -designed to happen in the first place. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bfac12f0e89c4c7d48321ea608363518742304af..4888873a5efa026a1082c9f216eecc950b6f2471 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -459,6 +459,36 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); - } - // Paper end - optimise entity tracking -+ // Paper start - make end portalling safe -+ public BlockPos portalBlock; -+ public ServerLevel portalWorld; -+ public void tickEndPortal() { -+ BlockPos pos = this.portalBlock; -+ ServerLevel world = this.portalWorld; -+ this.portalBlock = null; -+ this.portalWorld = null; -+ -+ if (pos == null || world == null || world != this.level) { -+ return; -+ } -+ -+ if (this.isPassenger() || this.isVehicle() || !this.canChangeDimensions() || this.isRemoved() || !this.valid || !this.isAlive()) { -+ return; -+ } -+ -+ ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends -+ ServerLevel worldserver = world.getServer().getLevel(resourcekey); -+ -+ org.bukkit.event.entity.EntityPortalEnterEvent event = new org.bukkit.event.entity.EntityPortalEnterEvent(this.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ event.callEvent(); -+ -+ if (this instanceof ServerPlayer) { -+ ((ServerPlayer)this).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -+ return; -+ } -+ this.teleportTo(worldserver, null); -+ } -+ // Paper end - make end portalling safe - - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -@@ -2621,6 +2651,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - this.processPortalCooldown(); -+ this.tickEndPortal(); // Paper - make end portalling safe - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 150c16da7caa655cfc2c371d3336a8d7345438c6..15c5cccfe02c924c02f605eb47dd0b420b189891 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -53,16 +53,10 @@ public class EndPortalBlock extends BaseEntityBlock { - // return; // CraftBukkit - always fire event in case plugins wish to change it - } - -- // CraftBukkit start - Entity in portal -- EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -- world.getCraftServer().getPluginManager().callEvent(event); -- -- if (entity instanceof ServerPlayer) { -- ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -- return; -- } -- // CraftBukkit end -- entity.changeDimension(worldserver); -+ // Paper start - move all of this logic into portal tick -+ entity.portalWorld = ((ServerLevel)world); -+ entity.portalBlock = pos.immutable(); -+ // Paper end - move all of this logic into portal tick - } - - } diff --git a/patches/server/0665-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0665-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch new file mode 100644 index 0000000000..c46f8fbb65 --- /dev/null +++ b/patches/server/0665-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch @@ -0,0 +1,57 @@ +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/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +index 08e3a3c250056227c518a15d5df38b346abae45a..7c92decbf0f4a10cc31f821a249749009d8b1485 100644 +--- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -59,11 +59,43 @@ public class MobSpawnSettings { + } + + public static class Builder { +- private final Map> spawners = Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((mobCategory) -> { ++ // 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(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { ++ biomes.add(BiomeSettingsMobs); ++ return super.add(BiomeSettingsMobs); ++ } ++ ++ @Override ++ public MobSpawnSettings.SpawnerData remove(int index) { ++ MobSpawnSettings.SpawnerData 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> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((mobCategory) -> { + return mobCategory; + }, (mobCategory) -> { +- return Lists.newArrayList(); ++ return new MobList(); // Use MobList instead of ArrayList + })); ++ // Paper end + private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); + private float creatureGenerationProbability = 0.1F; + diff --git a/patches/server/0666-Make-item-validations-configurable.patch b/patches/server/0666-Make-item-validations-configurable.patch new file mode 100644 index 0000000000..d079cc7ed2 --- /dev/null +++ b/patches/server/0666-Make-item-validations-configurable.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 4 Jun 2021 12:12:35 -0700 +Subject: [PATCH] Make item validations configurable + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +index e8413ad360e9b6c4eef13edf9dd0095e7e64bce2..a5d7fae348b0b262a0a8a5e8e76f1bc75ca52a16 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java +@@ -88,11 +88,11 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + super(tag); + + if (tag.contains(BOOK_TITLE.NBT)) { +- this.title = limit( tag.getString(BOOK_TITLE.NBT), 8192 ); // Spigot ++ this.title = limit( tag.getString(BOOK_TITLE.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.title); // Spigot // Paper - make configurable + } + + if (tag.contains(BOOK_AUTHOR.NBT)) { +- this.author = limit( tag.getString(BOOK_AUTHOR.NBT), 8192 ); // Spigot ++ this.author = limit( tag.getString(BOOK_AUTHOR.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.author ); // Spigot // Paper - make configurable + } + + if (tag.contains(RESOLVED.NBT)) { +@@ -120,7 +120,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { + } else { + page = this.validatePage(page); + } +- this.pages.add( limit( page, 16384 ) ); // Spigot ++ this.pages.add( limit( page, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.page ) ); // Spigot // Paper - make configurable + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index c475ddea1c995df1dfcaf4f491f341761a5f8802..a8294bf057e03c5d866f6da31e6cdfa9edd3f146 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -357,7 +357,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + CompoundTag display = tag.getCompound(DISPLAY.NBT); + + if (display.contains(NAME.NBT)) { +- this.displayName = limit( display.getString(NAME.NBT), 8192 ); // Spigot ++ this.displayName = limit( display.getString(NAME.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.displayName ); // Spigot // Paper - make configurable + } + + if (display.contains(LOCNAME.NBT)) { +@@ -368,7 +368,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + ListTag list = display.getList(LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); + this.lore = new ArrayList(list.size()); + for (int index = 0; index < list.size(); index++) { +- String line = limit( list.getString(index), 8192 ); // Spigot ++ String line = limit( list.getString(index), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.loreLine ); // Spigot // Paper - make configurable + this.lore.add(line); + } + } diff --git a/patches/server/0666-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0666-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch deleted file mode 100644 index c46f8fbb65..0000000000 --- a/patches/server/0666-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch +++ /dev/null @@ -1,57 +0,0 @@ -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/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -index 08e3a3c250056227c518a15d5df38b346abae45a..7c92decbf0f4a10cc31f821a249749009d8b1485 100644 ---- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -+++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java -@@ -59,11 +59,43 @@ public class MobSpawnSettings { - } - - public static class Builder { -- private final Map> spawners = Stream.of(MobCategory.values()).collect(ImmutableMap.toImmutableMap((mobCategory) -> { -+ // 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(MobSpawnSettings.SpawnerData BiomeSettingsMobs) { -+ biomes.add(BiomeSettingsMobs); -+ return super.add(BiomeSettingsMobs); -+ } -+ -+ @Override -+ public MobSpawnSettings.SpawnerData remove(int index) { -+ MobSpawnSettings.SpawnerData 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> spawners = (Map) Stream.of(MobCategory.values()).collect(Maps.toImmutableEnumMap((mobCategory) -> { - return mobCategory; - }, (mobCategory) -> { -- return Lists.newArrayList(); -+ return new MobList(); // Use MobList instead of ArrayList - })); -+ // Paper end - private final Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap(); - private float creatureGenerationProbability = 0.1F; - diff --git a/patches/server/0667-Line-Of-Sight-Changes.patch b/patches/server/0667-Line-Of-Sight-Changes.patch new file mode 100644 index 0000000000..80cd9d95ac --- /dev/null +++ b/patches/server/0667-Line-Of-Sight-Changes.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> +Date: Sat, 29 May 2021 14:33:25 -0500 +Subject: [PATCH] Line Of Sight Changes + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d427cd90177b14062ea56dcf6fa5fedddcdbb624..4dee04c8012245b94191454943d68ee20fae887a 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3506,7 +3506,8 @@ public abstract class LivingEntity extends Entity { + Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); + Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); + +- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; ++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists ++ return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 84d84ceda1855bd1d11b2917c16fdb845a7600fe..d1fca0e3227b5f37c11367548be362f5a49b6a71 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -944,5 +944,16 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + public org.bukkit.NamespacedKey getKey() { + return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); + } ++ ++ public boolean lineOfSightExists(Location from, Location to) { ++ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); ++ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); ++ if (from.getWorld() != to.getWorld()) return false; ++ net.minecraft.world.phys.Vec3 vec3d = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); ++ net.minecraft.world.phys.Vec3 vec3d1 = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); ++ if (vec3d1.distanceToSqr(vec3d) > 128D * 128D) return false; //Return early if the distance is greater than 128 blocks ++ ++ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(vec3d, vec3d1, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, null)).getType() == net.minecraft.world.phys.HitResult.Type.MISS; ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 3ba00e72d9760ec0dcde93d54cd7868eea23ec8a..310dcf72f24fcbb86bc5b118536a8ebfc084eb7e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -29,6 +29,9 @@ import net.minecraft.world.entity.projectile.ThrownEgg; + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.entity.projectile.ThrownExperienceBottle; + import net.minecraft.world.entity.projectile.ThrownTrident; ++import net.minecraft.world.level.ClipContext; ++import net.minecraft.world.phys.HitResult; ++import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang.Validate; + import org.bukkit.FluidCollisionMode; + import org.bukkit.Location; +@@ -559,6 +562,18 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); + } + ++ // Paper start ++ @Override ++ public boolean hasLineOfSight(Location loc) { ++ if (this.getHandle().level != ((CraftWorld) loc.getWorld()).getHandle()) return false; ++ Vec3 vec3d = new Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); ++ Vec3 vec3d1 = new Vec3(loc.getX(), loc.getY(), loc.getZ()); ++ if (vec3d1.distanceToSqr(vec3d) > 128D * 128D) return false; //Return early if the distance is greater than 128 blocks ++ ++ return this.getHandle().level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.getHandle())).getType() == HitResult.Type.MISS; ++ } ++ // Paper end ++ + @Override + public boolean getRemoveWhenFarAway() { + return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0667-Make-item-validations-configurable.patch b/patches/server/0667-Make-item-validations-configurable.patch deleted file mode 100644 index 8c87e474af..0000000000 --- a/patches/server/0667-Make-item-validations-configurable.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 4 Jun 2021 12:12:35 -0700 -Subject: [PATCH] Make item validations configurable - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -index fefa4d83c5699be0b55794cd28d13d27b66ef108..d662cb0567884ec91c900f5c90ecb36912b127dd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java -@@ -92,11 +92,11 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { - super(tag); - - if (tag.contains(BOOK_TITLE.NBT)) { -- this.title = limit( tag.getString(BOOK_TITLE.NBT), 8192 ); // Spigot -+ this.title = limit( tag.getString(BOOK_TITLE.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.title); // Spigot // Paper - make configurable - } - - if (tag.contains(BOOK_AUTHOR.NBT)) { -- this.author = limit( tag.getString(BOOK_AUTHOR.NBT), 8192 ); // Spigot -+ this.author = limit( tag.getString(BOOK_AUTHOR.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.author ); // Spigot // Paper - make configurable - } - - if (tag.contains(RESOLVED.NBT)) { -@@ -124,7 +124,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta { - } else { - page = this.validatePage(page); - } -- this.pages.add( limit( page, 16384 ) ); // Spigot -+ this.pages.add( limit( page, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.book.page ) ); // Spigot // Paper - make configurable - } - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index c475ddea1c995df1dfcaf4f491f341761a5f8802..a8294bf057e03c5d866f6da31e6cdfa9edd3f146 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -357,7 +357,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - CompoundTag display = tag.getCompound(DISPLAY.NBT); - - if (display.contains(NAME.NBT)) { -- this.displayName = limit( display.getString(NAME.NBT), 8192 ); // Spigot -+ this.displayName = limit( display.getString(NAME.NBT), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.displayName ); // Spigot // Paper - make configurable - } - - if (display.contains(LOCNAME.NBT)) { -@@ -368,7 +368,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - ListTag list = display.getList(LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); - this.lore = new ArrayList(list.size()); - for (int index = 0; index < list.size(); index++) { -- String line = limit( list.getString(index), 8192 ); // Spigot -+ String line = limit( list.getString(index), io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.loreLine ); // Spigot // Paper - make configurable - this.lore.add(line); - } - } diff --git a/patches/server/0668-Line-Of-Sight-Changes.patch b/patches/server/0668-Line-Of-Sight-Changes.patch deleted file mode 100644 index 23a60d3d62..0000000000 --- a/patches/server/0668-Line-Of-Sight-Changes.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: TwoLeggedCat <80929284+TwoLeggedCat@users.noreply.github.com> -Date: Sat, 29 May 2021 14:33:25 -0500 -Subject: [PATCH] Line Of Sight Changes - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 644d71c024103c39d7532559c810038d687106e5..2694f7af35deff5c94929350589b2564f7bbdeff 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3484,7 +3484,8 @@ public abstract class LivingEntity extends Entity { - Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ()); - Vec3 vec3d1 = new Vec3(entity.getX(), entity.getEyeY(), entity.getZ()); - -- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; -+ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists -+ return vec3d1.distanceToSqr(vec3d) > 128D * 128D ? false : this.level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this)).getType() == HitResult.Type.MISS; // Paper - use distanceToSqr - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 84d84ceda1855bd1d11b2917c16fdb845a7600fe..d1fca0e3227b5f37c11367548be362f5a49b6a71 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -944,5 +944,16 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - public org.bukkit.NamespacedKey getKey() { - return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location()); - } -+ -+ public boolean lineOfSightExists(Location from, Location to) { -+ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null"); -+ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null"); -+ if (from.getWorld() != to.getWorld()) return false; -+ net.minecraft.world.phys.Vec3 vec3d = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ()); -+ net.minecraft.world.phys.Vec3 vec3d1 = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ()); -+ if (vec3d1.distanceToSqr(vec3d) > 128D * 128D) return false; //Return early if the distance is greater than 128 blocks -+ -+ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(vec3d, vec3d1, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, null)).getType() == net.minecraft.world.phys.HitResult.Type.MISS; -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 8fe1f5deddfee329c020d93c990dc686fe2b458e..ca176b9331345e343c19a02b6ba2ea886d20962d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -29,6 +29,9 @@ import net.minecraft.world.entity.projectile.ThrownEgg; - import net.minecraft.world.entity.projectile.ThrownEnderpearl; - import net.minecraft.world.entity.projectile.ThrownExperienceBottle; - import net.minecraft.world.entity.projectile.ThrownTrident; -+import net.minecraft.world.level.ClipContext; -+import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; - import org.apache.commons.lang.Validate; - import org.bukkit.FluidCollisionMode; - import org.bukkit.Location; -@@ -557,6 +560,18 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle()); - } - -+ // Paper start -+ @Override -+ public boolean hasLineOfSight(Location loc) { -+ if (this.getHandle().level != ((CraftWorld) loc.getWorld()).getHandle()) return false; -+ Vec3 vec3d = new Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ()); -+ Vec3 vec3d1 = new Vec3(loc.getX(), loc.getY(), loc.getZ()); -+ if (vec3d1.distanceToSqr(vec3d) > 128D * 128D) return false; //Return early if the distance is greater than 128 blocks -+ -+ return this.getHandle().level.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.getHandle())).getType() == HitResult.Type.MISS; -+ } -+ // Paper end -+ - @Override - public boolean getRemoveWhenFarAway() { - return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired(); diff --git a/patches/server/0668-add-per-world-spawn-limits.patch b/patches/server/0668-add-per-world-spawn-limits.patch new file mode 100644 index 0000000000..b75079eb4c --- /dev/null +++ b/patches/server/0668-add-per-world-spawn-limits.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chase +Date: Wed, 2 Dec 2020 22:43:39 -0800 +Subject: [PATCH] add per world spawn limits + +Taken from #2982. Credit to Chasewhip8 + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5fbc8fabf1c94798c21035049f6585e915da0536..2bed5b4eaeac4e48df606b755489a3ca5ffc895e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -210,6 +210,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { + this.biomeProvider = biomeProvider; + + this.environment = env; ++ // Paper start - per world spawn limits ++ for (SpawnCategory spawnCategory : SpawnCategory.values()) { ++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { ++ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); ++ } ++ } ++ // Paper end + } + + @Override diff --git a/patches/server/0669-Fix-PotionSplashEvent-for-water-splash-potions.patch b/patches/server/0669-Fix-PotionSplashEvent-for-water-splash-potions.patch new file mode 100644 index 0000000000..56f02dad6e --- /dev/null +++ b/patches/server/0669-Fix-PotionSplashEvent-for-water-splash-potions.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 20 May 2021 20:40:53 -0700 +Subject: [PATCH] Fix PotionSplashEvent for water splash potions + +Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index ebe9b713905d33a4107fe32874a783e154bd0524..fee09e6ff72cf1da389d5811dd005642cd50a5b4 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -121,30 +121,47 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + } + } + ++ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE.or(Axolotl.class::isInstance); // Paper + private void applyWater() { + AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); +- List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE); ++ List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); // Paper ++ Map affected = new HashMap<>(); // Paper + + if (!list.isEmpty()) { + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { + net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); ++ // Paper start - Change into single getEntities for axolotls & water sensitive ++ if (entityliving instanceof Axolotl axolotl) { ++ affected.put(axolotl.getBukkitLivingEntity(), 1.0); ++ continue; ++ } ++ // Paper end + double d0 = this.distanceToSqr((Entity) entityliving); + + if (d0 < 16.0D && entityliving.isSensitiveToWater()) { +- entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); ++ // Paper start ++ double intensity = 1.0D - Math.sqrt(d0) / 4.0D; ++ affected.put(entityliving.getBukkitLivingEntity(), intensity); ++ // entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); // Paper - moved down ++ // Paper end + } + } + } + +- List list1 = this.level.getEntitiesOfClass(Axolotl.class, axisalignedbb); +- Iterator iterator1 = list1.iterator(); +- +- while (iterator1.hasNext()) { +- Axolotl axolotl = (Axolotl) iterator1.next(); +- +- axolotl.rehydrate(); ++ // Paper start ++ org.bukkit.event.entity.PotionSplashEvent event = CraftEventFactory.callPotionSplashEvent(this, affected); ++ if (!event.isCancelled()) { ++ for (LivingEntity affectedEntity : event.getAffectedEntities()) { ++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) affectedEntity).getHandle(); ++ if (entityliving instanceof Axolotl axolotl && event.getIntensity(affectedEntity) > 0) { ++ axolotl.rehydrate(); ++ } else { ++ entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); ++ } ++ } ++ // Paper end + } + + } +@@ -165,6 +182,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + double d0 = this.distanceToSqr((Entity) entityliving); + + if (d0 < 16.0D) { ++ // Paper - diff on change, used when calling the splash event for water splash potions + double d1 = 1.0D - Math.sqrt(d0) / 4.0D; + + if (entityliving == entity) { diff --git a/patches/server/0669-add-per-world-spawn-limits.patch b/patches/server/0669-add-per-world-spawn-limits.patch deleted file mode 100644 index b75079eb4c..0000000000 --- a/patches/server/0669-add-per-world-spawn-limits.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chase -Date: Wed, 2 Dec 2020 22:43:39 -0800 -Subject: [PATCH] add per world spawn limits - -Taken from #2982. Credit to Chasewhip8 - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 5fbc8fabf1c94798c21035049f6585e915da0536..2bed5b4eaeac4e48df606b755489a3ca5ffc895e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -210,6 +210,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - this.biomeProvider = biomeProvider; - - this.environment = env; -+ // Paper start - per world spawn limits -+ for (SpawnCategory spawnCategory : SpawnCategory.values()) { -+ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { -+ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); -+ } -+ } -+ // Paper end - } - - @Override diff --git a/patches/server/0670-Add-more-LimitedRegion-API.patch b/patches/server/0670-Add-more-LimitedRegion-API.patch new file mode 100644 index 0000000000..e9e7611f99 --- /dev/null +++ b/patches/server/0670-Add-more-LimitedRegion-API.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dfsek +Date: Sat, 19 Jun 2021 20:15:59 -0700 +Subject: [PATCH] Add more LimitedRegion API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +index 992fc24a040c147596e6fe9f27936b4820ebc4b3..392701b0022e05d0fd03ee5836fd18f00502f028 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +@@ -226,4 +226,45 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe + public void addEntityToWorld(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) { + this.entities.add(entity); + } ++ ++ // Paper start ++ @Override ++ public void setBlockState(int x, int y, int z, BlockState state) { ++ BlockPos pos = new BlockPos(x, y, z); ++ if (!state.getBlockData().matches(getHandle().getBlockState(pos).createCraftBlockData())) { ++ throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getHandle().getBlockState(pos).createCraftBlockData().getAsString(false)); ++ } ++ getHandle().getBlockEntity(pos).load(((org.bukkit.craftbukkit.block.CraftBlockEntityState) state).getSnapshotNBT()); ++ } ++ ++ @Override ++ public void scheduleBlockUpdate(int x, int y, int z) { ++ BlockPos position = new BlockPos(x, y, z); ++ getHandle().scheduleTick(position, getHandle().getBlockState(position).getBlock(), 0); ++ } ++ ++ @Override ++ public void scheduleFluidUpdate(int x, int y, int z) { ++ BlockPos position = new BlockPos(x, y, z); ++ getHandle().scheduleTick(position, getHandle().getFluidState(position).getType(), 0); ++ } ++ ++ @Override ++ public World getWorld() { ++ // reading/writing the returned Minecraft world causes a deadlock. ++ // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and ++ // if they are stupid, they'll figure it out pretty fast. ++ return getHandle().getMinecraftWorld().getWorld(); ++ } ++ ++ @Override ++ public int getCenterChunkX() { ++ return centerChunkX; ++ } ++ ++ @Override ++ public int getCenterChunkZ() { ++ return centerChunkZ; ++ } ++ // Paper end + } diff --git a/patches/server/0670-Fix-PotionSplashEvent-for-water-splash-potions.patch b/patches/server/0670-Fix-PotionSplashEvent-for-water-splash-potions.patch deleted file mode 100644 index 56f02dad6e..0000000000 --- a/patches/server/0670-Fix-PotionSplashEvent-for-water-splash-potions.patch +++ /dev/null @@ -1,76 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 20 May 2021 20:40:53 -0700 -Subject: [PATCH] Fix PotionSplashEvent for water splash potions - -Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221 - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index ebe9b713905d33a4107fe32874a783e154bd0524..fee09e6ff72cf1da389d5811dd005642cd50a5b4 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -121,30 +121,47 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - } - } - -+ private static final Predicate APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE.or(Axolotl.class::isInstance); // Paper - private void applyWater() { - AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D); -- List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE); -+ List list = this.level.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE); // Paper -+ Map affected = new HashMap<>(); // Paper - - if (!list.isEmpty()) { - Iterator iterator = list.iterator(); - - while (iterator.hasNext()) { - net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next(); -+ // Paper start - Change into single getEntities for axolotls & water sensitive -+ if (entityliving instanceof Axolotl axolotl) { -+ affected.put(axolotl.getBukkitLivingEntity(), 1.0); -+ continue; -+ } -+ // Paper end - double d0 = this.distanceToSqr((Entity) entityliving); - - if (d0 < 16.0D && entityliving.isSensitiveToWater()) { -- entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); -+ // Paper start -+ double intensity = 1.0D - Math.sqrt(d0) / 4.0D; -+ affected.put(entityliving.getBukkitLivingEntity(), intensity); -+ // entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); // Paper - moved down -+ // Paper end - } - } - } - -- List list1 = this.level.getEntitiesOfClass(Axolotl.class, axisalignedbb); -- Iterator iterator1 = list1.iterator(); -- -- while (iterator1.hasNext()) { -- Axolotl axolotl = (Axolotl) iterator1.next(); -- -- axolotl.rehydrate(); -+ // Paper start -+ org.bukkit.event.entity.PotionSplashEvent event = CraftEventFactory.callPotionSplashEvent(this, affected); -+ if (!event.isCancelled()) { -+ for (LivingEntity affectedEntity : event.getAffectedEntities()) { -+ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) affectedEntity).getHandle(); -+ if (entityliving instanceof Axolotl axolotl && event.getIntensity(affectedEntity) > 0) { -+ axolotl.rehydrate(); -+ } else { -+ entityliving.hurt(DamageSource.indirectMagic(this, this.getOwner()), 1.0F); -+ } -+ } -+ // Paper end - } - - } -@@ -165,6 +182,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - double d0 = this.distanceToSqr((Entity) entityliving); - - if (d0 < 16.0D) { -+ // Paper - diff on change, used when calling the splash event for water splash potions - double d1 = 1.0D - Math.sqrt(d0) / 4.0D; - - if (entityliving == entity) { diff --git a/patches/server/0671-Add-more-LimitedRegion-API.patch b/patches/server/0671-Add-more-LimitedRegion-API.patch deleted file mode 100644 index e9e7611f99..0000000000 --- a/patches/server/0671-Add-more-LimitedRegion-API.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dfsek -Date: Sat, 19 Jun 2021 20:15:59 -0700 -Subject: [PATCH] Add more LimitedRegion API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -index 992fc24a040c147596e6fe9f27936b4820ebc4b3..392701b0022e05d0fd03ee5836fd18f00502f028 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -@@ -226,4 +226,45 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe - public void addEntityToWorld(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) { - this.entities.add(entity); - } -+ -+ // Paper start -+ @Override -+ public void setBlockState(int x, int y, int z, BlockState state) { -+ BlockPos pos = new BlockPos(x, y, z); -+ if (!state.getBlockData().matches(getHandle().getBlockState(pos).createCraftBlockData())) { -+ throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getHandle().getBlockState(pos).createCraftBlockData().getAsString(false)); -+ } -+ getHandle().getBlockEntity(pos).load(((org.bukkit.craftbukkit.block.CraftBlockEntityState) state).getSnapshotNBT()); -+ } -+ -+ @Override -+ public void scheduleBlockUpdate(int x, int y, int z) { -+ BlockPos position = new BlockPos(x, y, z); -+ getHandle().scheduleTick(position, getHandle().getBlockState(position).getBlock(), 0); -+ } -+ -+ @Override -+ public void scheduleFluidUpdate(int x, int y, int z) { -+ BlockPos position = new BlockPos(x, y, z); -+ getHandle().scheduleTick(position, getHandle().getFluidState(position).getType(), 0); -+ } -+ -+ @Override -+ public World getWorld() { -+ // reading/writing the returned Minecraft world causes a deadlock. -+ // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and -+ // if they are stupid, they'll figure it out pretty fast. -+ return getHandle().getMinecraftWorld().getWorld(); -+ } -+ -+ @Override -+ public int getCenterChunkX() { -+ return centerChunkX; -+ } -+ -+ @Override -+ public int getCenterChunkZ() { -+ return centerChunkZ; -+ } -+ // Paper end - } diff --git a/patches/server/0671-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0671-Fix-PlayerDropItemEvent-using-wrong-item.patch new file mode 100644 index 0000000000..7d613a826d --- /dev/null +++ b/patches/server/0671-Fix-PlayerDropItemEvent-using-wrong-item.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 21:55:59 -0700 +Subject: [PATCH] Fix PlayerDropItemEvent using wrong item + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 4a35720430990b358ea5d7f2b6293e27e8d9f7ac..9a60cf249e0b9f089b0966c670e6046e7e1ed08a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2201,7 +2201,7 @@ public class ServerPlayer extends Player { + + if (retainOwnership) { + if (!itemstack1.isEmpty()) { +- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper + } + + this.awardStat(Stats.DROP); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index a6bd94ed379ef1ab0ffe71183aef3cf3061fd092..92a5aadef076cb905962dab86f32d4ff253fef93 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -723,6 +723,11 @@ public abstract class Player extends LivingEntity { + } + + double d0 = this.getEyeY() - 0.30000001192092896D; ++ // Paper start ++ ItemStack tmp = itemstack.copy(); ++ itemstack.setCount(0); ++ itemstack = tmp; ++ // Paper end + ItemEntity entityitem = new ItemEntity(this.level, this.getX(), d0, this.getZ(), itemstack); + + entityitem.setPickUpDelay(40); diff --git a/patches/server/0672-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0672-Fix-PlayerDropItemEvent-using-wrong-item.patch deleted file mode 100644 index 7d613a826d..0000000000 --- a/patches/server/0672-Fix-PlayerDropItemEvent-using-wrong-item.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 21:55:59 -0700 -Subject: [PATCH] Fix PlayerDropItemEvent using wrong item - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 4a35720430990b358ea5d7f2b6293e27e8d9f7ac..9a60cf249e0b9f089b0966c670e6046e7e1ed08a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2201,7 +2201,7 @@ public class ServerPlayer extends Player { - - if (retainOwnership) { - if (!itemstack1.isEmpty()) { -- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); -+ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - } - - this.awardStat(Stats.DROP); -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index a6bd94ed379ef1ab0ffe71183aef3cf3061fd092..92a5aadef076cb905962dab86f32d4ff253fef93 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -723,6 +723,11 @@ public abstract class Player extends LivingEntity { - } - - double d0 = this.getEyeY() - 0.30000001192092896D; -+ // Paper start -+ ItemStack tmp = itemstack.copy(); -+ itemstack.setCount(0); -+ itemstack = tmp; -+ // Paper end - ItemEntity entityitem = new ItemEntity(this.level, this.getX(), d0, this.getZ(), itemstack); - - entityitem.setPickUpDelay(40); diff --git a/patches/server/0672-Missing-Entity-Behavior-API.patch b/patches/server/0672-Missing-Entity-Behavior-API.patch new file mode 100644 index 0000000000..ba264fa322 --- /dev/null +++ b/patches/server/0672-Missing-Entity-Behavior-API.patch @@ -0,0 +1,612 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 23:56:07 -0400 +Subject: [PATCH] Missing Entity Behavior API + +Co-authored-by: Nassim Jahnke +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index d13f3460644f635ded96bf92ddf9ecf8984c8e47..bd048cc30046f19f9eee89c6ba45d0816a160e67 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -540,11 +540,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + this.setFlag(4, hasStung); + } + ++ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override + public boolean isRolling() { + return this.getFlag(2); + } + + public void setRolling(boolean nearTarget) { ++ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override + this.setFlag(2, nearTarget); + } + +diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +index 04a119e6641898454253e2478bc1b4dff181b5ee..a8da601b8342aa6e4902b452eb588c76c98a7adf 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java ++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +@@ -663,6 +663,14 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + + } + ++ // Paper Start - Horse API ++ public void setMouthOpen(boolean open) { ++ this.setFlag(FLAG_OPEN_MOUTH, open); ++ } ++ public boolean isMouthOpen() { ++ return this.getFlag(FLAG_OPEN_MOUTH); ++ } ++ // Paper End - Horse API + private void openMouth() { + if (!this.level.isClientSide) { + this.mouthCounter = 1; +@@ -675,6 +683,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, + this.setFlag(16, eatingGrass); + } + ++ // Paper Start - Horse API ++ public void setForceStanding(boolean standing) { ++ this.setFlag(FLAG_STANDING, standing); ++ } ++ // Paper End - Horse API + public void setStanding(boolean angry) { + if (angry) { + this.setEating(false); +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 32b302aad0319ce3ee412912425c1c8db9979f8a..92734f767fde60351a179a88350a97b861be0e88 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -84,6 +84,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); + }; + private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); ++ // Paper start ++ private boolean canPortal = false; ++ ++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } ++ // Paper end + + public WitherBoss(EntityType type, Level world) { + super(type, world); +@@ -602,7 +607,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + + @Override + public boolean canChangeDimensions() { +- return false; ++ return super.canChangeDimensions() && canPortal; // Paper + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +index e0e32528ed9f2f494b5ee2079c3167021f2e84c4..f22e615dba31619c97bf58930da060476a52facf 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java ++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +@@ -433,6 +433,16 @@ public class EnderMan extends Monster implements NeutralMob { + this.entityData.set(EnderMan.DATA_STARED_AT, true); + } + ++ // Paper start ++ public void setCreepy(boolean creepy) { ++ this.entityData.set(EnderMan.DATA_CREEPY, creepy); ++ } ++ ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public boolean requiresCustomPersistence() { + return super.requiresCustomPersistence() || this.getCarriedBlock() != null; +diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +index 1f3506d38894fea224f3b2f125b45c3b68d705c7..bb2cb17e4e5ce142eeec18951c8948e3d6b3209c 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java +@@ -66,6 +66,12 @@ public class Ghast extends FlyingMob implements Enemy { + return this.explosionPower; + } + ++ // Paper start ++ public void setExplosionPower(int explosionPower) { ++ this.explosionPower = explosionPower; ++ } ++ // Paper end ++ + @Override + protected boolean shouldDespawnInPeaceful() { + return true; +diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +index e93a2634cd80cd4f1caf6bd60e569783bc6b577c..fb0a77b4cf1ba47c73c00993bd9b7454240fe5d6 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java ++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +@@ -197,6 +197,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + } + + public void startConverting(@Nullable UUID uuid, int delay) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.startConverting(uuid, delay, true); ++ } ++ ++ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { ++ // Paper end - missing entity behaviour api - converting without entity event + this.conversionStarter = uuid; + this.villagerConversionTime = delay; + this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); +@@ -204,7 +210,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { + this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level.getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + // CraftBukkit end +- this.level.broadcastEntityEvent(this, (byte) 16); ++ if (broadcastEntityEvent) this.level.broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +index a367f50b0e3fe9e7a1b87892a8c98e88bd678f6f..1b31b32d42eeb54680b902cd7e82d10ba7daa5d0 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +@@ -105,6 +105,20 @@ public class ThrownTrident extends AbstractArrow { + return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); + } + ++ // Paper start ++ public void setFoil(boolean foil) { ++ this.entityData.set(ThrownTrident.ID_FOIL, foil); ++ } ++ ++ public int getLoyalty() { ++ return this.entityData.get(ThrownTrident.ID_LOYALTY); ++ } ++ ++ public void setLoyalty(byte loyalty) { ++ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); ++ } ++ // Paper end ++ + @Nullable + @Override + protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +index 254d4f2e45d7c8f572a4368eccd84560d4d0d836..299ab868252c8f326e3a56e878c9ee230c9635dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java +@@ -115,4 +115,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac + public AbstractHorseInventory getInventory() { + return new CraftSaddledInventory(getHandle().inventory); + } ++ ++ // Paper start - Horse API ++ @Override ++ public boolean isEatingGrass() { ++ return this.getHandle().isEating(); ++ } ++ ++ @Override ++ public void setEatingGrass(boolean eating) { ++ this.getHandle().setEating(eating); ++ } ++ ++ @Override ++ public boolean isRearing() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setRearing(boolean rearing) { ++ this.getHandle().setForceStanding(rearing); ++ } ++ ++ @Override ++ public boolean isEating() { ++ return this.getHandle().isMouthOpen(); ++ } ++ ++ @Override ++ public void setEating(boolean eating) { ++ this.getHandle().setMouthOpen(eating); ++ } ++ // Paper end - Horse API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +index 8ada3dfbe89c8b55d85c31c71e365af0cbf66d19..b5d3a00a48d3b7618f974bb0f6909aa7c304b012 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java +@@ -91,4 +91,22 @@ public class CraftBee extends CraftAnimals implements Bee { + public void setCannotEnterHiveTicks(int ticks) { + this.getHandle().setStayOutOfHiveCountdown(ticks); + } ++ // Paper start ++ @Override ++ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { ++ this.getHandle().rollingOverride = rolling; ++ ++ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state ++ } ++ ++ @Override ++ public boolean isRolling() { ++ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); ++ } ++ ++ @Override ++ public net.kyori.adventure.util.TriState getRollingOverride() { ++ return this.getHandle().rollingOverride; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +index 37352ca3ff267d02a26ed182ce3df3ef775fa9bc..6a504f61c55d3983871f8d1c5c002c7a7b9c50ff 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +@@ -51,4 +51,25 @@ public class CraftCat extends CraftTameableAnimal implements Cat { + public void setCollarColor(DyeColor color) { + this.getHandle().setCollarColor(net.minecraft.world.item.DyeColor.byId(color.getWoolData())); + } ++ // Paper Start - More cat api ++ @Override ++ public void setLyingDown(boolean lyingDown) { ++ this.getHandle().setLying(lyingDown); ++ } ++ ++ @Override ++ public boolean isLyingDown() { ++ return this.getHandle().isLying(); ++ } ++ ++ @Override ++ public void setHeadUp(boolean headUp) { ++ this.getHandle().setRelaxStateOne(headUp); ++ } ++ ++ @Override ++ public boolean isHeadUp() { ++ return this.getHandle().isRelaxStateOne(); ++ } ++ // Paper End - More cat api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +index ae669a970aa1f17ed786640de8a481364543c58e..acdc4e578d70f8121c8c6be7682ba1ecef7687cf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java +@@ -39,6 +39,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { + this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); + } + ++ // Paper start ++ @Override ++ public boolean isScreaming() { ++ return this.getHandle().isCreepy(); ++ } ++ ++ @Override ++ public void setScreaming(boolean screaming) { ++ this.getHandle().setCreepy(screaming); ++ } ++ ++ @Override ++ public boolean hasBeenStaredAt() { ++ return this.getHandle().hasBeenStaredAt(); ++ } ++ ++ @Override ++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { ++ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); ++ } ++ // Paper end ++ + @Override + public EnderMan getHandle() { + return (EnderMan) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +index f6369a1b0ea3fc64e9e7902d9da25924a0745855..fce96b67300b8808984904ee19d4e987f5235bfd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java +@@ -119,4 +119,41 @@ public class CraftFox extends CraftAnimals implements Fox { + public boolean isFaceplanted() { + return this.getHandle().isFaceplanted(); + } ++ ++ // Paper start - Add more fox behavior API ++ @Override ++ public void setInterested(boolean interested) { ++ this.getHandle().setIsInterested(interested); ++ } ++ ++ @Override ++ public boolean isInterested() { ++ return this.getHandle().isInterested(); ++ } ++ ++ @Override ++ public void setLeaping(boolean leaping) { ++ this.getHandle().setIsPouncing(leaping); ++ } ++ ++ @Override ++ public boolean isLeaping() { ++ return this.getHandle().isPouncing(); ++ } ++ ++ @Override ++ public void setDefending(boolean defending) { ++ this.getHandle().setDefending(defending); ++ } ++ ++ @Override ++ public boolean isDefending() { ++ return this.getHandle().isDefending(); ++ } ++ ++ @Override ++ public void setFaceplanted(boolean faceplanted) { ++ this.getHandle().setFaceplanted(faceplanted); ++ } ++ // Paper end - Add more fox behavior API + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +index 7adda5c93e7c172ea0ba4a3f15828b5e54a283e7..fffaf4108b632ceabac4186d93b34ad0eb069a04 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java +@@ -34,4 +34,17 @@ public class CraftGhast extends CraftFlying implements Ghast { + public void setCharging(boolean flag) { + this.getHandle().setCharging(flag); + } ++ ++ // Paper start ++ @Override ++ public int getExplosionPower() { ++ return this.getHandle().getExplosionPower(); ++ } ++ ++ @Override ++ public void setExplosionPower(int explosionPower) { ++ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); ++ this.getHandle().setExplosionPower(explosionPower); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +index ff9f711b83a8ea1bf4135751a9ba865224bce787..1f6dcad764240e15083731d017f9bb1c5c84622f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java +@@ -46,6 +46,32 @@ public class CraftPanda extends CraftAnimals implements Panda { + public void setHiddenGene(Gene gene) { + this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); + } ++ // Paper start - Panda API ++ @Override ++ public void setSneezeTicks(int ticks) { ++ this.getHandle().setSneezeCounter(ticks); ++ } ++ ++ @Override ++ public int getSneezeTicks() { ++ return this.getHandle().getSneezeCounter(); ++ } ++ ++ @Override ++ public void setEatingTicks(int ticks) { ++ this.getHandle().setEatCounter(ticks); ++ } ++ ++ @Override ++ public int getEatingTicks() { ++ return this.getHandle().getEatCounter(); ++ } ++ ++ @Override ++ public void setUnhappyTicks(int ticks) { ++ this.getHandle().setUnhappyCounter(ticks); ++ } ++ // Paper end - Panda API + + @Override + public boolean isRolling() { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +index aeda5fc001fe4ce55ee467240b275b6050a29f98..48d0a4e42e1b90d1323784d1284acabfe9497dd6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java +@@ -90,4 +90,15 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest + public String toString() { + return "CraftPiglin"; + } ++ // Paper start ++ @Override ++ public void setChargingCrossbow(boolean chargingCrossbow) { ++ this.getHandle().setChargingCrossbow(chargingCrossbow); ++ } ++ ++ @Override ++ public boolean isChargingCrossbow() { ++ return this.getHandle().isChargingCrossbow(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +index da1488c9cae53bd554727c850da2192adda2478a..30a0eac179c86b0fe94a2a40b5bfcd3eee01e53b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java +@@ -23,4 +23,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { + public EntityType getType() { + return EntityType.POLAR_BEAR; + } ++ ++ // Paper start ++ @Override ++ public boolean isStanding() { ++ return this.getHandle().isStanding(); ++ } ++ ++ @Override ++ public void setStanding(boolean standing) { ++ this.getHandle().setStanding(standing); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java +index b10bcbc19362f0f8596ebcf3f3e1060486cfc74f..e24eec79402843105a13de2bb8554260908057cc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java +@@ -65,4 +65,16 @@ public abstract class CraftRaider extends CraftMonster implements Raider { + public Sound getCelebrationSound() { + return CraftSound.getBukkit(this.getHandle().getCelebrateSound()); + } ++ ++ // Paper start ++ @Override ++ public boolean isCelebrating() { ++ return this.getHandle().isCelebrating(); ++ } ++ ++ @Override ++ public void setCelebrating(boolean celebrating) { ++ this.getHandle().setCelebrating(celebrating); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +index bf5b2fd6676c4430578db4cc6c603c501cc5e349..832981b07ef5c633ef00a382f56798ee87eec0df 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java +@@ -37,4 +37,27 @@ public class CraftTrident extends CraftArrow implements Trident { + public EntityType getType() { + return EntityType.TRIDENT; + } ++ ++ // Paper start ++ @Override ++ public boolean hasGlint() { ++ return this.getHandle().isFoil(); ++ } ++ ++ @Override ++ public void setGlint(boolean glint) { ++ this.getHandle().setFoil(glint); ++ } ++ ++ @Override ++ public int getLoyaltyLevel() { ++ return this.getHandle().getLoyalty(); ++ } ++ ++ @Override ++ public void setLoyaltyLevel(int loyaltyLevel) { ++ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); ++ this.getHandle().setLoyalty((byte) loyaltyLevel); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +index 2ba16e33dd21c3c72cb12244aa78c59bf53e76d1..634a5099fb6faea03615783f57e643ad0083fa30 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java +@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { + public void setSummoner(org.bukkit.entity.Mob summoner) { + getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); + } ++ ++ @Override ++ public boolean hasLimitedLifetime() { ++ return this.getHandle().hasLimitedLife; ++ } ++ ++ @Override ++ public void setLimitedLifetime(boolean hasLimitedLifetime) { ++ this.getHandle().hasLimitedLife = hasLimitedLifetime; ++ } ++ ++ @Override ++ public int getLimitedLifetimeTicks() { ++ return this.getHandle().limitedLifeTicks; ++ } ++ ++ @Override ++ public void setLimitedLifetimeTicks(int ticks) { ++ this.getHandle().limitedLifeTicks = ticks; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +index 8a0a905f6701c6e94cbbf15793788350958fb728..2a74e6ecb4f57bc6879b37f7bc0675417223bcbf 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java +@@ -72,13 +72,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { + + @Override + public void setConversionTime(int time) { ++ // Paper start - missing entity behaviour api - converting without entity event ++ this.setConversionTime(time, true); ++ } ++ ++ @Override ++ public void setConversionTime(int time, boolean broadcastEntityEvent) { ++ // Paper stop - missing entity behaviour api - converting without entity event + if (time < 0) { + this.getHandle().villagerConversionTime = -1; + this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); + this.getHandle().conversionStarter = null; + this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); + } else { +- this.getHandle().startConverting((UUID) null, time); ++ this.getHandle().startConverting((UUID) null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +index 7ece945d1a4fa29c7b98532788076483037f4bda..963928fc8e29b8abc2026c0b0183ebb07f0de4d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java +@@ -43,6 +43,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde + return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); + } + ++ // Paper start ++ @Override ++ public int getHighestAnger() { ++ return this.getHandle().getAngerManagement().getActiveAnger(null); ++ } ++ // Paper end ++ + @Override + public void increaseAnger(Entity entity, int increase) { + Preconditions.checkArgument(entity != null, "Entity cannot be null"); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +index e92355fa2042c4cf15354a11b7058cacbe996f0d..4cf3a374c9ee7c7bcf82e778aa094eb4f8463595 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +@@ -61,4 +61,31 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok + Entity target = this.getHandle().getLevel().getEntity(entityId); + return (target != null) ? (LivingEntity) target.getBukkitEntity() : null; + } ++ ++ // Paper start ++ @Override ++ public boolean isCharged() { ++ return getHandle().isPowered(); ++ } ++ ++ @Override ++ public int getInvulnerableTicks() { ++ return getHandle().getInvulnerableTicks(); ++ } ++ ++ @Override ++ public void setInvulnerableTicks(int ticks) { ++ getHandle().setInvulnerableTicks(ticks); ++ } ++ ++ @Override ++ public boolean canTravelThroughPortals() { ++ return getHandle().canChangeDimensions(); ++ } ++ ++ @Override ++ public void setCanTravelThroughPortals(boolean value) { ++ getHandle().setCanTravelThroughPortals(value); ++ } ++ // Paper end + } diff --git a/patches/server/0673-Ensure-disconnect-for-book-edit-is-called-on-main.patch b/patches/server/0673-Ensure-disconnect-for-book-edit-is-called-on-main.patch new file mode 100644 index 0000000000..a2ab09f58e --- /dev/null +++ b/patches/server/0673-Ensure-disconnect-for-book-edit-is-called-on-main.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 22 Jun 2021 19:58:53 +0100 +Subject: [PATCH] Ensure disconnect for book edit is called on main + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 55c5348e793fa560d7042cf90076a8f7a3b76547..63bfea707e0664e19726692546dd0ac8a86f8b8f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1213,7 +1213,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // Paper end + // CraftBukkit start + if (this.lastBookTick + 20 > MinecraftServer.currentTick) { +- this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause ++ server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main + return; + } + this.lastBookTick = MinecraftServer.currentTick; diff --git a/patches/server/0673-Missing-Entity-Behavior-API.patch b/patches/server/0673-Missing-Entity-Behavior-API.patch deleted file mode 100644 index 31cf843b16..0000000000 --- a/patches/server/0673-Missing-Entity-Behavior-API.patch +++ /dev/null @@ -1,612 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 23:56:07 -0400 -Subject: [PATCH] Missing Entity Behavior API - -Co-authored-by: Nassim Jahnke -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index d13f3460644f635ded96bf92ddf9ecf8984c8e47..bd048cc30046f19f9eee89c6ba45d0816a160e67 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -540,11 +540,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.setFlag(4, hasStung); - } - -+ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override - public boolean isRolling() { - return this.getFlag(2); - } - - public void setRolling(boolean nearTarget) { -+ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override - this.setFlag(2, nearTarget); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index 04a119e6641898454253e2478bc1b4dff181b5ee..a8da601b8342aa6e4902b452eb588c76c98a7adf 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -663,6 +663,14 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - - } - -+ // Paper Start - Horse API -+ public void setMouthOpen(boolean open) { -+ this.setFlag(FLAG_OPEN_MOUTH, open); -+ } -+ public boolean isMouthOpen() { -+ return this.getFlag(FLAG_OPEN_MOUTH); -+ } -+ // Paper End - Horse API - private void openMouth() { - if (!this.level.isClientSide) { - this.mouthCounter = 1; -@@ -675,6 +683,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - this.setFlag(16, eatingGrass); - } - -+ // Paper Start - Horse API -+ public void setForceStanding(boolean standing) { -+ this.setFlag(FLAG_STANDING, standing); -+ } -+ // Paper End - Horse API - public void setStanding(boolean angry) { - if (angry) { - this.setEating(false); -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 32b302aad0319ce3ee412912425c1c8db9979f8a..92734f767fde60351a179a88350a97b861be0e88 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -84,6 +84,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); - }; - private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); -+ // Paper start -+ private boolean canPortal = false; -+ -+ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } -+ // Paper end - - public WitherBoss(EntityType type, Level world) { - super(type, world); -@@ -602,7 +607,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canChangeDimensions() { -- return false; -+ return super.canChangeDimensions() && canPortal; // Paper - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index e0e32528ed9f2f494b5ee2079c3167021f2e84c4..f22e615dba31619c97bf58930da060476a52facf 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -433,6 +433,16 @@ public class EnderMan extends Monster implements NeutralMob { - this.entityData.set(EnderMan.DATA_STARED_AT, true); - } - -+ // Paper start -+ public void setCreepy(boolean creepy) { -+ this.entityData.set(EnderMan.DATA_CREEPY, creepy); -+ } -+ -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public boolean requiresCustomPersistence() { - return super.requiresCustomPersistence() || this.getCarriedBlock() != null; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -index 1f3506d38894fea224f3b2f125b45c3b68d705c7..bb2cb17e4e5ce142eeec18951c8948e3d6b3209c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java -@@ -66,6 +66,12 @@ public class Ghast extends FlyingMob implements Enemy { - return this.explosionPower; - } - -+ // Paper start -+ public void setExplosionPower(int explosionPower) { -+ this.explosionPower = explosionPower; -+ } -+ // Paper end -+ - @Override - protected boolean shouldDespawnInPeaceful() { - return true; -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index e93a2634cd80cd4f1caf6bd60e569783bc6b577c..fb0a77b4cf1ba47c73c00993bd9b7454240fe5d6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -197,6 +197,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - } - - public void startConverting(@Nullable UUID uuid, int delay) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.startConverting(uuid, delay, true); -+ } -+ -+ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) { -+ // Paper end - missing entity behaviour api - converting without entity event - this.conversionStarter = uuid; - this.villagerConversionTime = delay; - this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true); -@@ -204,7 +210,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level.getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - // CraftBukkit end -- this.level.broadcastEntityEvent(this, (byte) 16); -+ if (broadcastEntityEvent) this.level.broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -index a367f50b0e3fe9e7a1b87892a8c98e88bd678f6f..1b31b32d42eeb54680b902cd7e82d10ba7daa5d0 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -105,6 +105,20 @@ public class ThrownTrident extends AbstractArrow { - return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL); - } - -+ // Paper start -+ public void setFoil(boolean foil) { -+ this.entityData.set(ThrownTrident.ID_FOIL, foil); -+ } -+ -+ public int getLoyalty() { -+ return this.entityData.get(ThrownTrident.ID_LOYALTY); -+ } -+ -+ public void setLoyalty(byte loyalty) { -+ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty); -+ } -+ // Paper end -+ - @Nullable - @Override - protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -index 254d4f2e45d7c8f572a4368eccd84560d4d0d836..299ab868252c8f326e3a56e878c9ee230c9635dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java -@@ -115,4 +115,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac - public AbstractHorseInventory getInventory() { - return new CraftSaddledInventory(getHandle().inventory); - } -+ -+ // Paper start - Horse API -+ @Override -+ public boolean isEatingGrass() { -+ return this.getHandle().isEating(); -+ } -+ -+ @Override -+ public void setEatingGrass(boolean eating) { -+ this.getHandle().setEating(eating); -+ } -+ -+ @Override -+ public boolean isRearing() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setRearing(boolean rearing) { -+ this.getHandle().setForceStanding(rearing); -+ } -+ -+ @Override -+ public boolean isEating() { -+ return this.getHandle().isMouthOpen(); -+ } -+ -+ @Override -+ public void setEating(boolean eating) { -+ this.getHandle().setMouthOpen(eating); -+ } -+ // Paper end - Horse API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -index 8ada3dfbe89c8b55d85c31c71e365af0cbf66d19..b5d3a00a48d3b7618f974bb0f6909aa7c304b012 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java -@@ -91,4 +91,22 @@ public class CraftBee extends CraftAnimals implements Bee { - public void setCannotEnterHiveTicks(int ticks) { - this.getHandle().setStayOutOfHiveCountdown(ticks); - } -+ // Paper start -+ @Override -+ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) { -+ this.getHandle().rollingOverride = rolling; -+ -+ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state -+ } -+ -+ @Override -+ public boolean isRolling() { -+ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling()); -+ } -+ -+ @Override -+ public net.kyori.adventure.util.TriState getRollingOverride() { -+ return this.getHandle().rollingOverride; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -index 37352ca3ff267d02a26ed182ce3df3ef775fa9bc..6a504f61c55d3983871f8d1c5c002c7a7b9c50ff 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java -@@ -51,4 +51,25 @@ public class CraftCat extends CraftTameableAnimal implements Cat { - public void setCollarColor(DyeColor color) { - this.getHandle().setCollarColor(net.minecraft.world.item.DyeColor.byId(color.getWoolData())); - } -+ // Paper Start - More cat api -+ @Override -+ public void setLyingDown(boolean lyingDown) { -+ this.getHandle().setLying(lyingDown); -+ } -+ -+ @Override -+ public boolean isLyingDown() { -+ return this.getHandle().isLying(); -+ } -+ -+ @Override -+ public void setHeadUp(boolean headUp) { -+ this.getHandle().setRelaxStateOne(headUp); -+ } -+ -+ @Override -+ public boolean isHeadUp() { -+ return this.getHandle().isRelaxStateOne(); -+ } -+ // Paper End - More cat api - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -index ae669a970aa1f17ed786640de8a481364543c58e..acdc4e578d70f8121c8c6be7682ba1ecef7687cf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java -@@ -39,6 +39,28 @@ public class CraftEnderman extends CraftMonster implements Enderman { - this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState()); - } - -+ // Paper start -+ @Override -+ public boolean isScreaming() { -+ return this.getHandle().isCreepy(); -+ } -+ -+ @Override -+ public void setScreaming(boolean screaming) { -+ this.getHandle().setCreepy(screaming); -+ } -+ -+ @Override -+ public boolean hasBeenStaredAt() { -+ return this.getHandle().hasBeenStaredAt(); -+ } -+ -+ @Override -+ public void setHasBeenStaredAt(boolean hasBeenStaredAt) { -+ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt); -+ } -+ // Paper end -+ - @Override - public EnderMan getHandle() { - return (EnderMan) entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -index f6369a1b0ea3fc64e9e7902d9da25924a0745855..fce96b67300b8808984904ee19d4e987f5235bfd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java -@@ -119,4 +119,41 @@ public class CraftFox extends CraftAnimals implements Fox { - public boolean isFaceplanted() { - return this.getHandle().isFaceplanted(); - } -+ -+ // Paper start - Add more fox behavior API -+ @Override -+ public void setInterested(boolean interested) { -+ this.getHandle().setIsInterested(interested); -+ } -+ -+ @Override -+ public boolean isInterested() { -+ return this.getHandle().isInterested(); -+ } -+ -+ @Override -+ public void setLeaping(boolean leaping) { -+ this.getHandle().setIsPouncing(leaping); -+ } -+ -+ @Override -+ public boolean isLeaping() { -+ return this.getHandle().isPouncing(); -+ } -+ -+ @Override -+ public void setDefending(boolean defending) { -+ this.getHandle().setDefending(defending); -+ } -+ -+ @Override -+ public boolean isDefending() { -+ return this.getHandle().isDefending(); -+ } -+ -+ @Override -+ public void setFaceplanted(boolean faceplanted) { -+ this.getHandle().setFaceplanted(faceplanted); -+ } -+ // Paper end - Add more fox behavior API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -index 7adda5c93e7c172ea0ba4a3f15828b5e54a283e7..fffaf4108b632ceabac4186d93b34ad0eb069a04 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java -@@ -34,4 +34,17 @@ public class CraftGhast extends CraftFlying implements Ghast { - public void setCharging(boolean flag) { - this.getHandle().setCharging(flag); - } -+ -+ // Paper start -+ @Override -+ public int getExplosionPower() { -+ return this.getHandle().getExplosionPower(); -+ } -+ -+ @Override -+ public void setExplosionPower(int explosionPower) { -+ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127"); -+ this.getHandle().setExplosionPower(explosionPower); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -index ff9f711b83a8ea1bf4135751a9ba865224bce787..1f6dcad764240e15083731d017f9bb1c5c84622f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java -@@ -46,6 +46,32 @@ public class CraftPanda extends CraftAnimals implements Panda { - public void setHiddenGene(Gene gene) { - this.getHandle().setHiddenGene(CraftPanda.toNms(gene)); - } -+ // Paper start - Panda API -+ @Override -+ public void setSneezeTicks(int ticks) { -+ this.getHandle().setSneezeCounter(ticks); -+ } -+ -+ @Override -+ public int getSneezeTicks() { -+ return this.getHandle().getSneezeCounter(); -+ } -+ -+ @Override -+ public void setEatingTicks(int ticks) { -+ this.getHandle().setEatCounter(ticks); -+ } -+ -+ @Override -+ public int getEatingTicks() { -+ return this.getHandle().getEatCounter(); -+ } -+ -+ @Override -+ public void setUnhappyTicks(int ticks) { -+ this.getHandle().setUnhappyCounter(ticks); -+ } -+ // Paper end - Panda API - - @Override - public boolean isRolling() { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -index aeda5fc001fe4ce55ee467240b275b6050a29f98..48d0a4e42e1b90d1323784d1284acabfe9497dd6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java -@@ -90,4 +90,15 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest - public String toString() { - return "CraftPiglin"; - } -+ // Paper start -+ @Override -+ public void setChargingCrossbow(boolean chargingCrossbow) { -+ this.getHandle().setChargingCrossbow(chargingCrossbow); -+ } -+ -+ @Override -+ public boolean isChargingCrossbow() { -+ return this.getHandle().isChargingCrossbow(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -index da1488c9cae53bd554727c850da2192adda2478a..30a0eac179c86b0fe94a2a40b5bfcd3eee01e53b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java -@@ -23,4 +23,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear { - public EntityType getType() { - return EntityType.POLAR_BEAR; - } -+ -+ // Paper start -+ @Override -+ public boolean isStanding() { -+ return this.getHandle().isStanding(); -+ } -+ -+ @Override -+ public void setStanding(boolean standing) { -+ this.getHandle().setStanding(standing); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -index 27a8a2b1e03254b1fc6fe8edc3ff77841a42f5f6..8d77b870fd9de69b57ae1affdfbd2a02f62e75c7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -@@ -58,4 +58,16 @@ public abstract class CraftRaider extends CraftMonster implements Raider { - public void setCanJoinRaid(boolean join) { - this.getHandle().setCanJoinRaid(join); - } -+ -+ // Paper start -+ @Override -+ public boolean isCelebrating() { -+ return this.getHandle().isCelebrating(); -+ } -+ -+ @Override -+ public void setCelebrating(boolean celebrating) { -+ this.getHandle().setCelebrating(celebrating); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -index bf5b2fd6676c4430578db4cc6c603c501cc5e349..832981b07ef5c633ef00a382f56798ee87eec0df 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java -@@ -37,4 +37,27 @@ public class CraftTrident extends CraftArrow implements Trident { - public EntityType getType() { - return EntityType.TRIDENT; - } -+ -+ // Paper start -+ @Override -+ public boolean hasGlint() { -+ return this.getHandle().isFoil(); -+ } -+ -+ @Override -+ public void setGlint(boolean glint) { -+ this.getHandle().setFoil(glint); -+ } -+ -+ @Override -+ public int getLoyaltyLevel() { -+ return this.getHandle().getLoyalty(); -+ } -+ -+ @Override -+ public void setLoyaltyLevel(int loyaltyLevel) { -+ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127"); -+ this.getHandle().setLoyalty((byte) loyaltyLevel); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -index 2ba16e33dd21c3c72cb12244aa78c59bf53e76d1..634a5099fb6faea03615783f57e643ad0083fa30 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java -@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex { - public void setSummoner(org.bukkit.entity.Mob summoner) { - getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle()); - } -+ -+ @Override -+ public boolean hasLimitedLifetime() { -+ return this.getHandle().hasLimitedLife; -+ } -+ -+ @Override -+ public void setLimitedLifetime(boolean hasLimitedLifetime) { -+ this.getHandle().hasLimitedLife = hasLimitedLifetime; -+ } -+ -+ @Override -+ public int getLimitedLifetimeTicks() { -+ return this.getHandle().limitedLifeTicks; -+ } -+ -+ @Override -+ public void setLimitedLifetimeTicks(int ticks) { -+ this.getHandle().limitedLifeTicks = ticks; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -index 8a0a905f6701c6e94cbbf15793788350958fb728..2a74e6ecb4f57bc6879b37f7bc0675417223bcbf 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java -@@ -72,13 +72,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager { - - @Override - public void setConversionTime(int time) { -+ // Paper start - missing entity behaviour api - converting without entity event -+ this.setConversionTime(time, true); -+ } -+ -+ @Override -+ public void setConversionTime(int time, boolean broadcastEntityEvent) { -+ // Paper stop - missing entity behaviour api - converting without entity event - if (time < 0) { - this.getHandle().villagerConversionTime = -1; - this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false); - this.getHandle().conversionStarter = null; - this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); - } else { -- this.getHandle().startConverting((UUID) null, time); -+ this.getHandle().startConverting((UUID) null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -index 7ece945d1a4fa29c7b98532788076483037f4bda..963928fc8e29b8abc2026c0b0183ebb07f0de4d1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java -@@ -43,6 +43,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde - return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle()); - } - -+ // Paper start -+ @Override -+ public int getHighestAnger() { -+ return this.getHandle().getAngerManagement().getActiveAnger(null); -+ } -+ // Paper end -+ - @Override - public void increaseAnger(Entity entity, int increase) { - Preconditions.checkArgument(entity != null, "Entity cannot be null"); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index e92355fa2042c4cf15354a11b7058cacbe996f0d..4cf3a374c9ee7c7bcf82e778aa094eb4f8463595 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -61,4 +61,31 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - Entity target = this.getHandle().getLevel().getEntity(entityId); - return (target != null) ? (LivingEntity) target.getBukkitEntity() : null; - } -+ -+ // Paper start -+ @Override -+ public boolean isCharged() { -+ return getHandle().isPowered(); -+ } -+ -+ @Override -+ public int getInvulnerableTicks() { -+ return getHandle().getInvulnerableTicks(); -+ } -+ -+ @Override -+ public void setInvulnerableTicks(int ticks) { -+ getHandle().setInvulnerableTicks(ticks); -+ } -+ -+ @Override -+ public boolean canTravelThroughPortals() { -+ return getHandle().canChangeDimensions(); -+ } -+ -+ @Override -+ public void setCanTravelThroughPortals(boolean value) { -+ getHandle().setCanTravelThroughPortals(value); -+ } -+ // Paper end - } diff --git a/patches/server/0674-Ensure-disconnect-for-book-edit-is-called-on-main.patch b/patches/server/0674-Ensure-disconnect-for-book-edit-is-called-on-main.patch deleted file mode 100644 index 8dfe520cc0..0000000000 --- a/patches/server/0674-Ensure-disconnect-for-book-edit-is-called-on-main.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 22 Jun 2021 19:58:53 +0100 -Subject: [PATCH] Ensure disconnect for book edit is called on main - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b87d5dda2ac847cdc4c83b713568d9e9a37d0c8f..6d3929916da5ea45c5f9f3d0d11b3bd4db9660ef 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1213,7 +1213,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // Paper end - // CraftBukkit start - if (this.lastBookTick + 20 > MinecraftServer.currentTick) { -- this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause -+ server.scheduleOnMain(() -> this.disconnect("Book edited too quickly!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause // Paper - Also ensure this is called on main - return; - } - this.lastBookTick = MinecraftServer.currentTick; diff --git a/patches/server/0674-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0674-Fix-return-value-of-Block-applyBoneMeal-always-being.patch new file mode 100644 index 0000000000..863796fe03 --- /dev/null +++ b/patches/server/0674-Fix-return-value-of-Block-applyBoneMeal-always-being.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 28 Jun 2021 18:16:52 -0700 +Subject: [PATCH] Fix return value of Block#applyBoneMeal always being false + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 1bd1f5993fc296b7d8255cabaf28382c2e258099..b2628b1698ef2a235e7b465f09747cafbb133b7a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -558,7 +558,7 @@ public class CraftBlock implements Block { + } + } + +- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ return result == InteractionResult.CONSUME && (event == null || !event.isCancelled()); // Paper - CONSUME is returned on success server-side (see BoneMealItem.applyBoneMeal and InteractionResult.sidedSuccess(boolean)) + } + + @Override diff --git a/patches/server/0675-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0675-Fix-return-value-of-Block-applyBoneMeal-always-being.patch deleted file mode 100644 index 863796fe03..0000000000 --- a/patches/server/0675-Fix-return-value-of-Block-applyBoneMeal-always-being.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 28 Jun 2021 18:16:52 -0700 -Subject: [PATCH] Fix return value of Block#applyBoneMeal always being false - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 1bd1f5993fc296b7d8255cabaf28382c2e258099..b2628b1698ef2a235e7b465f09747cafbb133b7a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -558,7 +558,7 @@ public class CraftBlock implements Block { - } - } - -- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); -+ return result == InteractionResult.CONSUME && (event == null || !event.isCancelled()); // Paper - CONSUME is returned on success server-side (see BoneMealItem.applyBoneMeal and InteractionResult.sidedSuccess(boolean)) - } - - @Override diff --git a/patches/server/0675-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0675-Use-getChunkIfLoadedImmediately-in-places.patch new file mode 100644 index 0000000000..1e0ae5d1ce --- /dev/null +++ b/patches/server/0675-Use-getChunkIfLoadedImmediately-in-places.patch @@ -0,0 +1,49 @@ +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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 6e9f1f01227a94480043ba3120c77f1ae080ec02..d0c4c9c172c8caa3eaf6c0bf56a8be9f16d8c4e7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -223,7 +223,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI +- return this.chunkSource.getChunk(x, z, false); ++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper + } + + @Override +@@ -1441,7 +1441,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + + for (int l1 = j; l1 <= i1; ++l1) { + for (int i2 = l; i2 <= k1; ++i2) { +- LevelChunk chunk = this.getChunkSource().getChunkNow(l1, i2); ++ LevelChunk chunk = (LevelChunk) this.getChunkIfLoadedImmediately(l1, i2); // Paper + + if (chunk != null) { + for (int j2 = k; j2 <= j1; ++j2) { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index ea2e75b227673f8b0016254f8c52a6721c8adf33..4b7ed29a7a38cf7f8fb488c9c34471fafcdf2f25 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -199,6 +199,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return (CraftServer) Bukkit.getServer(); + } + ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkIfLoaded(chunkX, chunkZ) != null; ++ } ++ // Paper end ++ + public abstract ResourceKey getTypeKey(); + + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor diff --git a/patches/server/0676-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0676-Fix-commands-from-signs-not-firing-command-events.patch new file mode 100644 index 0000000000..c66c6d829d --- /dev/null +++ b/patches/server/0676-Fix-commands-from-signs-not-firing-command-events.patch @@ -0,0 +1,126 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 9 Jul 2021 13:50:48 -0700 +Subject: [PATCH] Fix commands from signs not firing command events + +This patch changes sign command logic so that `run_command` click events: + - are logged to the console + - fire PlayerCommandPreprocessEvent + - work with double-slash commands like `//wand` + - sends failure messages to the player who clicked the sign + +diff --git a/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..01a2bc1feec808790bb93618ce46adb9bea5a9c8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java +@@ -0,0 +1,42 @@ ++package io.papermc.paper.commands; ++ ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.Component; ++import org.bukkit.command.CommandSender; ++ ++import java.util.UUID; ++ ++public class DelegatingCommandSource implements CommandSource { ++ ++ private final CommandSource delegate; ++ ++ public DelegatingCommandSource(CommandSource delegate) { ++ this.delegate = delegate; ++ } ++ ++ @Override ++ public void sendSystemMessage(Component message) { ++ delegate.sendSystemMessage(message); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return delegate.acceptsSuccess(); ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return delegate.acceptsFailure(); ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return delegate.shouldInformAdmins(); ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return delegate.getBukkitSender(wrapper); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +index 831db5ee21938d71e99bf9d17b92a6ca15531740..def4fdd2c7e4f925fa128692a744e5d10ae0203a 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +@@ -40,6 +40,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + private boolean renderMessagedFiltered; + private DyeColor color; + private boolean hasGlowingText; ++ private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger(); + + public SignBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.SIGN, pos, state); +@@ -224,7 +225,17 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + ClickEvent chatclickable = chatmodifier.getClickEvent(); + + if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) { +- player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player), chatclickable.getValue()); ++ // Paper start ++ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue(); ++ if (org.spigotmc.SpigotConfig.logCommands) { ++ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command); ++ } ++ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent(player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) net.minecraft.server.MCUtil.toBukkitBlock(this.level, this.worldPosition).getState()); ++ if (!event.callEvent()) { ++ return false; ++ } ++ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle()), event.getMessage()); ++ // Paper end + } + } + +@@ -260,8 +271,21 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C + String s = player == null ? "Sign" : player.getName().getString(); + Object object = player == null ? Component.literal("Sign") : player.getDisplayName(); + ++ // Paper start - send messages back to the player ++ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this) { ++ @Override ++ public void sendSystemMessage(Component message) { ++ player.sendSystemMessage(message); ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ } : this; ++ // Paper end + // CraftBukkit - this +- return new CommandSourceStack(this, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player); ++ return new CommandSourceStack(commandSource, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player); // Paper + } + + public DyeColor getColor() { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +index d113e54a30db16e2ad955170df6030d15de530d6..26f3a2799e687731d883e7733591f6934479e88d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java +@@ -61,7 +61,7 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command -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/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 6e9f1f01227a94480043ba3120c77f1ae080ec02..d0c4c9c172c8caa3eaf6c0bf56a8be9f16d8c4e7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -223,7 +223,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - @Override public LevelChunk getChunkIfLoaded(int x, int z) { // Paper - this was added in world too but keeping here for NMS ABI -- return this.chunkSource.getChunk(x, z, false); -+ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - } - - @Override -@@ -1441,7 +1441,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - for (int l1 = j; l1 <= i1; ++l1) { - for (int i2 = l; i2 <= k1; ++i2) { -- LevelChunk chunk = this.getChunkSource().getChunkNow(l1, i2); -+ LevelChunk chunk = (LevelChunk) this.getChunkIfLoadedImmediately(l1, i2); // Paper - - if (chunk != null) { - for (int j2 = k; j2 <= j1; ++j2) { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c21274a72dca31c9160ecbcfa7eb42de64e91454..2a4e6c6f732d9cd2567352b7fca2c284b0bb9c1b 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -199,6 +199,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return (CraftServer) Bukkit.getServer(); - } - -+ // Paper start -+ @Override -+ public boolean hasChunk(int chunkX, int chunkZ) { -+ return this.getChunkIfLoaded(chunkX, chunkZ) != null; -+ } -+ // Paper end -+ - public abstract ResourceKey getTypeKey(); - - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor diff --git a/patches/server/0677-Adds-PlayerArmSwingEvent.patch b/patches/server/0677-Adds-PlayerArmSwingEvent.patch new file mode 100644 index 0000000000..a21292ebf2 --- /dev/null +++ b/patches/server/0677-Adds-PlayerArmSwingEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 12 Mar 2021 19:22:21 -0800 +Subject: [PATCH] Adds PlayerArmSwingEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 63bfea707e0664e19726692546dd0ac8a86f8b8f..7d594e23dc7c393f258b16ec5f04e60fc31f91c8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2631,7 +2631,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + // Arm swing animation +- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); ++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), packet.getHand() == InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND); // Paper + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; diff --git a/patches/server/0677-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0677-Fix-commands-from-signs-not-firing-command-events.patch deleted file mode 100644 index c66c6d829d..0000000000 --- a/patches/server/0677-Fix-commands-from-signs-not-firing-command-events.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 9 Jul 2021 13:50:48 -0700 -Subject: [PATCH] Fix commands from signs not firing command events - -This patch changes sign command logic so that `run_command` click events: - - are logged to the console - - fire PlayerCommandPreprocessEvent - - work with double-slash commands like `//wand` - - sends failure messages to the player who clicked the sign - -diff --git a/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java -new file mode 100644 -index 0000000000000000000000000000000000000000..01a2bc1feec808790bb93618ce46adb9bea5a9c8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java -@@ -0,0 +1,42 @@ -+package io.papermc.paper.commands; -+ -+import net.minecraft.commands.CommandSource; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.Component; -+import org.bukkit.command.CommandSender; -+ -+import java.util.UUID; -+ -+public class DelegatingCommandSource implements CommandSource { -+ -+ private final CommandSource delegate; -+ -+ public DelegatingCommandSource(CommandSource delegate) { -+ this.delegate = delegate; -+ } -+ -+ @Override -+ public void sendSystemMessage(Component message) { -+ delegate.sendSystemMessage(message); -+ } -+ -+ @Override -+ public boolean acceptsSuccess() { -+ return delegate.acceptsSuccess(); -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return delegate.acceptsFailure(); -+ } -+ -+ @Override -+ public boolean shouldInformAdmins() { -+ return delegate.shouldInformAdmins(); -+ } -+ -+ @Override -+ public CommandSender getBukkitSender(CommandSourceStack wrapper) { -+ return delegate.getBukkitSender(wrapper); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index 831db5ee21938d71e99bf9d17b92a6ca15531740..def4fdd2c7e4f925fa128692a744e5d10ae0203a 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -40,6 +40,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - private boolean renderMessagedFiltered; - private DyeColor color; - private boolean hasGlowingText; -+ private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger(); - - public SignBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.SIGN, pos, state); -@@ -224,7 +225,17 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - ClickEvent chatclickable = chatmodifier.getClickEvent(); - - if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) { -- player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player), chatclickable.getValue()); -+ // Paper start -+ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue(); -+ if (org.spigotmc.SpigotConfig.logCommands) { -+ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command); -+ } -+ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent(player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) net.minecraft.server.MCUtil.toBukkitBlock(this.level, this.worldPosition).getState()); -+ if (!event.callEvent()) { -+ return false; -+ } -+ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle()), event.getMessage()); -+ // Paper end - } - } - -@@ -260,8 +271,21 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - String s = player == null ? "Sign" : player.getName().getString(); - Object object = player == null ? Component.literal("Sign") : player.getDisplayName(); - -+ // Paper start - send messages back to the player -+ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this) { -+ @Override -+ public void sendSystemMessage(Component message) { -+ player.sendSystemMessage(message); -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return true; -+ } -+ } : this; -+ // Paper end - // CraftBukkit - this -- return new CommandSourceStack(this, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player); -+ return new CommandSourceStack(commandSource, Vec3.atCenterOf(this.worldPosition), Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player); // Paper - } - - public DyeColor getColor() { -diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -index d113e54a30db16e2ad955170df6030d15de530d6..26f3a2799e687731d883e7733591f6934479e88d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java -@@ -61,7 +61,7 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command -Date: Fri, 12 Mar 2021 19:22:21 -0800 -Subject: [PATCH] Adds PlayerArmSwingEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 4e969e37c3ba9ada9ee770a599e52873ada78cdc..74f1221e51e8b0875c4242c9ec2f635aa0827bea 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2627,7 +2627,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - // Arm swing animation -- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); -+ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), packet.getHand() == InteractionHand.MAIN_HAND ? org.bukkit.inventory.EquipmentSlot.HAND : org.bukkit.inventory.EquipmentSlot.OFF_HAND); // Paper - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled()) return; diff --git a/patches/server/0678-Fixes-kick-event-leave-message-not-being-sent.patch b/patches/server/0678-Fixes-kick-event-leave-message-not-being-sent.patch new file mode 100644 index 0000000000..8ca4f8856f --- /dev/null +++ b/patches/server/0678-Fixes-kick-event-leave-message-not-being-sent.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 7 Jul 2021 16:19:41 -0700 +Subject: [PATCH] Fixes kick event leave message not being sent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 9a60cf249e0b9f089b0966c670e6046e7e1ed08a..787f2b23352fbfb66a76ca5fce6de94cc10b30d2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -260,7 +260,6 @@ public class ServerPlayer extends Player { + public boolean supressTrackerForLogin = false; // Paper + public boolean didPlayerJoinEvent = false; // Paper + public Integer clientViewDistance; +- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 7d594e23dc7c393f258b16ec5f04e60fc31f91c8..ebaf6ddcdccf4e2a5836782e58770e2cf1008f9d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -522,7 +522,6 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // Do not kick the player + return; + } +- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent + // Send the possibly modified leave message + final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure + // CraftBukkit end +@@ -531,7 +530,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { + this.connection.disconnect(ichatbasecomponent); + })); +- this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly ++ this.onDisconnect(ichatbasecomponent, event.leaveMessage()); // CraftBukkit - fire quit instantly // Paper - use kick event leave message + this.connection.setReadOnly(); + MinecraftServer minecraftserver = this.server; + Connection networkmanager = this.connection; +@@ -1998,6 +1997,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + @Override + public void onDisconnect(Component reason) { ++ // Paper start ++ this.onDisconnect(reason, null); ++ } ++ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ // Paper end + // CraftBukkit start - Rarely it would send a disconnect line twice + if (this.processedDisconnect) { + return; +@@ -2014,7 +2018,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + this.player.disconnect(); + // Paper start - Adventure +- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); ++ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used + if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); + // Paper end +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index cd7fdf629ac1dd17e83445afdf352c33823cbd25..a34df8db7ae5a1e2fd304e006db7b4af609efb4d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -598,6 +598,11 @@ public abstract class PlayerList { + } + + public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // Paper - return Component ++ // Paper start ++ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); ++ } ++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { ++ // Paper end + ServerLevel worldserver = entityplayer.getLevel(); + + entityplayer.awardStat(Stats.LEAVE_GAME); +@@ -608,7 +613,7 @@ public abstract class PlayerList { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper + } + +- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())), entityplayer.quitReason); // Paper - quit reason ++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - quit reason + if (entityplayer.didPlayerJoinEvent) this.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/0679-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0679-Add-config-for-mobs-immune-to-default-effects.patch new file mode 100644 index 0000000000..66f43a21fd --- /dev/null +++ b/patches/server/0679-Add-config-for-mobs-immune-to-default-effects.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Dec 2020 21:03:02 -0800 +Subject: [PATCH] Add config for mobs immune to default effects + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 4dee04c8012245b94191454943d68ee20fae887a..6e2301f58f103b70b491fd59d5a6657593ac94b7 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1153,7 +1153,7 @@ public abstract class LivingEntity extends Entity { + if (this.getMobType() == MobType.UNDEAD) { + MobEffect mobeffectlist = effect.getEffect(); + +- if (mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) { ++ if ((mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) && this.level.paperConfig().entities.mobEffects.undeadImmuneToCertainEffects) { // Paper + return false; + } + } +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index 92734f767fde60351a179a88350a97b861be0e88..b3e2e834f4f151497bf842796dd8e3a8b5143f1b 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -612,7 +612,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.WITHER && this.level.paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper + } + + private class WitherDoNothingGoal extends Goal { +diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java +index 46779380f44a037d3915f287f40515a9bd31a439..c3085ad5410b41d9c7703b28ca835dbee3f49ee7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java +@@ -133,7 +133,7 @@ public class Spider extends Monster { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.POISON ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.POISON && this.level.paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper + } + + public boolean isClimbing() { +diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +index ea6233cb3ca30864e54d553a5d1071ea9147a868..6449213d717271bcc516e393a78dfe1e5c762d68 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +@@ -123,6 +123,6 @@ public class WitherSkeleton extends AbstractSkeleton { + + @Override + public boolean canBeAffected(MobEffectInstance effect) { +- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); ++ return effect.getEffect() == MobEffects.WITHER && this.level.paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper + } + } diff --git a/patches/server/0679-Fixes-kick-event-leave-message-not-being-sent.patch b/patches/server/0679-Fixes-kick-event-leave-message-not-being-sent.patch deleted file mode 100644 index f8473df413..0000000000 --- a/patches/server/0679-Fixes-kick-event-leave-message-not-being-sent.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 7 Jul 2021 16:19:41 -0700 -Subject: [PATCH] Fixes kick event leave message not being sent - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 9a60cf249e0b9f089b0966c670e6046e7e1ed08a..787f2b23352fbfb66a76ca5fce6de94cc10b30d2 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -260,7 +260,6 @@ public class ServerPlayer extends Player { - public boolean supressTrackerForLogin = false; // Paper - public boolean didPlayerJoinEvent = false; // Paper - public Integer clientViewDistance; -- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent - // CraftBukkit end - public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 47043ebc5054a03ac56d171dc0c8c54bff0230c3..513a0e9c941a3ce7c3123d2476454ccc21dbeeba 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -522,7 +522,6 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // Do not kick the player - return; - } -- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent - // Send the possibly modified leave message - final Component ichatbasecomponent = PaperAdventure.asVanilla(event.reason()); // Paper - Adventure - // CraftBukkit end -@@ -531,7 +530,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { - this.connection.disconnect(ichatbasecomponent); - })); -- this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly -+ this.onDisconnect(ichatbasecomponent, event.leaveMessage()); // CraftBukkit - fire quit instantly // Paper - use kick event leave message - this.connection.setReadOnly(); - MinecraftServer minecraftserver = this.server; - Connection networkmanager = this.connection; -@@ -1997,6 +1996,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - @Override - public void onDisconnect(Component reason) { -+ // Paper start -+ this.onDisconnect(reason, null); -+ } -+ public void onDisconnect(Component reason, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - // CraftBukkit start - Rarely it would send a disconnect line twice - if (this.processedDisconnect) { - return; -@@ -2013,7 +2017,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - this.player.disconnect(); - // Paper start - Adventure -- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player); -+ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used - if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { - this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); - // Paper end -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index cd7fdf629ac1dd17e83445afdf352c33823cbd25..a34df8db7ae5a1e2fd304e006db7b4af609efb4d 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -598,6 +598,11 @@ public abstract class PlayerList { - } - - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // Paper - return Component -+ // Paper start -+ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName()))); -+ } -+ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { -+ // Paper end - ServerLevel worldserver = entityplayer.getLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); -@@ -608,7 +613,7 @@ public abstract class PlayerList { - entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - } - -- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(entityplayer.getScoreboardName())), entityplayer.quitReason); // Paper - quit reason -+ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - quit reason - if (entityplayer.didPlayerJoinEvent) this.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/0680-Add-config-for-mobs-immune-to-default-effects.patch b/patches/server/0680-Add-config-for-mobs-immune-to-default-effects.patch deleted file mode 100644 index 600a8111cd..0000000000 --- a/patches/server/0680-Add-config-for-mobs-immune-to-default-effects.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Dec 2020 21:03:02 -0800 -Subject: [PATCH] Add config for mobs immune to default effects - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 2694f7af35deff5c94929350589b2564f7bbdeff..b0f38933d25397773ac8668f9d1e759d6b43884d 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1153,7 +1153,7 @@ public abstract class LivingEntity extends Entity { - if (this.getMobType() == MobType.UNDEAD) { - MobEffect mobeffectlist = effect.getEffect(); - -- if (mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) { -+ if ((mobeffectlist == MobEffects.REGENERATION || mobeffectlist == MobEffects.POISON) && this.level.paperConfig().entities.mobEffects.undeadImmuneToCertainEffects) { // Paper - return false; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 92734f767fde60351a179a88350a97b861be0e88..b3e2e834f4f151497bf842796dd8e3a8b5143f1b 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -612,7 +612,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.WITHER && this.level.paperConfig().entities.mobEffects.immuneToWitherEffect.wither ? false : super.canBeAffected(effect); // Paper - } - - private class WitherDoNothingGoal extends Goal { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 46779380f44a037d3915f287f40515a9bd31a439..c3085ad5410b41d9c7703b28ca835dbee3f49ee7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -133,7 +133,7 @@ public class Spider extends Monster { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.POISON ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.POISON && this.level.paperConfig().entities.mobEffects.spidersImmuneToPoisonEffect ? false : super.canBeAffected(effect); // Paper - } - - public boolean isClimbing() { -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index ea6233cb3ca30864e54d553a5d1071ea9147a868..6449213d717271bcc516e393a78dfe1e5c762d68 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -123,6 +123,6 @@ public class WitherSkeleton extends AbstractSkeleton { - - @Override - public boolean canBeAffected(MobEffectInstance effect) { -- return effect.getEffect() == MobEffects.WITHER ? false : super.canBeAffected(effect); -+ return effect.getEffect() == MobEffects.WITHER && this.level.paperConfig().entities.mobEffects.immuneToWitherEffect.witherSkeleton ? false : super.canBeAffected(effect); // Paper - } - } diff --git a/patches/server/0680-Fix-incorrect-message-for-outdated-client.patch b/patches/server/0680-Fix-incorrect-message-for-outdated-client.patch new file mode 100644 index 0000000000..7f39718ca5 --- /dev/null +++ b/patches/server/0680-Fix-incorrect-message-for-outdated-client.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: sulu5890 +Date: Sun, 11 Jul 2021 19:34:03 -0500 +Subject: [PATCH] Fix incorrect message for outdated client + + +diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +index b02cbf6bcc57167a1373925f652950e0212dfa4f..08b21f5a7a07f44f8044f56991fb6723cd8f3eea 100644 +--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -80,7 +80,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL + if (packet.getProtocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { + Component ichatmutablecomponent; // Paper - Fix hex colors not working in some kick messages + +- if (packet.getProtocolVersion() < 754) { ++ if (packet.getProtocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Paper - Fix incorrect message for outdated clients + ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages + } else { + ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages diff --git a/patches/server/0681-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0681-Don-t-apply-cramming-damage-to-players.patch new file mode 100644 index 0000000000..28e1719c3c --- /dev/null +++ b/patches/server/0681-Don-t-apply-cramming-damage-to-players.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Sun, 20 Jun 2021 16:35:42 +0100 +Subject: [PATCH] Don't apply cramming damage to players + +It does not make a lot of sense to damage players if they get crammed, + especially as the usecase of teleporting lots of players to the same + location isn't too uncommon and killing all those players isn't + really what one would expect to happen. + +For those who really want it a config option is provided. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 787f2b23352fbfb66a76ca5fce6de94cc10b30d2..56ac440465813a7dab8d166e882e18143a50729f 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1446,7 +1446,7 @@ public class ServerPlayer extends Player { + + @Override + public boolean isInvulnerableTo(DamageSource damageSource) { +- return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || this.getAbilities().invulnerable && damageSource == DamageSource.WITHER; ++ return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || this.getAbilities().invulnerable && damageSource == DamageSource.WITHER || !level.paperConfig().collisions.allowPlayerCrammingDamage && damageSource == DamageSource.CRAMMING; // Paper - disable player cramming + } + + @Override diff --git a/patches/server/0681-Fix-incorrect-message-for-outdated-client.patch b/patches/server/0681-Fix-incorrect-message-for-outdated-client.patch deleted file mode 100644 index 7f39718ca5..0000000000 --- a/patches/server/0681-Fix-incorrect-message-for-outdated-client.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: sulu5890 -Date: Sun, 11 Jul 2021 19:34:03 -0500 -Subject: [PATCH] Fix incorrect message for outdated client - - -diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index b02cbf6bcc57167a1373925f652950e0212dfa4f..08b21f5a7a07f44f8044f56991fb6723cd8f3eea 100644 ---- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -80,7 +80,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL - if (packet.getProtocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { - Component ichatmutablecomponent; // Paper - Fix hex colors not working in some kick messages - -- if (packet.getProtocolVersion() < 754) { -+ if (packet.getProtocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Paper - Fix incorrect message for outdated clients - ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages - } else { - ichatmutablecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromString( java.text.MessageFormat.format( org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName() ) , true )[0]; // Spigot // Paper - Fix hex colors not working in some kick messages diff --git a/patches/server/0682-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0682-Don-t-apply-cramming-damage-to-players.patch deleted file mode 100644 index 28e1719c3c..0000000000 --- a/patches/server/0682-Don-t-apply-cramming-damage-to-players.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Sun, 20 Jun 2021 16:35:42 +0100 -Subject: [PATCH] Don't apply cramming damage to players - -It does not make a lot of sense to damage players if they get crammed, - especially as the usecase of teleporting lots of players to the same - location isn't too uncommon and killing all those players isn't - really what one would expect to happen. - -For those who really want it a config option is provided. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 787f2b23352fbfb66a76ca5fce6de94cc10b30d2..56ac440465813a7dab8d166e882e18143a50729f 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1446,7 +1446,7 @@ public class ServerPlayer extends Player { - - @Override - public boolean isInvulnerableTo(DamageSource damageSource) { -- return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || this.getAbilities().invulnerable && damageSource == DamageSource.WITHER; -+ return super.isInvulnerableTo(damageSource) || this.isChangingDimension() || this.getAbilities().invulnerable && damageSource == DamageSource.WITHER || !level.paperConfig().collisions.allowPlayerCrammingDamage && damageSource == DamageSource.CRAMMING; // Paper - disable player cramming - } - - @Override diff --git a/patches/server/0682-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0682-Rate-options-and-timings-for-sensors-and-behaviors.patch new file mode 100644 index 0000000000..30867e6cf9 --- /dev/null +++ b/patches/server/0682-Rate-options-and-timings-for-sensors-and-behaviors.patch @@ -0,0 +1,134 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Phoenix616 +Date: Mon, 28 Jun 2021 22:38:29 +0100 +Subject: [PATCH] Rate options and timings for sensors and behaviors + +This adds config options to specify the tick rate for sensors + and behaviors of different entity types as well as timings + for those in order to be able to have some metrics as to which + ones might need tweaking. + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 3f540dc05315103ef97fd53628f681c67f7e7c2d..23e564b05ba438924180c91f9b19a60731eedd1b 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -114,6 +114,14 @@ public final class MinecraftTimings { + return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); + } + ++ public static Timing getBehaviorTimings(String type) { ++ return Timings.ofSafe("## Behavior - " + type); ++ } ++ ++ public static Timing getSensorTimings(String type, int rate) { ++ return Timings.ofSafe("## Sensor - " + type + " (Default rate: " + rate + ")"); ++ } ++ + /** + * Get a named timer for the specified tile entity type to track type specific timings. + * @param entity +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 +index ea5dea8fc6af73afb3b9f79bcb254e1644a3b302..eb5a332f0705dd2e5568749a22f2f318d68010d1 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +@@ -13,6 +13,10 @@ public abstract class Behavior { + private long endTimestamp; + private final int minDuration; + private final int maxDuration; ++ // Paper start - configurable behavior tick rate and timings ++ private final String configKey; ++ private final co.aikar.timings.Timing timing; ++ // Paper end + + public Behavior(Map, MemoryStatus> requiredMemoryState) { + this(requiredMemoryState, 60); +@@ -26,6 +30,15 @@ public abstract class Behavior { + this.minDuration = minRunTime; + this.maxDuration = maxRunTime; + this.entryCondition = requiredMemoryState; ++ // Paper start - configurable behavior tick rate and timings ++ String key = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ this.timing = co.aikar.timings.MinecraftTimings.getBehaviorTimings(configKey); ++ // Paper end + } + + public Behavior.Status getStatus() { +@@ -33,11 +46,19 @@ public abstract class Behavior { + } + + public final boolean tryStart(ServerLevel world, E entity, long time) { ++ // Paper start - behavior tick rate ++ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1); ++ if (tickRate > -1 && time < this.endTimestamp + tickRate) { ++ return false; ++ } ++ // Paper end + if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { + this.status = Behavior.Status.RUNNING; + int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); + this.endTimestamp = time + (long)i; ++ this.timing.startTiming(); // Paper - behavior timings + this.start(world, entity, time); ++ this.timing.stopTiming(); // Paper - behavior timings + return true; + } else { + return false; +@@ -48,11 +69,13 @@ public abstract class Behavior { + } + + public final void tickOrStop(ServerLevel world, E entity, long time) { ++ this.timing.startTiming(); // Paper - behavior timings + if (!this.timedOut(time) && this.canStillUse(world, entity, time)) { + this.tick(world, entity, time); + } else { + this.doStop(world, entity, time); + } ++ this.timing.stopTiming(); // Paper - behavior timings + + } + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +index 7970eebbd6935402223e6bba962bb8ba7d861dfd..fcdb9bde8e1605e30dde3e580491522d4b62cdc0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +@@ -19,8 +19,21 @@ public abstract class Sensor { + private static final TargetingConditions ATTACK_TARGET_CONDITIONS_IGNORE_INVISIBILITY_AND_LINE_OF_SIGHT = TargetingConditions.forCombat().range(16.0D).ignoreLineOfSight().ignoreInvisibilityTesting(); + private final int scanRate; + private long timeToTick; ++ // Paper start - configurable sensor tick rate and timings ++ private final String configKey; ++ private final co.aikar.timings.Timing timing; ++ // Paper end + + public Sensor(int senseInterval) { ++ // Paper start - configurable sensor tick rate and timings ++ String key = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()); ++ int lastSeparator = key.lastIndexOf('.'); ++ if (lastSeparator != -1) { ++ key = key.substring(lastSeparator + 1); ++ } ++ this.configKey = key.toLowerCase(java.util.Locale.ROOT); ++ this.timing = co.aikar.timings.MinecraftTimings.getSensorTimings(configKey, senseInterval); ++ // Paper end + this.scanRate = senseInterval; + this.timeToTick = (long)RANDOM.nextInt(senseInterval); + } +@@ -31,8 +44,12 @@ public abstract class Sensor { + + public final void tick(ServerLevel world, E entity) { + if (--this.timeToTick <= 0L) { +- this.timeToTick = (long)this.scanRate; ++ // Paper start - configurable sensor tick rate and timings ++ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); ++ this.timing.startTiming(); ++ // Paper end + this.doTick(world, entity); ++ this.timing.stopTiming(); // Paper - sensor timings + } + + } diff --git a/patches/server/0683-Add-a-bunch-of-missing-forceDrop-toggles.patch b/patches/server/0683-Add-a-bunch-of-missing-forceDrop-toggles.patch new file mode 100644 index 0000000000..34628f8969 --- /dev/null +++ b/patches/server/0683-Add-a-bunch-of-missing-forceDrop-toggles.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 20 Jul 2021 21:25:35 -0700 +Subject: [PATCH] Add a bunch of missing forceDrop toggles + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java +index e47f3092b6bd6b86b577de705db1a575d0952348..490212cfe4e5cea7219eaf4304e14bfac0e6472d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java +@@ -100,7 +100,9 @@ public class WorkAtComposter extends WorkAtPoi { + ItemStack itemstack = inventorysubcontainer.addItem(new ItemStack(Items.BREAD, j)); + + if (!itemstack.isEmpty()) { ++ entity.forceDrops = true; // Paper + entity.spawnAtLocation(itemstack, 0.5F); ++ entity.forceDrops = false; // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java +index 9c07e3f5554b3b9cf2a2c4d9239a72342567d7f1..39c26f486d6392eb0a9b623cdb2161846357174b 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java +@@ -522,7 +522,9 @@ public class Panda extends Animal { + } + + if (!this.level.isClientSide() && this.random.nextInt(700) == 0 && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ++ this.forceDrops = true; // Paper + this.spawnAtLocation((ItemLike) Items.SLIME_BALL); ++ this.forceDrops = false; // Paper + } + + } +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +index d733bcf1049a21009c2548679a51563c15f79523..793576928dad6752dddd86e62d4c0800d8515fc4 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -306,7 +306,9 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + @Override + protected void finishConversion(ServerLevel world) { + PiglinAi.cancelAdmiring(this); ++ this.forceDrops = true; // Paper + this.inventory.removeAllItems().forEach(this::spawnAtLocation); ++ this.forceDrops = false; // Paper + super.finishConversion(world); + } + diff --git a/patches/server/0683-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0683-Rate-options-and-timings-for-sensors-and-behaviors.patch deleted file mode 100644 index 30867e6cf9..0000000000 --- a/patches/server/0683-Rate-options-and-timings-for-sensors-and-behaviors.patch +++ /dev/null @@ -1,134 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 -Date: Mon, 28 Jun 2021 22:38:29 +0100 -Subject: [PATCH] Rate options and timings for sensors and behaviors - -This adds config options to specify the tick rate for sensors - and behaviors of different entity types as well as timings - for those in order to be able to have some metrics as to which - ones might need tweaking. - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index 3f540dc05315103ef97fd53628f681c67f7e7c2d..23e564b05ba438924180c91f9b19a60731eedd1b 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -114,6 +114,14 @@ public final class MinecraftTimings { - return Timings.ofSafe("Minecraft", "## tickEntity - " + entityType + " - " + type, tickEntityTimer); - } - -+ public static Timing getBehaviorTimings(String type) { -+ return Timings.ofSafe("## Behavior - " + type); -+ } -+ -+ public static Timing getSensorTimings(String type, int rate) { -+ return Timings.ofSafe("## Sensor - " + type + " (Default rate: " + rate + ")"); -+ } -+ - /** - * Get a named timer for the specified tile entity type to track type specific timings. - * @param entity -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 -index ea5dea8fc6af73afb3b9f79bcb254e1644a3b302..eb5a332f0705dd2e5568749a22f2f318d68010d1 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java -@@ -13,6 +13,10 @@ public abstract class Behavior { - private long endTimestamp; - private final int minDuration; - private final int maxDuration; -+ // Paper start - configurable behavior tick rate and timings -+ private final String configKey; -+ private final co.aikar.timings.Timing timing; -+ // Paper end - - public Behavior(Map, MemoryStatus> requiredMemoryState) { - this(requiredMemoryState, 60); -@@ -26,6 +30,15 @@ public abstract class Behavior { - this.minDuration = minRunTime; - this.maxDuration = maxRunTime; - this.entryCondition = requiredMemoryState; -+ // Paper start - configurable behavior tick rate and timings -+ String key = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()); -+ int lastSeparator = key.lastIndexOf('.'); -+ if (lastSeparator != -1) { -+ key = key.substring(lastSeparator + 1); -+ } -+ this.configKey = key.toLowerCase(java.util.Locale.ROOT); -+ this.timing = co.aikar.timings.MinecraftTimings.getBehaviorTimings(configKey); -+ // Paper end - } - - public Behavior.Status getStatus() { -@@ -33,11 +46,19 @@ public abstract class Behavior { - } - - public final boolean tryStart(ServerLevel world, E entity, long time) { -+ // Paper start - behavior tick rate -+ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1); -+ if (tickRate > -1 && time < this.endTimestamp + tickRate) { -+ return false; -+ } -+ // Paper end - if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) { - this.status = Behavior.Status.RUNNING; - int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); - this.endTimestamp = time + (long)i; -+ this.timing.startTiming(); // Paper - behavior timings - this.start(world, entity, time); -+ this.timing.stopTiming(); // Paper - behavior timings - return true; - } else { - return false; -@@ -48,11 +69,13 @@ public abstract class Behavior { - } - - public final void tickOrStop(ServerLevel world, E entity, long time) { -+ this.timing.startTiming(); // Paper - behavior timings - if (!this.timedOut(time) && this.canStillUse(world, entity, time)) { - this.tick(world, entity, time); - } else { - this.doStop(world, entity, time); - } -+ this.timing.stopTiming(); // Paper - behavior timings - - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -index 7970eebbd6935402223e6bba962bb8ba7d861dfd..fcdb9bde8e1605e30dde3e580491522d4b62cdc0 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java -@@ -19,8 +19,21 @@ public abstract class Sensor { - private static final TargetingConditions ATTACK_TARGET_CONDITIONS_IGNORE_INVISIBILITY_AND_LINE_OF_SIGHT = TargetingConditions.forCombat().range(16.0D).ignoreLineOfSight().ignoreInvisibilityTesting(); - private final int scanRate; - private long timeToTick; -+ // Paper start - configurable sensor tick rate and timings -+ private final String configKey; -+ private final co.aikar.timings.Timing timing; -+ // Paper end - - public Sensor(int senseInterval) { -+ // Paper start - configurable sensor tick rate and timings -+ String key = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()); -+ int lastSeparator = key.lastIndexOf('.'); -+ if (lastSeparator != -1) { -+ key = key.substring(lastSeparator + 1); -+ } -+ this.configKey = key.toLowerCase(java.util.Locale.ROOT); -+ this.timing = co.aikar.timings.MinecraftTimings.getSensorTimings(configKey, senseInterval); -+ // Paper end - this.scanRate = senseInterval; - this.timeToTick = (long)RANDOM.nextInt(senseInterval); - } -@@ -31,8 +44,12 @@ public abstract class Sensor { - - public final void tick(ServerLevel world, E entity) { - if (--this.timeToTick <= 0L) { -- this.timeToTick = (long)this.scanRate; -+ // Paper start - configurable sensor tick rate and timings -+ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); -+ this.timing.startTiming(); -+ // Paper end - this.doTick(world, entity); -+ this.timing.stopTiming(); // Paper - sensor timings - } - - } diff --git a/patches/server/0684-Add-a-bunch-of-missing-forceDrop-toggles.patch b/patches/server/0684-Add-a-bunch-of-missing-forceDrop-toggles.patch deleted file mode 100644 index 34628f8969..0000000000 --- a/patches/server/0684-Add-a-bunch-of-missing-forceDrop-toggles.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 20 Jul 2021 21:25:35 -0700 -Subject: [PATCH] Add a bunch of missing forceDrop toggles - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -index e47f3092b6bd6b86b577de705db1a575d0952348..490212cfe4e5cea7219eaf4304e14bfac0e6472d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java -@@ -100,7 +100,9 @@ public class WorkAtComposter extends WorkAtPoi { - ItemStack itemstack = inventorysubcontainer.addItem(new ItemStack(Items.BREAD, j)); - - if (!itemstack.isEmpty()) { -+ entity.forceDrops = true; // Paper - entity.spawnAtLocation(itemstack, 0.5F); -+ entity.forceDrops = false; // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index 9c07e3f5554b3b9cf2a2c4d9239a72342567d7f1..39c26f486d6392eb0a9b623cdb2161846357174b 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -522,7 +522,9 @@ public class Panda extends Animal { - } - - if (!this.level.isClientSide() && this.random.nextInt(700) == 0 && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -+ this.forceDrops = true; // Paper - this.spawnAtLocation((ItemLike) Items.SLIME_BALL); -+ this.forceDrops = false; // Paper - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -index d733bcf1049a21009c2548679a51563c15f79523..793576928dad6752dddd86e62d4c0800d8515fc4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -@@ -306,7 +306,9 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - @Override - protected void finishConversion(ServerLevel world) { - PiglinAi.cancelAdmiring(this); -+ this.forceDrops = true; // Paper - this.inventory.removeAllItems().forEach(this::spawnAtLocation); -+ this.forceDrops = false; // Paper - super.finishConversion(world); - } - diff --git a/patches/server/0684-Stinger-API.patch b/patches/server/0684-Stinger-API.patch new file mode 100644 index 0000000000..bfa434970f --- /dev/null +++ b/patches/server/0684-Stinger-API.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 22 Jun 2021 23:15:44 -0400 +Subject: [PATCH] Stinger API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index 310dcf72f24fcbb86bc5b118536a8ebfc084eb7e..1994dd3c272395a27474ec1b37a924a24fc50fd6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -323,7 +323,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + Preconditions.checkArgument(count >= 0, "New arrow amount must be >= 0"); + this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_ARROW_COUNT_ID, count); + } ++ // Paper Start - Bee Stinger API ++ @Override ++ public int getBeeStingerCooldown() { ++ return getHandle().removeStingerTime; ++ } ++ ++ @Override ++ public void setBeeStingerCooldown(int ticks) { ++ getHandle().removeStingerTime = ticks; ++ } + ++ @Override ++ public int getBeeStingersInBody() { ++ return getHandle().getStingerCount(); ++ } ++ ++ @Override ++ public void setBeeStingersInBody(int count) { ++ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); ++ getHandle().setStingerCount(count); ++ } ++ // Paper End - Bee Stinger API + @Override + public void damage(double amount) { + this.damage(amount, null); diff --git a/patches/server/0685-Fix-incosistency-issue-with-empty-map-items-in-CB.patch b/patches/server/0685-Fix-incosistency-issue-with-empty-map-items-in-CB.patch new file mode 100644 index 0000000000..3f1afe6bf8 --- /dev/null +++ b/patches/server/0685-Fix-incosistency-issue-with-empty-map-items-in-CB.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sun, 1 Aug 2021 09:49:06 +0100 +Subject: [PATCH] Fix incosistency issue with empty map items in CB + + +diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java +index d407cf849a31a7a77fda07aa687ebb254f43d6ab..45a7e01288f780cf8a812d8e0ae12c4fb79d79e1 100644 +--- a/src/main/java/net/minecraft/world/item/MapItem.java ++++ b/src/main/java/net/minecraft/world/item/MapItem.java +@@ -71,7 +71,7 @@ public class MapItem extends ComplexItem { + public static Integer getMapId(ItemStack stack) { + CompoundTag nbttagcompound = stack.getTag(); + +- return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : -1; // CraftBukkit - make new maps for no tag ++ return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : null; // CraftBukkit - make new maps for no tag // Paper - don't return invalid ID + } + + public static int createNewSavedData(Level world, int x, int z, int scale, boolean showIcons, boolean unlimitedTracking, ResourceKey dimension) { +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +index 6f16f12b4cb9b53878416f1cea532548c418f518..e802623e2ef5fb8c423429335ee7dbabc45a1b74 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java +@@ -138,6 +138,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { + + @Override + public int getMapId() { ++ Preconditions.checkState(this.hasMapView(), "Item does not have map associated - check hasMapView() first!"); // Paper - more friendly message + return this.mapId; + } + diff --git a/patches/server/0685-Stinger-API.patch b/patches/server/0685-Stinger-API.patch deleted file mode 100644 index 33f8959315..0000000000 --- a/patches/server/0685-Stinger-API.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:15:44 -0400 -Subject: [PATCH] Stinger API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index ca176b9331345e343c19a02b6ba2ea886d20962d..c022751e3b45469cc0ad6732e2d6ff08918bafa4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -321,7 +321,28 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - Preconditions.checkArgument(count >= 0, "New arrow amount must be >= 0"); - this.getHandle().getEntityData().set(net.minecraft.world.entity.LivingEntity.DATA_ARROW_COUNT_ID, count); - } -+ // Paper Start - Bee Stinger API -+ @Override -+ public int getBeeStingerCooldown() { -+ return getHandle().removeStingerTime; -+ } -+ -+ @Override -+ public void setBeeStingerCooldown(int ticks) { -+ getHandle().removeStingerTime = ticks; -+ } - -+ @Override -+ public int getBeeStingersInBody() { -+ return getHandle().getStingerCount(); -+ } -+ -+ @Override -+ public void setBeeStingersInBody(int count) { -+ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0"); -+ getHandle().setStingerCount(count); -+ } -+ // Paper End - Bee Stinger API - @Override - public void damage(double amount) { - this.damage(amount, null); diff --git a/patches/server/0686-Add-System.out-err-catcher.patch b/patches/server/0686-Add-System.out-err-catcher.patch new file mode 100644 index 0000000000..e2587514e8 --- /dev/null +++ b/patches/server/0686-Add-System.out-err-catcher.patch @@ -0,0 +1,118 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: underscore11code +Date: Fri, 23 Jul 2021 23:01:42 -0700 +Subject: [PATCH] Add System.out/err catcher + + +diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..76d0d00cd6742991e3f3ec827a75ee87d856b6c9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +@@ -0,0 +1,94 @@ ++package io.papermc.paper.logging; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.OutputStream; ++import java.io.PrintStream; ++import java.util.Objects; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.concurrent.ConcurrentMap; ++import java.util.concurrent.TimeUnit; ++import java.util.logging.Level; ++ ++public final class SysoutCatcher { ++ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); ++ // Nanoseconds between nag at most; if interval is caught first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( ++ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); ++ // Count since last nag; if timeout is first, this is reset. ++ // <= 0 for disabling. ++ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); ++ ++ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. ++ // The records are also pretty small, so let's just go for a size of 64 to start... ++ // ++ // Content: Plugin name => nag object ++ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. ++ // Why not clean on reload?: Effort. ++ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); ++ ++ public SysoutCatcher() { ++ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); ++ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); ++ } ++ ++ private final class WrappedOutStream extends PrintStream { ++ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); ++ private final Level level; ++ private final String prefix; ++ ++ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { ++ super(out); ++ this.level = level; ++ this.prefix = prefix; ++ } ++ ++ @Override ++ public void println(@Nullable final String line) { ++ final Class clazz = STACK_WALKER.getCallerClass(); ++ try { ++ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); ++ ++ // Instead of just printing the message, send it to the plugin's logger ++ plugin.getLogger().log(this.level, this.prefix + line); ++ ++ if (SysoutCatcher.SUPPRESS_NAGS) { ++ return; ++ } ++ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { ++ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); ++ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 ++ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE ++ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); ++ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 ++ && (nagRecord.messagesSinceNag == Long.MIN_VALUE ++ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); ++ if (!hasMessagesPassed && !hasTimePassed) { ++ return; ++ } ++ nagRecord.lastNagTimestamp = System.nanoTime(); ++ nagRecord.messagesSinceNag = 0; ++ } ++ Bukkit.getLogger().warning( ++ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " ++ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", ++ plugin.getDescription().getAuthors(), ++ plugin.getName()) ++ ); ++ } catch (final IllegalArgumentException | IllegalStateException e) { ++ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc ++ // Just print out normally, with some added information ++ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); ++ } ++ } ++ } ++ ++ private static class PluginNag { ++ private long lastNagTimestamp = Long.MIN_VALUE; ++ private long messagesSinceNag = Long.MIN_VALUE; ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index dd43414dde55a5b447f9f51e4e5eef12fbbcfbde..e7863390c8394a6afcfa25099b4ce49c5e010f13 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -284,6 +284,7 @@ public final class CraftServer implements Server { + public int reloadCount; + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings ++ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0686-Fix-incosistency-issue-with-empty-map-items-in-CB.patch b/patches/server/0686-Fix-incosistency-issue-with-empty-map-items-in-CB.patch deleted file mode 100644 index a58750b226..0000000000 --- a/patches/server/0686-Fix-incosistency-issue-with-empty-map-items-in-CB.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sun, 1 Aug 2021 09:49:06 +0100 -Subject: [PATCH] Fix incosistency issue with empty map items in CB - - -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index d407cf849a31a7a77fda07aa687ebb254f43d6ab..45a7e01288f780cf8a812d8e0ae12c4fb79d79e1 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -71,7 +71,7 @@ public class MapItem extends ComplexItem { - public static Integer getMapId(ItemStack stack) { - CompoundTag nbttagcompound = stack.getTag(); - -- return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : -1; // CraftBukkit - make new maps for no tag -+ return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : null; // CraftBukkit - make new maps for no tag // Paper - don't return invalid ID - } - - public static int createNewSavedData(Level world, int x, int z, int scale, boolean showIcons, boolean unlimitedTracking, ResourceKey dimension) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java -index 96ab7d53f4e089c7666872f9fd0f09283259a726..8e634ccb91b58000412c572903e57d30ddb2caba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java -@@ -144,6 +144,7 @@ class CraftMetaMap extends CraftMetaItem implements MapMeta { - - @Override - public int getMapId() { -+ Preconditions.checkState(this.hasMapView(), "Item does not have map associated - check hasMapView() first!"); // Paper - more friendly message - return this.mapId; - } - diff --git a/patches/server/0687-Add-System.out-err-catcher.patch b/patches/server/0687-Add-System.out-err-catcher.patch deleted file mode 100644 index e2587514e8..0000000000 --- a/patches/server/0687-Add-System.out-err-catcher.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: underscore11code -Date: Fri, 23 Jul 2021 23:01:42 -0700 -Subject: [PATCH] Add System.out/err catcher - - -diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..76d0d00cd6742991e3f3ec827a75ee87d856b6c9 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java -@@ -0,0 +1,94 @@ -+package io.papermc.paper.logging; -+ -+import org.bukkit.Bukkit; -+import org.bukkit.plugin.java.JavaPlugin; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.io.OutputStream; -+import java.io.PrintStream; -+import java.util.Objects; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.ConcurrentMap; -+import java.util.concurrent.TimeUnit; -+import java.util.logging.Level; -+ -+public final class SysoutCatcher { -+ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags"); -+ // Nanoseconds between nag at most; if interval is caught first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( -+ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L))); -+ // Count since last nag; if timeout is first, this is reset. -+ // <= 0 for disabling. -+ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L); -+ -+ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis. -+ // The records are also pretty small, so let's just go for a size of 64 to start... -+ // -+ // Content: Plugin name => nag object -+ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability. -+ // Why not clean on reload?: Effort. -+ private final ConcurrentMap nagRecords = new ConcurrentHashMap<>(64); -+ -+ public SysoutCatcher() { -+ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] ")); -+ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] ")); -+ } -+ -+ private final class WrappedOutStream extends PrintStream { -+ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); -+ private final Level level; -+ private final String prefix; -+ -+ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) { -+ super(out); -+ this.level = level; -+ this.prefix = prefix; -+ } -+ -+ @Override -+ public void println(@Nullable final String line) { -+ final Class clazz = STACK_WALKER.getCallerClass(); -+ try { -+ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); -+ -+ // Instead of just printing the message, send it to the plugin's logger -+ plugin.getLogger().log(this.level, this.prefix + line); -+ -+ if (SysoutCatcher.SUPPRESS_NAGS) { -+ return; -+ } -+ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { -+ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag()); -+ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0 -+ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE -+ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime()); -+ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0 -+ && (nagRecord.messagesSinceNag == Long.MIN_VALUE -+ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL); -+ if (!hasMessagesPassed && !hasTimePassed) { -+ return; -+ } -+ nagRecord.lastNagTimestamp = System.nanoTime(); -+ nagRecord.messagesSinceNag = 0; -+ } -+ Bukkit.getLogger().warning( -+ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. " -+ + "Please use your plugin's logger instead (JavaPlugin#getLogger).", -+ plugin.getDescription().getAuthors(), -+ plugin.getName()) -+ ); -+ } catch (final IllegalArgumentException | IllegalStateException e) { -+ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc -+ // Just print out normally, with some added information -+ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line)); -+ } -+ } -+ } -+ -+ private static class PluginNag { -+ private long lastNagTimestamp = Long.MIN_VALUE; -+ private long messagesSinceNag = Long.MIN_VALUE; -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index dd43414dde55a5b447f9f51e4e5eef12fbbcfbde..e7863390c8394a6afcfa25099b4ce49c5e010f13 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -284,6 +284,7 @@ public final class CraftServer implements Server { - public int reloadCount; - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings -+ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); diff --git a/patches/server/0687-Fix-test-not-bootstrapping.patch b/patches/server/0687-Fix-test-not-bootstrapping.patch new file mode 100644 index 0000000000..5d0466c3b3 --- /dev/null +++ b/patches/server/0687-Fix-test-not-bootstrapping.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Mon, 2 Aug 2021 08:52:21 +0200 +Subject: [PATCH] Fix test not bootstrapping + +Signed-off-by: Mariell Hoversholm + +diff --git a/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java b/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java +index 19777eaca02640f220626940f5384563777dd74a..9873addca9e7a2a7eaa30a1c1cd332260ca07b35 100644 +--- a/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java ++++ b/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java +@@ -5,10 +5,11 @@ import net.minecraft.world.item.Item; + import net.minecraft.world.item.enchantment.EnchantmentCategory; + import org.bukkit.Material; + import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.support.AbstractTestingBase; + import org.junit.Assert; + import org.junit.Test; + +-public class EnchantmentTargetTest { ++public class EnchantmentTargetTest extends AbstractTestingBase { // Paper + + @Test + public void test() { diff --git a/patches/server/0688-Fix-test-not-bootstrapping.patch b/patches/server/0688-Fix-test-not-bootstrapping.patch deleted file mode 100644 index 5d0466c3b3..0000000000 --- a/patches/server/0688-Fix-test-not-bootstrapping.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Mon, 2 Aug 2021 08:52:21 +0200 -Subject: [PATCH] Fix test not bootstrapping - -Signed-off-by: Mariell Hoversholm - -diff --git a/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java b/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java -index 19777eaca02640f220626940f5384563777dd74a..9873addca9e7a2a7eaa30a1c1cd332260ca07b35 100644 ---- a/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java -+++ b/src/test/java/org/bukkit/enchantments/EnchantmentTargetTest.java -@@ -5,10 +5,11 @@ import net.minecraft.world.item.Item; - import net.minecraft.world.item.enchantment.EnchantmentCategory; - import org.bukkit.Material; - import org.bukkit.craftbukkit.util.CraftMagicNumbers; -+import org.bukkit.support.AbstractTestingBase; - import org.junit.Assert; - import org.junit.Test; - --public class EnchantmentTargetTest { -+public class EnchantmentTargetTest extends AbstractTestingBase { // Paper - - @Test - public void test() { diff --git a/patches/server/0688-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch b/patches/server/0688-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch new file mode 100644 index 0000000000..10cd90749d --- /dev/null +++ b/patches/server/0688-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch @@ -0,0 +1,246 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SirYwell +Date: Sat, 10 Jul 2021 11:12:30 +0200 +Subject: [PATCH] Rewrite LogEvents to contain the source jars in stack traces + + +diff --git a/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java b/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ffd1befe64c6c3036c22e05ed1c44808d64bd28 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java +@@ -0,0 +1,130 @@ ++package io.papermc.paper.logging; ++ ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.Marker; ++import org.apache.logging.log4j.ThreadContext; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.impl.ThrowableProxy; ++import org.apache.logging.log4j.core.time.Instant; ++import org.apache.logging.log4j.message.Message; ++import org.apache.logging.log4j.util.ReadOnlyStringMap; ++ ++import java.util.Map; ++ ++public class DelegateLogEvent implements LogEvent { ++ private final LogEvent original; ++ ++ protected DelegateLogEvent(LogEvent original) { ++ this.original = original; ++ } ++ ++ @Override ++ public LogEvent toImmutable() { ++ return this.original.toImmutable(); ++ } ++ ++ @Override ++ public Map getContextMap() { ++ return this.original.getContextMap(); ++ } ++ ++ @Override ++ public ReadOnlyStringMap getContextData() { ++ return this.original.getContextData(); ++ } ++ ++ @Override ++ public ThreadContext.ContextStack getContextStack() { ++ return this.original.getContextStack(); ++ } ++ ++ @Override ++ public String getLoggerFqcn() { ++ return this.original.getLoggerFqcn(); ++ } ++ ++ @Override ++ public Level getLevel() { ++ return this.original.getLevel(); ++ } ++ ++ @Override ++ public String getLoggerName() { ++ return this.original.getLoggerName(); ++ } ++ ++ @Override ++ public Marker getMarker() { ++ return this.original.getMarker(); ++ } ++ ++ @Override ++ public Message getMessage() { ++ return this.original.getMessage(); ++ } ++ ++ @Override ++ public long getTimeMillis() { ++ return this.original.getTimeMillis(); ++ } ++ ++ @Override ++ public Instant getInstant() { ++ return this.original.getInstant(); ++ } ++ ++ @Override ++ public StackTraceElement getSource() { ++ return this.original.getSource(); ++ } ++ ++ @Override ++ public String getThreadName() { ++ return this.original.getThreadName(); ++ } ++ ++ @Override ++ public long getThreadId() { ++ return this.original.getThreadId(); ++ } ++ ++ @Override ++ public int getThreadPriority() { ++ return this.original.getThreadPriority(); ++ } ++ ++ @Override ++ public Throwable getThrown() { ++ return this.original.getThrown(); ++ } ++ ++ @Override ++ public ThrowableProxy getThrownProxy() { ++ return this.original.getThrownProxy(); ++ } ++ ++ @Override ++ public boolean isEndOfBatch() { ++ return this.original.isEndOfBatch(); ++ } ++ ++ @Override ++ public boolean isIncludeLocation() { ++ return this.original.isIncludeLocation(); ++ } ++ ++ @Override ++ public void setEndOfBatch(boolean endOfBatch) { ++ this.original.setEndOfBatch(endOfBatch); ++ } ++ ++ @Override ++ public void setIncludeLocation(boolean locationRequired) { ++ this.original.setIncludeLocation(locationRequired); ++ } ++ ++ @Override ++ public long getNanoTime() { ++ return this.original.getNanoTime(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java b/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..558427c65b4051923f73d15d85ee519be005060a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java +@@ -0,0 +1,48 @@ ++package io.papermc.paper.logging; ++ ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.impl.ExtendedClassInfo; ++import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; ++import org.apache.logging.log4j.core.impl.ThrowableProxy; ++ ++public class ExtraClassInfoLogEvent extends DelegateLogEvent { ++ ++ private boolean fixed; ++ ++ public ExtraClassInfoLogEvent(LogEvent original) { ++ super(original); ++ } ++ ++ @Override ++ public ThrowableProxy getThrownProxy() { ++ if (fixed) { ++ return super.getThrownProxy(); ++ } ++ rewriteStackTrace(super.getThrownProxy()); ++ fixed = true; ++ return super.getThrownProxy(); ++ } ++ ++ private void rewriteStackTrace(ThrowableProxy throwable) { ++ ExtendedStackTraceElement[] stackTrace = throwable.getExtendedStackTrace(); ++ for (int i = 0; i < stackTrace.length; i++) { ++ ExtendedClassInfo classInfo = stackTrace[i].getExtraClassInfo(); ++ if (classInfo.getLocation().equals("?")) { ++ StackTraceElement element = stackTrace[i].getStackTraceElement(); ++ String classLoaderName = element.getClassLoaderName(); ++ if (classLoaderName != null) { ++ stackTrace[i] = new ExtendedStackTraceElement(element, ++ new ExtendedClassInfo(classInfo.getExact(), classLoaderName, "?")); ++ } ++ } ++ } ++ if (throwable.getCauseProxy() != null) { ++ rewriteStackTrace(throwable.getCauseProxy()); ++ } ++ if (throwable.getSuppressedProxies() != null) { ++ for (ThrowableProxy proxy : throwable.getSuppressedProxies()) { ++ rewriteStackTrace(proxy); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java b/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..34734bb969a1a74c7a4f9c17d40ebf007ad5d701 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java +@@ -0,0 +1,29 @@ ++package io.papermc.paper.logging; ++ ++import org.apache.logging.log4j.core.Core; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.config.plugins.PluginFactory; ++import org.jetbrains.annotations.NotNull; ++ ++@Plugin( ++ name = "ExtraClassInfoRewritePolicy", ++ category = Core.CATEGORY_NAME, ++ elementType = "rewritePolicy", ++ printObject = true ++) ++public final class ExtraClassInfoRewritePolicy implements RewritePolicy { ++ @Override ++ public LogEvent rewrite(LogEvent source) { ++ if (source.getThrown() != null) { ++ return new ExtraClassInfoLogEvent(source); ++ } ++ return source; ++ } ++ ++ @PluginFactory ++ public static @NotNull ExtraClassInfoRewritePolicy createPolicy() { ++ return new ExtraClassInfoRewritePolicy(); ++ } ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 2e421eaac80cf251b32e0bb504dd54a73edf4986..74ccc67e3c12dc5182602fb691ef3ddeb5b53280 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -34,6 +34,10 @@ + + + ++ ++ ++ ++ + + + diff --git a/patches/server/0689-Improve-boat-collision-performance.patch b/patches/server/0689-Improve-boat-collision-performance.patch new file mode 100644 index 0000000000..f6283ceb9a --- /dev/null +++ b/patches/server/0689-Improve-boat-collision-performance.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 2 Aug 2021 10:10:40 +0200 +Subject: [PATCH] Improve boat collision performance + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 4fce18c52c8144460ebf0c1e336dce712d796cc6..384ddb03af26ae360fd22e2e231d9d14d6ad0865 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -112,6 +112,7 @@ public class Util { + }).findFirst().orElseThrow(() -> { + return new IllegalStateException("No jar file system provider found"); + }); ++ public static final double COLLISION_EPSILON = 1.0E-7; // Paper + private static Consumer thePauser = (message) -> { + }; + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 6e2301f58f103b70b491fd59d5a6657593ac94b7..76ef3f561e3f8e0c0f9732feb64aacca93b57431 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -1341,7 +1341,7 @@ public abstract class LivingEntity extends Entity { + if (!source.isProjectile()) { + Entity entity = source.getDirectEntity(); + +- if (entity instanceof LivingEntity) { ++ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper + this.blockUsingShield((LivingEntity) entity); + } + } +@@ -1449,11 +1449,12 @@ public abstract class LivingEntity extends Entity { + } + + if (entity1 != null) { +- double d0 = entity1.getX() - this.getX(); ++ final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper ++ double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper + + double d1; + +- for (d1 = entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { ++ for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper + d0 = (Math.random() - Math.random()) * 0.01D; + } + +@@ -2163,7 +2164,7 @@ public abstract class LivingEntity extends Entity { + this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); + Entity entity = damagesource.getDirectEntity(); + +- if (entity instanceof LivingEntity) { ++ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper + this.blockUsingShield((LivingEntity) entity); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +index 5641a7b5c5e3d93cddabd91703c6f001700c5869..29da8a42406feccf7932097b07b1d32a38fa96b7 100644 +--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java ++++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +@@ -691,8 +691,8 @@ public class Boat extends Entity { + this.invFriction = 0.05F; + if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) { + this.waterLevel = this.getY(1.0D); +- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ()); +- this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); ++ this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper ++ this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); // Paper + this.lastYd = 0.0D; + this.status = Boat.Status.IN_WATER; + } else { diff --git a/patches/server/0689-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch b/patches/server/0689-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch deleted file mode 100644 index 10cd90749d..0000000000 --- a/patches/server/0689-Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch +++ /dev/null @@ -1,246 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SirYwell -Date: Sat, 10 Jul 2021 11:12:30 +0200 -Subject: [PATCH] Rewrite LogEvents to contain the source jars in stack traces - - -diff --git a/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java b/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6ffd1befe64c6c3036c22e05ed1c44808d64bd28 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/DelegateLogEvent.java -@@ -0,0 +1,130 @@ -+package io.papermc.paper.logging; -+ -+import org.apache.logging.log4j.Level; -+import org.apache.logging.log4j.Marker; -+import org.apache.logging.log4j.ThreadContext; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.impl.ThrowableProxy; -+import org.apache.logging.log4j.core.time.Instant; -+import org.apache.logging.log4j.message.Message; -+import org.apache.logging.log4j.util.ReadOnlyStringMap; -+ -+import java.util.Map; -+ -+public class DelegateLogEvent implements LogEvent { -+ private final LogEvent original; -+ -+ protected DelegateLogEvent(LogEvent original) { -+ this.original = original; -+ } -+ -+ @Override -+ public LogEvent toImmutable() { -+ return this.original.toImmutable(); -+ } -+ -+ @Override -+ public Map getContextMap() { -+ return this.original.getContextMap(); -+ } -+ -+ @Override -+ public ReadOnlyStringMap getContextData() { -+ return this.original.getContextData(); -+ } -+ -+ @Override -+ public ThreadContext.ContextStack getContextStack() { -+ return this.original.getContextStack(); -+ } -+ -+ @Override -+ public String getLoggerFqcn() { -+ return this.original.getLoggerFqcn(); -+ } -+ -+ @Override -+ public Level getLevel() { -+ return this.original.getLevel(); -+ } -+ -+ @Override -+ public String getLoggerName() { -+ return this.original.getLoggerName(); -+ } -+ -+ @Override -+ public Marker getMarker() { -+ return this.original.getMarker(); -+ } -+ -+ @Override -+ public Message getMessage() { -+ return this.original.getMessage(); -+ } -+ -+ @Override -+ public long getTimeMillis() { -+ return this.original.getTimeMillis(); -+ } -+ -+ @Override -+ public Instant getInstant() { -+ return this.original.getInstant(); -+ } -+ -+ @Override -+ public StackTraceElement getSource() { -+ return this.original.getSource(); -+ } -+ -+ @Override -+ public String getThreadName() { -+ return this.original.getThreadName(); -+ } -+ -+ @Override -+ public long getThreadId() { -+ return this.original.getThreadId(); -+ } -+ -+ @Override -+ public int getThreadPriority() { -+ return this.original.getThreadPriority(); -+ } -+ -+ @Override -+ public Throwable getThrown() { -+ return this.original.getThrown(); -+ } -+ -+ @Override -+ public ThrowableProxy getThrownProxy() { -+ return this.original.getThrownProxy(); -+ } -+ -+ @Override -+ public boolean isEndOfBatch() { -+ return this.original.isEndOfBatch(); -+ } -+ -+ @Override -+ public boolean isIncludeLocation() { -+ return this.original.isIncludeLocation(); -+ } -+ -+ @Override -+ public void setEndOfBatch(boolean endOfBatch) { -+ this.original.setEndOfBatch(endOfBatch); -+ } -+ -+ @Override -+ public void setIncludeLocation(boolean locationRequired) { -+ this.original.setIncludeLocation(locationRequired); -+ } -+ -+ @Override -+ public long getNanoTime() { -+ return this.original.getNanoTime(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java b/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..558427c65b4051923f73d15d85ee519be005060a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java -@@ -0,0 +1,48 @@ -+package io.papermc.paper.logging; -+ -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.impl.ExtendedClassInfo; -+import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; -+import org.apache.logging.log4j.core.impl.ThrowableProxy; -+ -+public class ExtraClassInfoLogEvent extends DelegateLogEvent { -+ -+ private boolean fixed; -+ -+ public ExtraClassInfoLogEvent(LogEvent original) { -+ super(original); -+ } -+ -+ @Override -+ public ThrowableProxy getThrownProxy() { -+ if (fixed) { -+ return super.getThrownProxy(); -+ } -+ rewriteStackTrace(super.getThrownProxy()); -+ fixed = true; -+ return super.getThrownProxy(); -+ } -+ -+ private void rewriteStackTrace(ThrowableProxy throwable) { -+ ExtendedStackTraceElement[] stackTrace = throwable.getExtendedStackTrace(); -+ for (int i = 0; i < stackTrace.length; i++) { -+ ExtendedClassInfo classInfo = stackTrace[i].getExtraClassInfo(); -+ if (classInfo.getLocation().equals("?")) { -+ StackTraceElement element = stackTrace[i].getStackTraceElement(); -+ String classLoaderName = element.getClassLoaderName(); -+ if (classLoaderName != null) { -+ stackTrace[i] = new ExtendedStackTraceElement(element, -+ new ExtendedClassInfo(classInfo.getExact(), classLoaderName, "?")); -+ } -+ } -+ } -+ if (throwable.getCauseProxy() != null) { -+ rewriteStackTrace(throwable.getCauseProxy()); -+ } -+ if (throwable.getSuppressedProxies() != null) { -+ for (ThrowableProxy proxy : throwable.getSuppressedProxies()) { -+ rewriteStackTrace(proxy); -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java b/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..34734bb969a1a74c7a4f9c17d40ebf007ad5d701 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java -@@ -0,0 +1,29 @@ -+package io.papermc.paper.logging; -+ -+import org.apache.logging.log4j.core.Core; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; -+import org.apache.logging.log4j.core.config.plugins.Plugin; -+import org.apache.logging.log4j.core.config.plugins.PluginFactory; -+import org.jetbrains.annotations.NotNull; -+ -+@Plugin( -+ name = "ExtraClassInfoRewritePolicy", -+ category = Core.CATEGORY_NAME, -+ elementType = "rewritePolicy", -+ printObject = true -+) -+public final class ExtraClassInfoRewritePolicy implements RewritePolicy { -+ @Override -+ public LogEvent rewrite(LogEvent source) { -+ if (source.getThrown() != null) { -+ return new ExtraClassInfoLogEvent(source); -+ } -+ return source; -+ } -+ -+ @PluginFactory -+ public static @NotNull ExtraClassInfoRewritePolicy createPolicy() { -+ return new ExtraClassInfoRewritePolicy(); -+ } -+} -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 2e421eaac80cf251b32e0bb504dd54a73edf4986..74ccc67e3c12dc5182602fb691ef3ddeb5b53280 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -34,6 +34,10 @@ - - - -+ -+ -+ -+ - - - diff --git a/patches/server/0690-Improve-boat-collision-performance.patch b/patches/server/0690-Improve-boat-collision-performance.patch deleted file mode 100644 index 242644a66c..0000000000 --- a/patches/server/0690-Improve-boat-collision-performance.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 2 Aug 2021 10:10:40 +0200 -Subject: [PATCH] Improve boat collision performance - - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 4fce18c52c8144460ebf0c1e336dce712d796cc6..384ddb03af26ae360fd22e2e231d9d14d6ad0865 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -112,6 +112,7 @@ public class Util { - }).findFirst().orElseThrow(() -> { - return new IllegalStateException("No jar file system provider found"); - }); -+ public static final double COLLISION_EPSILON = 1.0E-7; // Paper - private static Consumer thePauser = (message) -> { - }; - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index b0f38933d25397773ac8668f9d1e759d6b43884d..bc01742e3e762fbb5b7eb712a9211e4a8d411e03 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1341,7 +1341,7 @@ public abstract class LivingEntity extends Entity { - if (!source.isProjectile()) { - Entity entity = source.getDirectEntity(); - -- if (entity instanceof LivingEntity) { -+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - this.blockUsingShield((LivingEntity) entity); - } - } -@@ -1449,11 +1449,12 @@ public abstract class LivingEntity extends Entity { - } - - if (entity1 != null) { -- double d0 = entity1.getX() - this.getX(); -+ final boolean far = entity1.distanceToSqr(this) > (200.0 * 200.0); // Paper -+ double d0 = far ? (Math.random() - Math.random()) : entity1.getX() - this.getX(); // Paper - - double d1; - -- for (d1 = entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { -+ for (d1 = far ? Math.random() - Math.random() : entity1.getZ() - this.getZ(); d0 * d0 + d1 * d1 < 1.0E-4D; d1 = (Math.random() - Math.random()) * 0.01D) { // Paper - d0 = (Math.random() - Math.random()) * 0.01D; - } - -@@ -2141,7 +2142,7 @@ public abstract class LivingEntity extends Entity { - this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING)); - Entity entity = damagesource.getDirectEntity(); - -- if (entity instanceof LivingEntity) { -+ if (entity instanceof LivingEntity && entity.distanceToSqr(this) <= (200.0D * 200.0D)) { // Paper - this.blockUsingShield((LivingEntity) entity); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 5641a7b5c5e3d93cddabd91703c6f001700c5869..29da8a42406feccf7932097b07b1d32a38fa96b7 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -691,8 +691,8 @@ public class Boat extends Entity { - this.invFriction = 0.05F; - if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) { - this.waterLevel = this.getY(1.0D); -- this.setPos(this.getX(), (double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D, this.getZ()); -- this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); -+ this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper -+ this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); // Paper - this.lastYd = 0.0D; - this.status = Boat.Status.IN_WATER; - } else { diff --git a/patches/server/0690-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0690-Prevent-AFK-kick-while-watching-end-credits.patch new file mode 100644 index 0000000000..e20f451739 --- /dev/null +++ b/patches/server/0690-Prevent-AFK-kick-while-watching-end-credits.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Sat, 24 Jul 2021 16:54:11 +0200 +Subject: [PATCH] Prevent AFK kick while watching end credits. + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ebaf6ddcdccf4e2a5836782e58770e2cf1008f9d..a4ed338b3317378fdad694d3b0b12379128ffd26 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -438,7 +438,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + --this.dropSpamTickCount; + } + +- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { ++ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60) && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits. + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause + } diff --git a/patches/server/0691-Allow-skipping-writing-of-comments-to-server.propert.patch b/patches/server/0691-Allow-skipping-writing-of-comments-to-server.propert.patch new file mode 100644 index 0000000000..6395f3f4a8 --- /dev/null +++ b/patches/server/0691-Allow-skipping-writing-of-comments-to-server.propert.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Professor Bloodstone +Date: Fri, 23 Jul 2021 02:32:04 +0200 +Subject: [PATCH] Allow skipping writing of comments to server.properties + +Makes less git noise, as it won't update the date every single time + +Use -DPaper.skipServerPropertiesComments=true flag to disable writing it + +diff --git a/src/main/java/net/minecraft/server/dedicated/Settings.java b/src/main/java/net/minecraft/server/dedicated/Settings.java +index 7c35fb22df0bca2c2ca885a872ee42d6073d852f..aafa84578c7fb25feeee043259f9c056929ca008 100644 +--- a/src/main/java/net/minecraft/server/dedicated/Settings.java ++++ b/src/main/java/net/minecraft/server/dedicated/Settings.java +@@ -23,6 +23,7 @@ public abstract class Settings> { + + private static final Logger LOGGER = LogUtils.getLogger(); + public final Properties properties; ++ private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments + // CraftBukkit start + private OptionSet options = null; + +@@ -79,9 +80,47 @@ public abstract class Settings> { + } + // CraftBukkit end + OutputStream outputstream = Files.newOutputStream(path); ++ // Paper start - disable writing comments to properties file ++ java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) { ++ private boolean isRightAfterNewline = true; // If last written char was newline ++ private boolean isComment = false; // Are we writing comment currently? ++ ++ @Override ++ public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException { ++ this.write(b, 0, b.length); ++ } ++ ++ @Override ++ public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException { ++ int latest_offset = off; // The latest offset, updated when comment ends ++ for (int index = off; index < off + len; ++index ) { ++ byte c = bbuf[index]; ++ boolean isNewline = (c == '\n' || c == '\r'); ++ if (isNewline && isComment) { ++ // Comment has ended ++ isComment = false; ++ latest_offset = index+1; ++ } ++ if (c == '#' && isRightAfterNewline) { ++ isComment = true; ++ if (index != latest_offset) { ++ // We got some non-comment data earlier ++ super.write(bbuf, latest_offset, index-latest_offset); ++ } ++ } ++ isRightAfterNewline = isNewline; // Store for next iteration ++ ++ } ++ if (latest_offset < off+len && !isComment) { ++ // We have some unwritten data, that isn't part of a comment ++ super.write(bbuf, latest_offset, (off + len) - latest_offset); ++ } ++ } ++ }; ++ // Paper end + + try { +- this.properties.store(outputstream, "Minecraft server properties"); ++ this.properties.store(bufferedOutputStream, "Minecraft server properties"); // Paper - use bufferedOutputStream + } catch (Throwable throwable) { + if (outputstream != null) { + try { diff --git a/patches/server/0691-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0691-Prevent-AFK-kick-while-watching-end-credits.patch deleted file mode 100644 index ac9275d800..0000000000 --- a/patches/server/0691-Prevent-AFK-kick-while-watching-end-credits.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Sat, 24 Jul 2021 16:54:11 +0200 -Subject: [PATCH] Prevent AFK kick while watching end credits. - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 513a0e9c941a3ce7c3123d2476454ccc21dbeeba..fe2b99d0cde747c86cdc04c3d48f717b94747101 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -438,7 +438,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - --this.dropSpamTickCount; - } - -- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { -+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60) && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits. - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } diff --git a/patches/server/0692-Add-PlayerSetSpawnEvent.patch b/patches/server/0692-Add-PlayerSetSpawnEvent.patch new file mode 100644 index 0000000000..e726d70f85 --- /dev/null +++ b/patches/server/0692-Add-PlayerSetSpawnEvent.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 19 May 2021 18:59:10 -0700 +Subject: [PATCH] Add PlayerSetSpawnEvent + + +diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +index ce1c7512cc368e196ae94ee22c6a228c975b4980..1e41de9523c5fa3b9cfced798a5c35a24ec9d349 100644 +--- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java ++++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +@@ -32,9 +32,21 @@ public class SetSpawnCommand { + private static int setSpawn(CommandSourceStack source, Collection targets, BlockPos pos, float angle) { + ResourceKey resourceKey = source.getLevel().dimension(); + ++ final Collection actualTargets = new java.util.ArrayList<>(); // Paper + for(ServerPlayer serverPlayer : targets) { +- serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false); ++ // Paper start - PlayerSetSpawnEvent ++ if (serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { ++ actualTargets.add(serverPlayer); ++ } ++ // Paper end + } ++ // Paper start ++ if (actualTargets.isEmpty()) { ++ return 0; ++ } else { ++ targets = actualTargets; ++ } ++ // Paper end + + String string = resourceKey.location().toString(); + if (targets.size() == 1) { +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 56ac440465813a7dab8d166e882e18143a50729f..80e1970f568a74a43e624188a77cfbd28cfa52dd 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1287,7 +1287,7 @@ public class ServerPlayer extends Player { + } else if (this.bedBlocked(blockposition, enumdirection)) { + return Either.left(Player.BedSleepingProblem.OBSTRUCTED); + } else { +- this.setRespawnPosition(this.level.dimension(), blockposition, this.getYRot(), false, true); ++ this.setRespawnPosition(this.level.dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - PlayerSetSpawnEvent + if (this.level.isDay()) { + return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); + } else { +@@ -2128,12 +2128,33 @@ public class ServerPlayer extends Player { + return this.respawnForced; + } + ++ @Deprecated // Paper + public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { ++ // Paper start ++ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); ++ } ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { ++ Location spawnLoc = null; ++ boolean willNotify = false; + if (pos != null) { + boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); ++ spawnLoc = net.minecraft.server.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); ++ spawnLoc.setYaw(angle); ++ willNotify = sendMessage && !flag2; ++ } ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, spawnLoc, forced, willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); ++ if (!event.callEvent()) { ++ return false; ++ } ++ if (event.getLocation() != null) { ++ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; ++ pos = net.minecraft.server.MCUtil.toBlockPosition(event.getLocation()); ++ angle = (float) event.getLocation().getYaw(); ++ forced = event.isForced(); ++ // Paper end + +- if (sendMessage && !flag2) { +- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); ++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper ++ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper + } + + this.respawnPosition = pos; +@@ -2147,6 +2168,7 @@ public class ServerPlayer extends Player { + this.respawnForced = false; + } + ++ return true; // Paper + } + + public void trackChunk(ChunkPos chunkPos, Packet chunkDataPacket) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index a34df8db7ae5a1e2fd304e006db7b4af609efb4d..5ee0c3bb27ffbadc1e088983e643eed974753b65 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -902,13 +902,13 @@ public abstract class PlayerList { + f1 = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); + } + +- entityplayer1.setRespawnPosition(worldserver1.dimension(), blockposition, f, flag1, false); ++ entityplayer1.setRespawnPosition(worldserver1.dimension(), blockposition, f, flag1, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // Paper - PlayerSetSpawnEvent + flag2 = !flag && flag3; + isBedSpawn = true; + location = new Location(worldserver1.getWorld(), vec3d.x, vec3d.y, vec3d.z, f1, 0.0F); + } else if (blockposition != null) { + entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); +- entityplayer1.setRespawnPosition(null, null, 0f, false, false); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed ++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +index c3e49a781f838e6a46cb89744f3f1846de182275..c2f3d3a09327e7cb7d3167609eb3ce68eadf6443 100644 +--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +@@ -72,9 +72,14 @@ public class RespawnAnchorBlock extends Block { + if (!world.isClientSide) { + ServerPlayer serverPlayer = (ServerPlayer)player; + if (serverPlayer.getRespawnDimension() != world.dimension() || !pos.equals(serverPlayer.getRespawnPosition())) { +- serverPlayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true); ++ if (serverPlayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - PlayerSetSpawnEvent + world.playSound((Player)null, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.SUCCESS; ++ // Paper start - handle failed set spawn ++ } else { ++ return InteractionResult.FAIL; ++ } ++ // Paper end + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a7a0b892b50302ac7af4588bb65206834134dba2..5cdb599c6e460672ed0fe15d5c2a9d60ad22c2e3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1206,9 +1206,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + @Override + public void setBedSpawnLocation(Location location, boolean override) { + if (location == null) { +- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false); ++ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - PlayerSetSpawnEvent + } else { +- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getYaw(), override, false); ++ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - PlayerSetSpawnEvent + } + } + diff --git a/patches/server/0692-Allow-skipping-writing-of-comments-to-server.propert.patch b/patches/server/0692-Allow-skipping-writing-of-comments-to-server.propert.patch deleted file mode 100644 index 6395f3f4a8..0000000000 --- a/patches/server/0692-Allow-skipping-writing-of-comments-to-server.propert.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Professor Bloodstone -Date: Fri, 23 Jul 2021 02:32:04 +0200 -Subject: [PATCH] Allow skipping writing of comments to server.properties - -Makes less git noise, as it won't update the date every single time - -Use -DPaper.skipServerPropertiesComments=true flag to disable writing it - -diff --git a/src/main/java/net/minecraft/server/dedicated/Settings.java b/src/main/java/net/minecraft/server/dedicated/Settings.java -index 7c35fb22df0bca2c2ca885a872ee42d6073d852f..aafa84578c7fb25feeee043259f9c056929ca008 100644 ---- a/src/main/java/net/minecraft/server/dedicated/Settings.java -+++ b/src/main/java/net/minecraft/server/dedicated/Settings.java -@@ -23,6 +23,7 @@ public abstract class Settings> { - - private static final Logger LOGGER = LogUtils.getLogger(); - public final Properties properties; -+ private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments - // CraftBukkit start - private OptionSet options = null; - -@@ -79,9 +80,47 @@ public abstract class Settings> { - } - // CraftBukkit end - OutputStream outputstream = Files.newOutputStream(path); -+ // Paper start - disable writing comments to properties file -+ java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) { -+ private boolean isRightAfterNewline = true; // If last written char was newline -+ private boolean isComment = false; // Are we writing comment currently? -+ -+ @Override -+ public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException { -+ this.write(b, 0, b.length); -+ } -+ -+ @Override -+ public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException { -+ int latest_offset = off; // The latest offset, updated when comment ends -+ for (int index = off; index < off + len; ++index ) { -+ byte c = bbuf[index]; -+ boolean isNewline = (c == '\n' || c == '\r'); -+ if (isNewline && isComment) { -+ // Comment has ended -+ isComment = false; -+ latest_offset = index+1; -+ } -+ if (c == '#' && isRightAfterNewline) { -+ isComment = true; -+ if (index != latest_offset) { -+ // We got some non-comment data earlier -+ super.write(bbuf, latest_offset, index-latest_offset); -+ } -+ } -+ isRightAfterNewline = isNewline; // Store for next iteration -+ -+ } -+ if (latest_offset < off+len && !isComment) { -+ // We have some unwritten data, that isn't part of a comment -+ super.write(bbuf, latest_offset, (off + len) - latest_offset); -+ } -+ } -+ }; -+ // Paper end - - try { -- this.properties.store(outputstream, "Minecraft server properties"); -+ this.properties.store(bufferedOutputStream, "Minecraft server properties"); // Paper - use bufferedOutputStream - } catch (Throwable throwable) { - if (outputstream != null) { - try { diff --git a/patches/server/0693-Add-PlayerSetSpawnEvent.patch b/patches/server/0693-Add-PlayerSetSpawnEvent.patch deleted file mode 100644 index e726d70f85..0000000000 --- a/patches/server/0693-Add-PlayerSetSpawnEvent.patch +++ /dev/null @@ -1,146 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 19 May 2021 18:59:10 -0700 -Subject: [PATCH] Add PlayerSetSpawnEvent - - -diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -index ce1c7512cc368e196ae94ee22c6a228c975b4980..1e41de9523c5fa3b9cfced798a5c35a24ec9d349 100644 ---- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -+++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -@@ -32,9 +32,21 @@ public class SetSpawnCommand { - private static int setSpawn(CommandSourceStack source, Collection targets, BlockPos pos, float angle) { - ResourceKey resourceKey = source.getLevel().dimension(); - -+ final Collection actualTargets = new java.util.ArrayList<>(); // Paper - for(ServerPlayer serverPlayer : targets) { -- serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false); -+ // Paper start - PlayerSetSpawnEvent -+ if (serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) { -+ actualTargets.add(serverPlayer); -+ } -+ // Paper end - } -+ // Paper start -+ if (actualTargets.isEmpty()) { -+ return 0; -+ } else { -+ targets = actualTargets; -+ } -+ // Paper end - - String string = resourceKey.location().toString(); - if (targets.size() == 1) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 56ac440465813a7dab8d166e882e18143a50729f..80e1970f568a74a43e624188a77cfbd28cfa52dd 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1287,7 +1287,7 @@ public class ServerPlayer extends Player { - } else if (this.bedBlocked(blockposition, enumdirection)) { - return Either.left(Player.BedSleepingProblem.OBSTRUCTED); - } else { -- this.setRespawnPosition(this.level.dimension(), blockposition, this.getYRot(), false, true); -+ this.setRespawnPosition(this.level.dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - PlayerSetSpawnEvent - if (this.level.isDay()) { - return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); - } else { -@@ -2128,12 +2128,33 @@ public class ServerPlayer extends Player { - return this.respawnForced; - } - -+ @Deprecated // Paper - public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { -+ // Paper start -+ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); -+ } -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { -+ Location spawnLoc = null; -+ boolean willNotify = false; - if (pos != null) { - boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); -+ spawnLoc = net.minecraft.server.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); -+ spawnLoc.setYaw(angle); -+ willNotify = sendMessage && !flag2; -+ } -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, spawnLoc, forced, willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); -+ if (!event.callEvent()) { -+ return false; -+ } -+ if (event.getLocation() != null) { -+ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; -+ pos = net.minecraft.server.MCUtil.toBlockPosition(event.getLocation()); -+ angle = (float) event.getLocation().getYaw(); -+ forced = event.isForced(); -+ // Paper end - -- if (sendMessage && !flag2) { -- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); -+ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper -+ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - } - - this.respawnPosition = pos; -@@ -2147,6 +2168,7 @@ public class ServerPlayer extends Player { - this.respawnForced = false; - } - -+ return true; // Paper - } - - public void trackChunk(ChunkPos chunkPos, Packet chunkDataPacket) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index a34df8db7ae5a1e2fd304e006db7b4af609efb4d..5ee0c3bb27ffbadc1e088983e643eed974753b65 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -902,13 +902,13 @@ public abstract class PlayerList { - f1 = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); - } - -- entityplayer1.setRespawnPosition(worldserver1.dimension(), blockposition, f, flag1, false); -+ entityplayer1.setRespawnPosition(worldserver1.dimension(), blockposition, f, flag1, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // Paper - PlayerSetSpawnEvent - flag2 = !flag && flag3; - isBedSpawn = true; - location = new Location(worldserver1.getWorld(), vec3d.x, vec3d.y, vec3d.z, f1, 0.0F); - } else if (blockposition != null) { - entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); -- entityplayer1.setRespawnPosition(null, null, 0f, false, false); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed -+ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -index c3e49a781f838e6a46cb89744f3f1846de182275..c2f3d3a09327e7cb7d3167609eb3ce68eadf6443 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -72,9 +72,14 @@ public class RespawnAnchorBlock extends Block { - if (!world.isClientSide) { - ServerPlayer serverPlayer = (ServerPlayer)player; - if (serverPlayer.getRespawnDimension() != world.dimension() || !pos.equals(serverPlayer.getRespawnPosition())) { -- serverPlayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true); -+ if (serverPlayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - PlayerSetSpawnEvent - world.playSound((Player)null, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); - return InteractionResult.SUCCESS; -+ // Paper start - handle failed set spawn -+ } else { -+ return InteractionResult.FAIL; -+ } -+ // Paper end - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index a7a0b892b50302ac7af4588bb65206834134dba2..5cdb599c6e460672ed0fe15d5c2a9d60ad22c2e3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1206,9 +1206,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - @Override - public void setBedSpawnLocation(Location location, boolean override) { - if (location == null) { -- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false); -+ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - PlayerSetSpawnEvent - } else { -- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getYaw(), override, false); -+ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - PlayerSetSpawnEvent - } - } - diff --git a/patches/server/0693-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0693-Make-hoppers-respect-inventory-max-stack-size.patch new file mode 100644 index 0000000000..b4a083a5f7 --- /dev/null +++ b/patches/server/0693-Make-hoppers-respect-inventory-max-stack-size.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 7 Jul 2021 16:30:17 -0700 +Subject: [PATCH] Make hoppers respect inventory max stack size + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index a7ac6b528aecae528a17af157f8ec29371e4484c..ccad692aba2ed77259f6814d88f01b91ed9d229b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -586,17 +586,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + if (itemstack1.isEmpty()) { + // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem ++ ItemStack leftover = ItemStack.EMPTY; // Paper + if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) { ++ leftover = stack; // Paper + stack = stack.split(to.getMaxStackSize()); + } + // Spigot end + IGNORE_TILE_UPDATES = true; // Paper + to.setItem(slot, stack); + IGNORE_TILE_UPDATES = false; // Paper +- stack = ItemStack.EMPTY; ++ stack = leftover; // Paper + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +- int j = stack.getMaxStackSize() - itemstack1.getCount(); ++ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper + int k = Math.min(stack.getCount(), j); + + stack.shrink(k); diff --git a/patches/server/0694-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0694-Make-hoppers-respect-inventory-max-stack-size.patch deleted file mode 100644 index b4a083a5f7..0000000000 --- a/patches/server/0694-Make-hoppers-respect-inventory-max-stack-size.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 7 Jul 2021 16:30:17 -0700 -Subject: [PATCH] Make hoppers respect inventory max stack size - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index a7ac6b528aecae528a17af157f8ec29371e4484c..ccad692aba2ed77259f6814d88f01b91ed9d229b 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -586,17 +586,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen - - if (itemstack1.isEmpty()) { - // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem -+ ItemStack leftover = ItemStack.EMPTY; // Paper - if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) { -+ leftover = stack; // Paper - stack = stack.split(to.getMaxStackSize()); - } - // Spigot end - IGNORE_TILE_UPDATES = true; // Paper - to.setItem(slot, stack); - IGNORE_TILE_UPDATES = false; // Paper -- stack = ItemStack.EMPTY; -+ stack = leftover; // Paper - flag = true; - } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { -- int j = stack.getMaxStackSize() - itemstack1.getCount(); -+ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - int k = Math.min(stack.getCount(), j); - - stack.shrink(k); diff --git a/patches/server/0694-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0694-Optimize-entity-tracker-passenger-checks.patch new file mode 100644 index 0000000000..d3fef67200 --- /dev/null +++ b/patches/server/0694-Optimize-entity-tracker-passenger-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Sun, 8 Aug 2021 00:52:54 -0400 +Subject: [PATCH] Optimize entity tracker passenger checks + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index ddc5b4849939a96b76611cfa1cd34c06c7acc0f8..5246d427973f34843046e59c198785c73fccec33 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -71,7 +71,7 @@ public class ServerEntity { + this.trackedPlayers = trackedPlayers; + // CraftBukkit end + this.ap = Vec3.ZERO; +- this.lastPassengers = Collections.emptyList(); ++ this.lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks + this.level = worldserver; + this.broadcast = consumer; + this.entity = entity; diff --git a/patches/server/0695-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0695-Config-option-for-Piglins-guarding-chests.patch new file mode 100644 index 0000000000..c66e6ea022 --- /dev/null +++ b/patches/server/0695-Config-option-for-Piglins-guarding-chests.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 2 Dec 2020 03:07:58 -0800 +Subject: [PATCH] Config option for Piglins guarding chests + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +index 8aecd15cfb915f44bc6208b656e7db309270c132..9f220cf0668b5153c419215e8e25e418e765a1d6 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +@@ -463,6 +463,7 @@ public class PiglinAi { + } + + public static void angerNearbyPiglins(Player player, boolean blockOpen) { ++ if (!player.level.paperConfig().entities.behavior.piglinsGuardChests) return; // Paper + List list = player.level.getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D)); + + list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> { diff --git a/patches/server/0695-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0695-Optimize-entity-tracker-passenger-checks.patch deleted file mode 100644 index d3fef67200..0000000000 --- a/patches/server/0695-Optimize-entity-tracker-passenger-checks.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Sun, 8 Aug 2021 00:52:54 -0400 -Subject: [PATCH] Optimize entity tracker passenger checks - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index ddc5b4849939a96b76611cfa1cd34c06c7acc0f8..5246d427973f34843046e59c198785c73fccec33 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -71,7 +71,7 @@ public class ServerEntity { - this.trackedPlayers = trackedPlayers; - // CraftBukkit end - this.ap = Vec3.ZERO; -- this.lastPassengers = Collections.emptyList(); -+ this.lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks - this.level = worldserver; - this.broadcast = consumer; - this.entity = entity; diff --git a/patches/server/0696-Added-EntityDamageItemEvent.patch b/patches/server/0696-Added-EntityDamageItemEvent.patch new file mode 100644 index 0000000000..ec43bd00c9 --- /dev/null +++ b/patches/server/0696-Added-EntityDamageItemEvent.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Dec 2020 13:52:48 -0800 +Subject: [PATCH] Added EntityDamageItemEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index b4ad1610d30396be344a04f5f3a565ae2b8f2265..5c987e863a6ef257caebf8321fa3048dfc7a93c5 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -547,7 +547,7 @@ public final class ItemStack { + return this.getItem().getMaxDamage(); + } + +- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) { ++ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers + if (!this.isDamageableItem()) { + return false; + } else { +@@ -565,8 +565,8 @@ public final class ItemStack { + + amount -= k; + // CraftBukkit start +- if (player != null) { +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (player instanceof ServerPlayer serverPlayer) { // Paper ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (amount != event.getDamage() || event.isCancelled()) { +@@ -577,6 +577,14 @@ public final class ItemStack { + } + + amount = event.getDamage(); ++ // Paper start - EntityDamageItemEvent ++ } else if (player != null) { ++ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); ++ if (!event.callEvent()) { ++ return false; ++ } ++ amount = event.getDamage(); ++ // Paper end + } + // CraftBukkit end + if (amount <= 0) { +@@ -584,8 +592,8 @@ public final class ItemStack { + } + } + +- if (player != null && amount != 0) { +- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount); ++ if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper ++ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper + } + + j = this.getDamageValue() + amount; +@@ -597,7 +605,7 @@ public final class ItemStack { + public void hurtAndBreak(int amount, T entity, Consumer breakCallback) { + if (!entity.level.isClientSide && (!(entity instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) entity).getAbilities().instabuild)) { + if (this.isDamageableItem()) { +- if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer) entity : null)) { ++ if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent + breakCallback.accept(entity); + Item item = this.getItem(); + // CraftBukkit start - Check for item breaking diff --git a/patches/server/0696-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0696-Config-option-for-Piglins-guarding-chests.patch deleted file mode 100644 index c66e6ea022..0000000000 --- a/patches/server/0696-Config-option-for-Piglins-guarding-chests.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 2 Dec 2020 03:07:58 -0800 -Subject: [PATCH] Config option for Piglins guarding chests - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index 8aecd15cfb915f44bc6208b656e7db309270c132..9f220cf0668b5153c419215e8e25e418e765a1d6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -463,6 +463,7 @@ public class PiglinAi { - } - - public static void angerNearbyPiglins(Player player, boolean blockOpen) { -+ if (!player.level.paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - List list = player.level.getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D)); - - list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> { diff --git a/patches/server/0697-Added-EntityDamageItemEvent.patch b/patches/server/0697-Added-EntityDamageItemEvent.patch deleted file mode 100644 index ec43bd00c9..0000000000 --- a/patches/server/0697-Added-EntityDamageItemEvent.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Dec 2020 13:52:48 -0800 -Subject: [PATCH] Added EntityDamageItemEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index b4ad1610d30396be344a04f5f3a565ae2b8f2265..5c987e863a6ef257caebf8321fa3048dfc7a93c5 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -547,7 +547,7 @@ public final class ItemStack { - return this.getItem().getMaxDamage(); - } - -- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) { -+ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers - if (!this.isDamageableItem()) { - return false; - } else { -@@ -565,8 +565,8 @@ public final class ItemStack { - - amount -= k; - // CraftBukkit start -- if (player != null) { -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (player instanceof ServerPlayer serverPlayer) { // Paper -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { -@@ -577,6 +577,14 @@ public final class ItemStack { - } - - amount = event.getDamage(); -+ // Paper start - EntityDamageItemEvent -+ } else if (player != null) { -+ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount); -+ if (!event.callEvent()) { -+ return false; -+ } -+ amount = event.getDamage(); -+ // Paper end - } - // CraftBukkit end - if (amount <= 0) { -@@ -584,8 +592,8 @@ public final class ItemStack { - } - } - -- if (player != null && amount != 0) { -- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount); -+ if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper -+ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper - } - - j = this.getDamageValue() + amount; -@@ -597,7 +605,7 @@ public final class ItemStack { - public void hurtAndBreak(int amount, T entity, Consumer breakCallback) { - if (!entity.level.isClientSide && (!(entity instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) entity).getAbilities().instabuild)) { - if (this.isDamageableItem()) { -- if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer) entity : null)) { -+ if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent - breakCallback.accept(entity); - Item item = this.getItem(); - // CraftBukkit start - Check for item breaking diff --git a/patches/server/0697-Optimize-indirect-passenger-iteration.patch b/patches/server/0697-Optimize-indirect-passenger-iteration.patch new file mode 100644 index 0000000000..9768e1b56a --- /dev/null +++ b/patches/server/0697-Optimize-indirect-passenger-iteration.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 9 Aug 2021 00:38:37 -0400 +Subject: [PATCH] Optimize indirect passenger iteration + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0b524fb57af63ad3b9e11a30e7b2d0fd219664ad..307f3e4fa66bdf4f9b0864a7ba4aaa29f6e3b8d1 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3610,26 +3610,41 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + private Stream getIndirectPassengersStream() { ++ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper + return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); + } + + @Override + public Stream getSelfAndPassengers() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper + return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); + } + + @Override + public Stream getPassengersAndSelf() { ++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper + return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); + } + + public Iterable getIndirectPassengers() { ++ // Paper start - rewrite this method ++ if (this.passengers.isEmpty()) { return ImmutableList.of(); } ++ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); ++ for (Entity passenger : this.passengers) { ++ indirectPassengers.add(passenger); ++ indirectPassengers.addAll(passenger.getIndirectPassengers()); ++ } ++ return indirectPassengers.build(); ++ } ++ private Iterable getIndirectPassengers_old() { ++ // Paper end + return () -> { + return this.getIndirectPassengersStream().iterator(); + }; + } + + public boolean hasExactlyOnePlayerPassenger() { ++ if (this.passengers.isEmpty()) { return false; } // Paper + return this.getIndirectPassengersStream().filter((entity) -> { + return entity instanceof Player; + }).count() == 1L; diff --git a/patches/server/0698-Fix-block-drops-position-losing-precision-millions-o.patch b/patches/server/0698-Fix-block-drops-position-losing-precision-millions-o.patch new file mode 100644 index 0000000000..6b5cd596c4 --- /dev/null +++ b/patches/server/0698-Fix-block-drops-position-losing-precision-millions-o.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 12 Aug 2021 21:15:38 -0700 +Subject: [PATCH] Fix block drops position losing precision millions of blocks + out + + +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 cb11cef117fc896ddcb40993ddb852a2e717c2ad..2db7b9f145b53df5ef79ae235a0de7ccff50db7e 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -346,9 +346,11 @@ public class Block extends BlockBehaviour implements ItemLike { + + public static void popResource(Level world, BlockPos pos, ItemStack stack) { + float f = EntityType.ITEM.getHeight() / 2.0F; +- double d0 = (double) ((float) pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); +- double d1 = (double) ((float) pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - (double) f; +- double d2 = (double) ((float) pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); ++ // Paper start - don't convert potentially massive numbers to floats ++ double d0 = pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); ++ double d1 = pos.getY() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D) - (double) f; ++ double d2 = pos.getZ() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); ++ // Paper end + + Block.popResource(world, () -> { + return new ItemEntity(world, d0, d1, d2, stack); +@@ -361,9 +363,11 @@ public class Block extends BlockBehaviour implements ItemLike { + int k = direction.getStepZ(); + float f = EntityType.ITEM.getWidth() / 2.0F; + float f1 = EntityType.ITEM.getHeight() / 2.0F; +- double d0 = (double) ((float) pos.getX() + 0.5F) + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) i * (0.5F + f))); +- double d1 = (double) ((float) pos.getY() + 0.5F) + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) j * (0.5F + f1))) - (double) f1; +- double d2 = (double) ((float) pos.getZ() + 0.5F) + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) k * (0.5F + f))); ++ // Paper start - don't convert potentially massive numbers to floats ++ double d0 = pos.getX() + 0.5D + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) i * (0.5F + f))); ++ double d1 = pos.getY() + 0.5D + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) j * (0.5F + f1))) - (double) f1; ++ double d2 = pos.getZ() + 0.5D + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) k * (0.5F + f))); ++ // Paper end + double d3 = i == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) i * 0.1D; + double d4 = j == 0 ? Mth.nextDouble(world.random, 0.0D, 0.1D) : (double) j * 0.1D + 0.1D; + double d5 = k == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) k * 0.1D; diff --git a/patches/server/0698-Optimize-indirect-passenger-iteration.patch b/patches/server/0698-Optimize-indirect-passenger-iteration.patch deleted file mode 100644 index 6ae8c4e698..0000000000 --- a/patches/server/0698-Optimize-indirect-passenger-iteration.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 9 Aug 2021 00:38:37 -0400 -Subject: [PATCH] Optimize indirect passenger iteration - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 4888873a5efa026a1082c9f216eecc950b6f2471..460b51374e41c3d88e1c3641fb9f2f0205399b54 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3596,26 +3596,41 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - private Stream getIndirectPassengersStream() { -+ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - return this.passengers.stream().flatMap(Entity::getSelfAndPassengers); - } - - @Override - public Stream getSelfAndPassengers() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - return Stream.concat(Stream.of(this), this.getIndirectPassengersStream()); - } - - @Override - public Stream getPassengersAndSelf() { -+ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this)); - } - - public Iterable getIndirectPassengers() { -+ // Paper start - rewrite this method -+ if (this.passengers.isEmpty()) { return ImmutableList.of(); } -+ ImmutableList.Builder indirectPassengers = ImmutableList.builder(); -+ for (Entity passenger : this.passengers) { -+ indirectPassengers.add(passenger); -+ indirectPassengers.addAll(passenger.getIndirectPassengers()); -+ } -+ return indirectPassengers.build(); -+ } -+ private Iterable getIndirectPassengers_old() { -+ // Paper end - return () -> { - return this.getIndirectPassengersStream().iterator(); - }; - } - - public boolean hasExactlyOnePlayerPassenger() { -+ if (this.passengers.isEmpty()) { return false; } // Paper - return this.getIndirectPassengersStream().filter((entity) -> { - return entity instanceof Player; - }).count() == 1L; diff --git a/patches/server/0699-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0699-Configurable-item-frame-map-cursor-update-interval.patch new file mode 100644 index 0000000000..5e1d739a7c --- /dev/null +++ b/patches/server/0699-Configurable-item-frame-map-cursor-update-interval.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Warrior <50800980+Warriorrrr@users.noreply.github.com> +Date: Fri, 13 Aug 2021 01:14:38 +0200 +Subject: [PATCH] Configurable item frame map cursor update interval + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 5246d427973f34843046e59c198785c73fccec33..c84ec3b93f2783de7a2815f23a9f1de89c1ab109 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -100,7 +100,7 @@ public class ServerEntity { + if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block + ItemStack itemstack = entityitemframe.getItem(); + +- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks ++ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable + Integer integer = MapItem.getMapId(itemstack); + MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); + diff --git a/patches/server/0699-Fix-block-drops-position-losing-precision-millions-o.patch b/patches/server/0699-Fix-block-drops-position-losing-precision-millions-o.patch deleted file mode 100644 index 6b5cd596c4..0000000000 --- a/patches/server/0699-Fix-block-drops-position-losing-precision-millions-o.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 12 Aug 2021 21:15:38 -0700 -Subject: [PATCH] Fix block drops position losing precision millions of blocks - out - - -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 cb11cef117fc896ddcb40993ddb852a2e717c2ad..2db7b9f145b53df5ef79ae235a0de7ccff50db7e 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -346,9 +346,11 @@ public class Block extends BlockBehaviour implements ItemLike { - - public static void popResource(Level world, BlockPos pos, ItemStack stack) { - float f = EntityType.ITEM.getHeight() / 2.0F; -- double d0 = (double) ((float) pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); -- double d1 = (double) ((float) pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - (double) f; -- double d2 = (double) ((float) pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); -+ // Paper start - don't convert potentially massive numbers to floats -+ double d0 = pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); -+ double d1 = pos.getY() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D) - (double) f; -+ double d2 = pos.getZ() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); -+ // Paper end - - Block.popResource(world, () -> { - return new ItemEntity(world, d0, d1, d2, stack); -@@ -361,9 +363,11 @@ public class Block extends BlockBehaviour implements ItemLike { - int k = direction.getStepZ(); - float f = EntityType.ITEM.getWidth() / 2.0F; - float f1 = EntityType.ITEM.getHeight() / 2.0F; -- double d0 = (double) ((float) pos.getX() + 0.5F) + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) i * (0.5F + f))); -- double d1 = (double) ((float) pos.getY() + 0.5F) + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) j * (0.5F + f1))) - (double) f1; -- double d2 = (double) ((float) pos.getZ() + 0.5F) + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) k * (0.5F + f))); -+ // Paper start - don't convert potentially massive numbers to floats -+ double d0 = pos.getX() + 0.5D + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) i * (0.5F + f))); -+ double d1 = pos.getY() + 0.5D + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) j * (0.5F + f1))) - (double) f1; -+ double d2 = pos.getZ() + 0.5D + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) ((float) k * (0.5F + f))); -+ // Paper end - double d3 = i == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) i * 0.1D; - double d4 = j == 0 ? Mth.nextDouble(world.random, 0.0D, 0.1D) : (double) j * 0.1D + 0.1D; - double d5 = k == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) k * 0.1D; diff --git a/patches/server/0700-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0700-Configurable-item-frame-map-cursor-update-interval.patch deleted file mode 100644 index 5e1d739a7c..0000000000 --- a/patches/server/0700-Configurable-item-frame-map-cursor-update-interval.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Warrior <50800980+Warriorrrr@users.noreply.github.com> -Date: Fri, 13 Aug 2021 01:14:38 +0200 -Subject: [PATCH] Configurable item frame map cursor update interval - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 5246d427973f34843046e59c198785c73fccec33..c84ec3b93f2783de7a2815f23a9f1de89c1ab109 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -100,7 +100,7 @@ public class ServerEntity { - if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block - ItemStack itemstack = entityitemframe.getItem(); - -- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks -+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable - Integer integer = MapItem.getMapId(itemstack); - MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); - diff --git a/patches/server/0700-Make-EntityUnleashEvent-cancellable.patch b/patches/server/0700-Make-EntityUnleashEvent-cancellable.patch new file mode 100644 index 0000000000..b16ad47f2f --- /dev/null +++ b/patches/server/0700-Make-EntityUnleashEvent-cancellable.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 21:25:31 -0800 +Subject: [PATCH] Make EntityUnleashEvent cancellable + + +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index e08d69af81f4ca0535be522eef4792e4127f454c..5fb88a3b7242a2712a568aaccebe601f89bfee3a 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -1488,7 +1488,7 @@ public abstract class Mob extends LivingEntity { + if (flag1 && this.isLeashed()) { + // Paper start - drop leash variable + EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); +- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ if (!event.callEvent()) { return flag1; } + this.dropLeash(true, event.isDropLeash()); + // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +index f5cb3576aa2560c86f4a1df9d51d8ecde4e98905..7b2a81f9a79c5e96beba44ffe9b56a305ac2404f 100644 +--- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java ++++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java +@@ -51,7 +51,7 @@ public abstract class PathfinderMob extends Mob { + if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper + // Paper start - drop leash variable + EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); +- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ if (!event.callEvent()) { return; } + this.dropLeash(true, event.isDropLeash()); + // Paper end + } +@@ -63,7 +63,7 @@ public abstract class PathfinderMob extends Mob { + if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper + // Paper start - drop leash variable + EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); +- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit ++ if (!event.callEvent()) return; + this.dropLeash(true, event.isDropLeash()); + // Paper end + this.goalSelector.disableControlFlag(Goal.Flag.MOVE); diff --git a/patches/server/0701-Clear-bucket-NBT-after-dispense.patch b/patches/server/0701-Clear-bucket-NBT-after-dispense.patch new file mode 100644 index 0000000000..52e26f4674 --- /dev/null +++ b/patches/server/0701-Clear-bucket-NBT-after-dispense.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 13 Aug 2021 15:00:06 -0700 +Subject: [PATCH] Clear bucket NBT after dispense + + +diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +index 15fc100e468e68cbb0c43363c0eb25dc2ef8c6e0..3d2b5f040715a0e4fac0e6786bd11a4d715330ce 100644 +--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -642,8 +642,7 @@ public interface DispenseItemBehavior { + Item item = Items.BUCKET; + stack.shrink(1); + if (stack.isEmpty()) { +- stack.setItem(Items.BUCKET); +- stack.setCount(1); ++ stack = new ItemStack(item); // Paper - clear tag + } else if (((DispenserBlockEntity) pointer.getEntity()).addItem(new ItemStack(item)) < 0) { + this.defaultDispenseItemBehavior.dispense(pointer, new ItemStack(item)); + } diff --git a/patches/server/0701-Make-EntityUnleashEvent-cancellable.patch b/patches/server/0701-Make-EntityUnleashEvent-cancellable.patch deleted file mode 100644 index 10b64ca856..0000000000 --- a/patches/server/0701-Make-EntityUnleashEvent-cancellable.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 21:25:31 -0800 -Subject: [PATCH] Make EntityUnleashEvent cancellable - - -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 837c0a8bd15388bdb60d6950a437640459adde3c..a49dfe4f81d449c5dd7ba5b8f9af7fec5c54f5de 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1482,7 +1482,7 @@ public abstract class Mob extends LivingEntity { - if (flag1 && this.isLeashed()) { - // Paper start - drop leash variable - EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN, true); -- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ if (!event.callEvent()) { return flag1; } - this.dropLeash(true, event.isDropLeash()); - // Paper end - } -diff --git a/src/main/java/net/minecraft/world/entity/PathfinderMob.java b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -index f5cb3576aa2560c86f4a1df9d51d8ecde4e98905..7b2a81f9a79c5e96beba44ffe9b56a305ac2404f 100644 ---- a/src/main/java/net/minecraft/world/entity/PathfinderMob.java -+++ b/src/main/java/net/minecraft/world/entity/PathfinderMob.java -@@ -51,7 +51,7 @@ public abstract class PathfinderMob extends Mob { - if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper - // Paper start - drop leash variable - EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ if (!event.callEvent()) { return; } - this.dropLeash(true, event.isDropLeash()); - // Paper end - } -@@ -63,7 +63,7 @@ public abstract class PathfinderMob extends Mob { - if (f > entity.level.paperConfig().misc.maxLeashDistance) { // Paper - // Paper start - drop leash variable - EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE, true); -- this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit -+ if (!event.callEvent()) return; - this.dropLeash(true, event.isDropLeash()); - // Paper end - this.goalSelector.disableControlFlag(Goal.Flag.MOVE); diff --git a/patches/server/0702-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0702-Change-EnderEye-target-without-changing-other-things.patch new file mode 100644 index 0000000000..ce9dc32309 --- /dev/null +++ b/patches/server/0702-Change-EnderEye-target-without-changing-other-things.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 12:13:53 -0700 +Subject: [PATCH] Change EnderEye target without changing other things + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +index 16f520706c22bd55135fe2bc114bdf440925333b..ac4b6840cca345416a9e5695fc07879cd96f64d2 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java +@@ -75,6 +75,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier { + } + + public void signalTo(BlockPos pos) { ++ // Paper start ++ this.signalTo(pos, true); ++ } ++ public void signalTo(BlockPos pos, boolean update) { ++ // Paper end + double d0 = (double) pos.getX(); + int i = pos.getY(); + double d1 = (double) pos.getZ(); +@@ -92,8 +97,10 @@ public class EyeOfEnder extends Entity implements ItemSupplier { + this.tz = d1; + } + ++ if (update) { // Paper + this.life = 0; + this.surviveAfterDeath = this.random.nextInt(5) > 0; ++ } // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +index 705dcae8f881cbdf5ff468f945b9269f9eae0e58..13c1188639e00cd96e00b179c4e353582bf66e64 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java +@@ -38,8 +38,15 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal { + + @Override + public void setTargetLocation(Location location) { ++ // Paper start ++ this.setTargetLocation(location, true); ++ } ++ ++ @Override ++ public void setTargetLocation(Location location, boolean update) { ++ // Paper end + Preconditions.checkArgument(getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds"); +- this.getHandle().signalTo(new BlockPos(location.getX(), location.getY(), location.getZ())); ++ this.getHandle().signalTo(new BlockPos(location.getX(), location.getY(), location.getZ()), update); // Paper + } + + @Override diff --git a/patches/server/0702-Clear-bucket-NBT-after-dispense.patch b/patches/server/0702-Clear-bucket-NBT-after-dispense.patch deleted file mode 100644 index 52e26f4674..0000000000 --- a/patches/server/0702-Clear-bucket-NBT-after-dispense.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 13 Aug 2021 15:00:06 -0700 -Subject: [PATCH] Clear bucket NBT after dispense - - -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 15fc100e468e68cbb0c43363c0eb25dc2ef8c6e0..3d2b5f040715a0e4fac0e6786bd11a4d715330ce 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -642,8 +642,7 @@ public interface DispenseItemBehavior { - Item item = Items.BUCKET; - stack.shrink(1); - if (stack.isEmpty()) { -- stack.setItem(Items.BUCKET); -- stack.setCount(1); -+ stack = new ItemStack(item); // Paper - clear tag - } else if (((DispenserBlockEntity) pointer.getEntity()).addItem(new ItemStack(item)) < 0) { - this.defaultDispenseItemBehavior.dispense(pointer, new ItemStack(item)); - } diff --git a/patches/server/0703-Add-BlockBreakBlockEvent.patch b/patches/server/0703-Add-BlockBreakBlockEvent.patch new file mode 100644 index 0000000000..a69de03ab7 --- /dev/null +++ b/patches/server/0703-Add-BlockBreakBlockEvent.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 17:58:11 -0800 +Subject: [PATCH] Add BlockBreakBlockEvent + + +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 2db7b9f145b53df5ef79ae235a0de7ccff50db7e..2e65b44f10aeb44fd524a58e7eb815a566c1ad61 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -333,6 +333,23 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + } ++ // Paper start ++ public static boolean dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { ++ if (world instanceof ServerLevel) { ++ List items = com.google.common.collect.Lists.newArrayList(); ++ for (net.minecraft.world.item.ItemStack drop : net.minecraft.world.level.block.Block.getDrops(state, world.getMinecraftWorld(), pos, blockEntity)) { ++ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); ++ } ++ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items); ++ event.callEvent(); ++ for (var drop : event.getDrops()) { ++ popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); ++ } ++ state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true); ++ } ++ return true; ++ } ++ // Paper end + + public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, Entity entity, ItemStack stack) { + if (world instanceof ServerLevel) { +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 8d73893100884c08aa552ff41c2a07a3e714df47..24a822ade17849a083161216c184f02c30b5cb1f 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -400,7 +400,7 @@ public class PistonBaseBlock extends DirectionalBlock { + iblockdata1 = world.getBlockState(blockposition3); + BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; + +- dropResources(iblockdata1, world, blockposition3, tileentity); ++ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper + world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); + world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); + if (!iblockdata1.is(BlockTags.FIRE)) { +diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +index 02be7c3d104fe3b3a2772201f5ebdfb6d16e9b49..ff40fe323964f173561a6838fb443e79abf9df38 100644 +--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +@@ -294,7 +294,7 @@ public abstract class FlowingFluid extends Fluid { + ((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState); + } else { + if (!state.isAir()) { +- this.beforeDestroyingBlock(world, pos, state); ++ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper + } + + world.setBlock(pos, fluidState.createLegacyBlock(), 3); +@@ -302,6 +302,7 @@ public abstract class FlowingFluid extends Fluid { + + } + ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - add source parameter + protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state); + + private static short getCacheKey(BlockPos blockposition, BlockPos blockposition1) { +diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +index a10b6a6b0ff9c104a94be3e9d0d1757333d81a00..ac33ba631f4b0ae0e08bff5748440ef5b76c2117 100644 +--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java ++++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java +@@ -63,6 +63,13 @@ public abstract class WaterFluid extends FlowingFluid { + return true; + } + ++ // Paper start ++ @Override ++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { ++ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; ++ Block.dropResources(state, world, pos, tileentity, source); ++ } ++ // Paper end + @Override + protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) { + BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; diff --git a/patches/server/0703-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0703-Change-EnderEye-target-without-changing-other-things.patch deleted file mode 100644 index ce9dc32309..0000000000 --- a/patches/server/0703-Change-EnderEye-target-without-changing-other-things.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 12:13:53 -0700 -Subject: [PATCH] Change EnderEye target without changing other things - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -index 16f520706c22bd55135fe2bc114bdf440925333b..ac4b6840cca345416a9e5695fc07879cd96f64d2 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -@@ -75,6 +75,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier { - } - - public void signalTo(BlockPos pos) { -+ // Paper start -+ this.signalTo(pos, true); -+ } -+ public void signalTo(BlockPos pos, boolean update) { -+ // Paper end - double d0 = (double) pos.getX(); - int i = pos.getY(); - double d1 = (double) pos.getZ(); -@@ -92,8 +97,10 @@ public class EyeOfEnder extends Entity implements ItemSupplier { - this.tz = d1; - } - -+ if (update) { // Paper - this.life = 0; - this.surviveAfterDeath = this.random.nextInt(5) > 0; -+ } // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -index 705dcae8f881cbdf5ff468f945b9269f9eae0e58..13c1188639e00cd96e00b179c4e353582bf66e64 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java -@@ -38,8 +38,15 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal { - - @Override - public void setTargetLocation(Location location) { -+ // Paper start -+ this.setTargetLocation(location, true); -+ } -+ -+ @Override -+ public void setTargetLocation(Location location, boolean update) { -+ // Paper end - Preconditions.checkArgument(getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds"); -- this.getHandle().signalTo(new BlockPos(location.getX(), location.getY(), location.getZ())); -+ this.getHandle().signalTo(new BlockPos(location.getX(), location.getY(), location.getZ()), update); // Paper - } - - @Override diff --git a/patches/server/0704-Add-BlockBreakBlockEvent.patch b/patches/server/0704-Add-BlockBreakBlockEvent.patch deleted file mode 100644 index a69de03ab7..0000000000 --- a/patches/server/0704-Add-BlockBreakBlockEvent.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 17:58:11 -0800 -Subject: [PATCH] Add BlockBreakBlockEvent - - -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 2db7b9f145b53df5ef79ae235a0de7ccff50db7e..2e65b44f10aeb44fd524a58e7eb815a566c1ad61 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -333,6 +333,23 @@ public class Block extends BlockBehaviour implements ItemLike { - } - - } -+ // Paper start -+ public static boolean dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) { -+ if (world instanceof ServerLevel) { -+ List items = com.google.common.collect.Lists.newArrayList(); -+ for (net.minecraft.world.item.ItemStack drop : net.minecraft.world.level.block.Block.getDrops(state, world.getMinecraftWorld(), pos, blockEntity)) { -+ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop)); -+ } -+ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items); -+ event.callEvent(); -+ for (var drop : event.getDrops()) { -+ popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); -+ } -+ state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true); -+ } -+ return true; -+ } -+ // Paper end - - public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, Entity entity, ItemStack stack) { - if (world instanceof ServerLevel) { -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 8d73893100884c08aa552ff41c2a07a3e714df47..24a822ade17849a083161216c184f02c30b5cb1f 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -400,7 +400,7 @@ public class PistonBaseBlock extends DirectionalBlock { - iblockdata1 = world.getBlockState(blockposition3); - BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null; - -- dropResources(iblockdata1, world, blockposition3, tileentity); -+ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); - world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); - if (!iblockdata1.is(BlockTags.FIRE)) { -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index 02be7c3d104fe3b3a2772201f5ebdfb6d16e9b49..ff40fe323964f173561a6838fb443e79abf9df38 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -294,7 +294,7 @@ public abstract class FlowingFluid extends Fluid { - ((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState); - } else { - if (!state.isAir()) { -- this.beforeDestroyingBlock(world, pos, state); -+ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - } - - world.setBlock(pos, fluidState.createLegacyBlock(), 3); -@@ -302,6 +302,7 @@ public abstract class FlowingFluid extends Fluid { - - } - -+ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - add source parameter - protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state); - - private static short getCacheKey(BlockPos blockposition, BlockPos blockposition1) { -diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -index a10b6a6b0ff9c104a94be3e9d0d1757333d81a00..ac33ba631f4b0ae0e08bff5748440ef5b76c2117 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -63,6 +63,13 @@ public abstract class WaterFluid extends FlowingFluid { - return true; - } - -+ // Paper start -+ @Override -+ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { -+ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; -+ Block.dropResources(state, world, pos, tileentity, source); -+ } -+ // Paper end - @Override - protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) { - BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; diff --git a/patches/server/0704-Option-to-prevent-NBT-copy-in-smithing-recipes.patch b/patches/server/0704-Option-to-prevent-NBT-copy-in-smithing-recipes.patch new file mode 100644 index 0000000000..14e666a79e --- /dev/null +++ b/patches/server/0704-Option-to-prevent-NBT-copy-in-smithing-recipes.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 26 Sep 2021 12:57:28 -0700 +Subject: [PATCH] Option to prevent NBT copy in smithing recipes + + +diff --git a/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java b/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java +index d80fc47820edbb3bea439aedf2e02e82c1931e35..076e10e5d7908c590402cfbb739bf73bc00276ce 100644 +--- a/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java ++++ b/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java +@@ -25,8 +25,15 @@ public class UpgradeRecipe implements net.minecraft.world.item.crafting.Recipe +Date: Thu, 23 Sep 2021 10:40:09 -0700 +Subject: [PATCH] More CommandBlock API + + +diff --git a/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b42306f17bf8850a13a51067c2d19e7583187e5 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java +@@ -0,0 +1,33 @@ ++package io.papermc.paper.commands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.command.CommandBlockHolder; ++import net.kyori.adventure.text.Component; ++import net.minecraft.world.level.BaseCommandBlock; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public interface PaperCommandBlockHolder extends CommandBlockHolder { ++ ++ BaseCommandBlock getCommandBlockHandle(); ++ ++ @Override ++ default @NotNull Component lastOutput() { ++ return PaperAdventure.asAdventure(getCommandBlockHandle().getLastOutput()); ++ } ++ ++ @Override ++ default void lastOutput(@Nullable Component lastOutput) { ++ getCommandBlockHandle().setLastOutput(PaperAdventure.asVanilla(lastOutput)); ++ } ++ ++ @Override ++ default int getSuccessCount() { ++ return getCommandBlockHandle().getSuccessCount(); ++ } ++ ++ @Override ++ default void setSuccessCount(int successCount) { ++ getCommandBlockHandle().setSuccessCount(successCount); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java +index c4ea6760f489e6171f9e6e170160b932597f842f..245a9b062a0033a39fd42f3ff94350192570aec4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java +@@ -5,7 +5,7 @@ import org.bukkit.World; + import org.bukkit.block.CommandBlock; + import org.bukkit.craftbukkit.util.CraftChatMessage; + +-public class CraftCommandBlock extends CraftBlockEntityState implements CommandBlock { ++public class CraftCommandBlock extends CraftBlockEntityState implements CommandBlock, io.papermc.paper.commands.PaperCommandBlockHolder { + + public CraftCommandBlock(World world, CommandBlockEntity tileEntity) { + super(world, tileEntity); +@@ -41,5 +41,10 @@ public class CraftCommandBlock extends CraftBlockEntityState + public void name(net.kyori.adventure.text.Component name) { + getSnapshot().getCommandBlock().setName(name == null ? net.minecraft.network.chat.Component.literal("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name)); + } ++ ++ @Override ++ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() { ++ return getSnapshot().getCommandBlock(); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java +index f9863e138994f6c7a7975a852f106faa96d52315..b709a1d909c189f60d0c3aa97b4b96623e7c1db0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java +@@ -14,7 +14,7 @@ import org.bukkit.permissions.PermissionAttachment; + import org.bukkit.permissions.PermissionAttachmentInfo; + import org.bukkit.plugin.Plugin; + +-public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart { ++public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart, io.papermc.paper.commands.PaperCommandBlockHolder { + private final PermissibleBase perm = new PermissibleBase(this); + + public CraftMinecartCommand(CraftServer server, MinecartCommandBlock entity) { +@@ -70,6 +70,17 @@ public class CraftMinecartCommand extends CraftMinecart implements CommandMineca + public net.kyori.adventure.text.@org.jetbrains.annotations.NotNull Component name() { + return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getHandle().getCommandBlock().getName()); + } ++ ++ @Override ++ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() { ++ return getHandle().getCommandBlock(); ++ } ++ ++ @Override ++ public void lastOutput(net.kyori.adventure.text.Component lastOutput) { ++ io.papermc.paper.commands.PaperCommandBlockHolder.super.lastOutput(lastOutput); ++ getCommandBlockHandle().onUpdated(); ++ } + // Paper end + + @Override diff --git a/patches/server/0705-Option-to-prevent-NBT-copy-in-smithing-recipes.patch b/patches/server/0705-Option-to-prevent-NBT-copy-in-smithing-recipes.patch deleted file mode 100644 index 14e666a79e..0000000000 --- a/patches/server/0705-Option-to-prevent-NBT-copy-in-smithing-recipes.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 26 Sep 2021 12:57:28 -0700 -Subject: [PATCH] Option to prevent NBT copy in smithing recipes - - -diff --git a/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java b/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java -index d80fc47820edbb3bea439aedf2e02e82c1931e35..076e10e5d7908c590402cfbb739bf73bc00276ce 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java -+++ b/src/main/java/net/minecraft/world/item/crafting/UpgradeRecipe.java -@@ -25,8 +25,15 @@ public class UpgradeRecipe implements net.minecraft.world.item.crafting.Recipe +Date: Fri, 1 Oct 2021 08:04:39 -0700 +Subject: [PATCH] Add missing team sidebar display slots + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java +index e2d3fe9af7d3bd82bee519b20e141cd58f68bbd6..944a4fee237730c0d89567aaa6ddf268467aa0e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java +@@ -7,36 +7,23 @@ import org.bukkit.scoreboard.DisplaySlot; + import org.bukkit.scoreboard.RenderType; + + public final class CraftScoreboardTranslations { +- static final int MAX_DISPLAY_SLOT = 19; ++ static final int MAX_DISPLAY_SLOT = Scoreboard.getDisplaySlotNames().length; // Paper ++ @Deprecated // Paper + static final ImmutableBiMap SLOTS = ImmutableBiMap.builder() + .put(DisplaySlot.BELOW_NAME, "belowName") + .put(DisplaySlot.PLAYER_LIST, "list") + .put(DisplaySlot.SIDEBAR, "sidebar") +- .put(DisplaySlot.SIDEBAR_BLACK, "sidebar.team.black") +- .put(DisplaySlot.SIDEBAR_DARK_BLUE, "sidebar.team.dark_blue") +- .put(DisplaySlot.SIDEBAR_DARK_GREEN, "sidebar.team.dark_green") +- .put(DisplaySlot.SIDEBAR_DARK_AQUA, "sidebar.team.dark_aqua") +- .put(DisplaySlot.SIDEBAR_DARK_RED, "sidebar.team.dark_red") +- .put(DisplaySlot.SIDEBAR_DARK_PURPLE, "sidebar.team.dark_purple") +- .put(DisplaySlot.SIDEBAR_GOLD, "sidebar.team.gold") +- .put(DisplaySlot.SIDEBAR_GRAY, "sidebar.team.gray") +- .put(DisplaySlot.SIDEBAR_DARK_GRAY, "sidebar.team.dark_gray") +- .put(DisplaySlot.SIDEBAR_BLUE, "sidebar.team.blue") +- .put(DisplaySlot.SIDEBAR_GREEN, "sidebar.team.green") +- .put(DisplaySlot.SIDEBAR_AQUA, "sidebar.team.aqua") +- .put(DisplaySlot.SIDEBAR_RED, "sidebar.team.red") +- .put(DisplaySlot.SIDEBAR_LIGHT_PURPLE, "sidebar.team.light_purple") +- .put(DisplaySlot.SIDEBAR_YELLOW, "sidebar.team.yellow") +- .put(DisplaySlot.SIDEBAR_WHITE, "sidebar.team.white") + .buildOrThrow(); + + private CraftScoreboardTranslations() {} + + public static DisplaySlot toBukkitSlot(int i) { ++ if (true) return org.bukkit.scoreboard.DisplaySlot.NAMES.value(Scoreboard.getDisplaySlotName(i)); // Paper + return CraftScoreboardTranslations.SLOTS.inverse().get(Scoreboard.getDisplaySlotName(i)); + } + + public static int fromBukkitSlot(DisplaySlot slot) { ++ if (true) return Scoreboard.getDisplaySlotByName(slot.getId()); // Paper + return Scoreboard.getDisplaySlotByName(CraftScoreboardTranslations.SLOTS.get(slot)); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +index 89a32de0a3fbb7465b078b2aa1cc1d156a4a174d..27646d963bd1158f3fe0a9c0593a312404f0e7b0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +@@ -252,6 +252,14 @@ public class Commodore + desc = getOriginalOrRewrite( desc ); + } + // Paper end ++ // Paper start - DisplaySlot ++ if (owner.equals("org/bukkit/scoreboard/DisplaySlot")) { ++ if (name.startsWith("SIDEBAR_") && !name.startsWith("SIDEBAR_TEAM_")) { ++ super.visitFieldInsn(opcode, owner, name.replace("SIDEBAR_", "SIDEBAR_TEAM_"), desc); ++ return; ++ } ++ } ++ // Paper end - DisplaySlot + + if ( owner.equals( "org/bukkit/block/Biome" ) ) + { +diff --git a/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bb41a2f2c0a5e3b4cb3fe1b584e0ceb7a7116afb +--- /dev/null ++++ b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.scoreboard; ++ ++import net.minecraft.world.scores.Scoreboard; ++import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations; ++import org.bukkit.scoreboard.DisplaySlot; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertNotEquals; ++import static org.junit.Assert.assertNotNull; ++ ++public class DisplaySlotTest { ++ ++ @Test ++ public void testBukkitToMinecraftDisplaySlots() { ++ for (DisplaySlot value : DisplaySlot.values()) { ++ assertNotEquals(-1, CraftScoreboardTranslations.fromBukkitSlot(value)); ++ } ++ } ++ ++ @Test ++ public void testMinecraftToBukkitDisplaySlots() { ++ for (String name : Scoreboard.getDisplaySlotNames()) { ++ assertNotNull(CraftScoreboardTranslations.toBukkitSlot(Scoreboard.getDisplaySlotByName(name))); ++ } ++ } ++} diff --git a/patches/server/0706-More-CommandBlock-API.patch b/patches/server/0706-More-CommandBlock-API.patch deleted file mode 100644 index 3cbd2436d3..0000000000 --- a/patches/server/0706-More-CommandBlock-API.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 23 Sep 2021 10:40:09 -0700 -Subject: [PATCH] More CommandBlock API - - -diff --git a/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b42306f17bf8850a13a51067c2d19e7583187e5 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java -@@ -0,0 +1,33 @@ -+package io.papermc.paper.commands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import io.papermc.paper.command.CommandBlockHolder; -+import net.kyori.adventure.text.Component; -+import net.minecraft.world.level.BaseCommandBlock; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public interface PaperCommandBlockHolder extends CommandBlockHolder { -+ -+ BaseCommandBlock getCommandBlockHandle(); -+ -+ @Override -+ default @NotNull Component lastOutput() { -+ return PaperAdventure.asAdventure(getCommandBlockHandle().getLastOutput()); -+ } -+ -+ @Override -+ default void lastOutput(@Nullable Component lastOutput) { -+ getCommandBlockHandle().setLastOutput(PaperAdventure.asVanilla(lastOutput)); -+ } -+ -+ @Override -+ default int getSuccessCount() { -+ return getCommandBlockHandle().getSuccessCount(); -+ } -+ -+ @Override -+ default void setSuccessCount(int successCount) { -+ getCommandBlockHandle().setSuccessCount(successCount); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java -index c4ea6760f489e6171f9e6e170160b932597f842f..245a9b062a0033a39fd42f3ff94350192570aec4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java -@@ -5,7 +5,7 @@ import org.bukkit.World; - import org.bukkit.block.CommandBlock; - import org.bukkit.craftbukkit.util.CraftChatMessage; - --public class CraftCommandBlock extends CraftBlockEntityState implements CommandBlock { -+public class CraftCommandBlock extends CraftBlockEntityState implements CommandBlock, io.papermc.paper.commands.PaperCommandBlockHolder { - - public CraftCommandBlock(World world, CommandBlockEntity tileEntity) { - super(world, tileEntity); -@@ -41,5 +41,10 @@ public class CraftCommandBlock extends CraftBlockEntityState - public void name(net.kyori.adventure.text.Component name) { - getSnapshot().getCommandBlock().setName(name == null ? net.minecraft.network.chat.Component.literal("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name)); - } -+ -+ @Override -+ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() { -+ return getSnapshot().getCommandBlock(); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java -index f9863e138994f6c7a7975a852f106faa96d52315..b709a1d909c189f60d0c3aa97b4b96623e7c1db0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java -@@ -14,7 +14,7 @@ import org.bukkit.permissions.PermissionAttachment; - import org.bukkit.permissions.PermissionAttachmentInfo; - import org.bukkit.plugin.Plugin; - --public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart { -+public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart, io.papermc.paper.commands.PaperCommandBlockHolder { - private final PermissibleBase perm = new PermissibleBase(this); - - public CraftMinecartCommand(CraftServer server, MinecartCommandBlock entity) { -@@ -70,6 +70,17 @@ public class CraftMinecartCommand extends CraftMinecart implements CommandMineca - public net.kyori.adventure.text.@org.jetbrains.annotations.NotNull Component name() { - return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getHandle().getCommandBlock().getName()); - } -+ -+ @Override -+ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() { -+ return getHandle().getCommandBlock(); -+ } -+ -+ @Override -+ public void lastOutput(net.kyori.adventure.text.Component lastOutput) { -+ io.papermc.paper.commands.PaperCommandBlockHolder.super.lastOutput(lastOutput); -+ getCommandBlockHandle().onUpdated(); -+ } - // Paper end - - @Override diff --git a/patches/server/0707-Add-back-EntityPortalExitEvent.patch b/patches/server/0707-Add-back-EntityPortalExitEvent.patch new file mode 100644 index 0000000000..3dec60a42f --- /dev/null +++ b/patches/server/0707-Add-back-EntityPortalExitEvent.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 16 May 2021 09:39:46 -0700 +Subject: [PATCH] Add back EntityPortalExitEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 307f3e4fa66bdf4f9b0864a7ba4aaa29f6e3b8d1..f212451892c73320cc9fa0e08b059948cc1b3162 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3112,6 +3112,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } else { + // CraftBukkit start + worldserver = shapedetectorshape.world; ++ // Paper start - Call EntityPortalExitEvent ++ CraftEntity bukkitEntity = this.getBukkitEntity(); ++ Vec3 position = shapedetectorshape.pos; ++ float yaw = shapedetectorshape.yRot; ++ float pitch = bukkitEntity.getLocation().getPitch(); // Keep entity pitch as per moveTo line below ++ Vec3 velocity = shapedetectorshape.speed; ++ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(bukkitEntity, ++ bukkitEntity.getLocation(), new Location(worldserver.getWorld(), position.x, position.y, position.z, yaw, pitch), ++ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(shapedetectorshape.speed)); ++ if (event.callEvent() && event.getTo() != null && this.isAlive()) { ++ worldserver = ((CraftWorld) event.getTo().getWorld()).getHandle(); ++ position = new Vec3(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()); ++ yaw = event.getTo().getYaw(); ++ pitch = event.getTo().getPitch(); ++ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); ++ } ++ // Paper end + if (worldserver == this.level) { + // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in + this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot); +@@ -3131,8 +3148,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + if (entity != null) { + entity.restoreFrom(this); +- entity.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, entity.getXRot()); +- entity.setDeltaMovement(shapedetectorshape.speed); ++ entity.moveTo(position.x, position.y, position.z, yaw, pitch); // Paper - use EntityPortalExitEvent values ++ entity.setDeltaMovement(velocity); // Paper - use EntityPortalExitEvent values + worldserver.addDuringTeleport(entity); + if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit + ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit diff --git a/patches/server/0707-Add-missing-team-sidebar-display-slots.patch b/patches/server/0707-Add-missing-team-sidebar-display-slots.patch deleted file mode 100644 index ee0f1ced64..0000000000 --- a/patches/server/0707-Add-missing-team-sidebar-display-slots.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 1 Oct 2021 08:04:39 -0700 -Subject: [PATCH] Add missing team sidebar display slots - - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -index e2d3fe9af7d3bd82bee519b20e141cd58f68bbd6..944a4fee237730c0d89567aaa6ddf268467aa0e0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java -@@ -7,36 +7,23 @@ import org.bukkit.scoreboard.DisplaySlot; - import org.bukkit.scoreboard.RenderType; - - public final class CraftScoreboardTranslations { -- static final int MAX_DISPLAY_SLOT = 19; -+ static final int MAX_DISPLAY_SLOT = Scoreboard.getDisplaySlotNames().length; // Paper -+ @Deprecated // Paper - static final ImmutableBiMap SLOTS = ImmutableBiMap.builder() - .put(DisplaySlot.BELOW_NAME, "belowName") - .put(DisplaySlot.PLAYER_LIST, "list") - .put(DisplaySlot.SIDEBAR, "sidebar") -- .put(DisplaySlot.SIDEBAR_BLACK, "sidebar.team.black") -- .put(DisplaySlot.SIDEBAR_DARK_BLUE, "sidebar.team.dark_blue") -- .put(DisplaySlot.SIDEBAR_DARK_GREEN, "sidebar.team.dark_green") -- .put(DisplaySlot.SIDEBAR_DARK_AQUA, "sidebar.team.dark_aqua") -- .put(DisplaySlot.SIDEBAR_DARK_RED, "sidebar.team.dark_red") -- .put(DisplaySlot.SIDEBAR_DARK_PURPLE, "sidebar.team.dark_purple") -- .put(DisplaySlot.SIDEBAR_GOLD, "sidebar.team.gold") -- .put(DisplaySlot.SIDEBAR_GRAY, "sidebar.team.gray") -- .put(DisplaySlot.SIDEBAR_DARK_GRAY, "sidebar.team.dark_gray") -- .put(DisplaySlot.SIDEBAR_BLUE, "sidebar.team.blue") -- .put(DisplaySlot.SIDEBAR_GREEN, "sidebar.team.green") -- .put(DisplaySlot.SIDEBAR_AQUA, "sidebar.team.aqua") -- .put(DisplaySlot.SIDEBAR_RED, "sidebar.team.red") -- .put(DisplaySlot.SIDEBAR_LIGHT_PURPLE, "sidebar.team.light_purple") -- .put(DisplaySlot.SIDEBAR_YELLOW, "sidebar.team.yellow") -- .put(DisplaySlot.SIDEBAR_WHITE, "sidebar.team.white") - .buildOrThrow(); - - private CraftScoreboardTranslations() {} - - public static DisplaySlot toBukkitSlot(int i) { -+ if (true) return org.bukkit.scoreboard.DisplaySlot.NAMES.value(Scoreboard.getDisplaySlotName(i)); // Paper - return CraftScoreboardTranslations.SLOTS.inverse().get(Scoreboard.getDisplaySlotName(i)); - } - - public static int fromBukkitSlot(DisplaySlot slot) { -+ if (true) return Scoreboard.getDisplaySlotByName(slot.getId()); // Paper - return Scoreboard.getDisplaySlotByName(CraftScoreboardTranslations.SLOTS.get(slot)); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -index 89a32de0a3fbb7465b078b2aa1cc1d156a4a174d..27646d963bd1158f3fe0a9c0593a312404f0e7b0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java -@@ -252,6 +252,14 @@ public class Commodore - desc = getOriginalOrRewrite( desc ); - } - // Paper end -+ // Paper start - DisplaySlot -+ if (owner.equals("org/bukkit/scoreboard/DisplaySlot")) { -+ if (name.startsWith("SIDEBAR_") && !name.startsWith("SIDEBAR_TEAM_")) { -+ super.visitFieldInsn(opcode, owner, name.replace("SIDEBAR_", "SIDEBAR_TEAM_"), desc); -+ return; -+ } -+ } -+ // Paper end - DisplaySlot - - if ( owner.equals( "org/bukkit/block/Biome" ) ) - { -diff --git a/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bb41a2f2c0a5e3b4cb3fe1b584e0ceb7a7116afb ---- /dev/null -+++ b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.scoreboard; -+ -+import net.minecraft.world.scores.Scoreboard; -+import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations; -+import org.bukkit.scoreboard.DisplaySlot; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertNotEquals; -+import static org.junit.Assert.assertNotNull; -+ -+public class DisplaySlotTest { -+ -+ @Test -+ public void testBukkitToMinecraftDisplaySlots() { -+ for (DisplaySlot value : DisplaySlot.values()) { -+ assertNotEquals(-1, CraftScoreboardTranslations.fromBukkitSlot(value)); -+ } -+ } -+ -+ @Test -+ public void testMinecraftToBukkitDisplaySlots() { -+ for (String name : Scoreboard.getDisplaySlotNames()) { -+ assertNotNull(CraftScoreboardTranslations.toBukkitSlot(Scoreboard.getDisplaySlotByName(name))); -+ } -+ } -+} diff --git a/patches/server/0708-Add-back-EntityPortalExitEvent.patch b/patches/server/0708-Add-back-EntityPortalExitEvent.patch deleted file mode 100644 index 79c8ecd49d..0000000000 --- a/patches/server/0708-Add-back-EntityPortalExitEvent.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 16 May 2021 09:39:46 -0700 -Subject: [PATCH] Add back EntityPortalExitEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 460b51374e41c3d88e1c3641fb9f2f0205399b54..3f4436a2257376f604926ff35c8589ba59c859e2 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3098,6 +3098,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } else { - // CraftBukkit start - worldserver = shapedetectorshape.world; -+ // Paper start - Call EntityPortalExitEvent -+ CraftEntity bukkitEntity = this.getBukkitEntity(); -+ Vec3 position = shapedetectorshape.pos; -+ float yaw = shapedetectorshape.yRot; -+ float pitch = bukkitEntity.getLocation().getPitch(); // Keep entity pitch as per moveTo line below -+ Vec3 velocity = shapedetectorshape.speed; -+ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(bukkitEntity, -+ bukkitEntity.getLocation(), new Location(worldserver.getWorld(), position.x, position.y, position.z, yaw, pitch), -+ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(shapedetectorshape.speed)); -+ if (event.callEvent() && event.getTo() != null && this.isAlive()) { -+ worldserver = ((CraftWorld) event.getTo().getWorld()).getHandle(); -+ position = new Vec3(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()); -+ yaw = event.getTo().getYaw(); -+ pitch = event.getTo().getPitch(); -+ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter()); -+ } -+ // Paper end - if (worldserver == this.level) { - // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in - this.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot); -@@ -3117,8 +3134,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - - if (entity != null) { - entity.restoreFrom(this); -- entity.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, entity.getXRot()); -- entity.setDeltaMovement(shapedetectorshape.speed); -+ entity.moveTo(position.x, position.y, position.z, yaw, pitch); // Paper - use EntityPortalExitEvent values -+ entity.setDeltaMovement(velocity); // Paper - use EntityPortalExitEvent values - worldserver.addDuringTeleport(entity); - if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit - ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit diff --git a/patches/server/0708-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0708-Add-methods-to-find-targets-for-lightning-strikes.patch new file mode 100644 index 0000000000..6880c6cd68 --- /dev/null +++ b/patches/server/0708-Add-methods-to-find-targets-for-lightning-strikes.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jakub Zacek +Date: Mon, 4 Oct 2021 10:16:44 +0200 +Subject: [PATCH] Add methods to find targets for lightning strikes + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index d0c4c9c172c8caa3eaf6c0bf56a8be9f16d8c4e7..10421fdac40c756c52abc9430f7149a9f58efbfb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -780,6 +780,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + protected BlockPos findLightningTargetAround(BlockPos pos) { ++ // Paper start ++ return this.findLightningTargetAround(pos, false); ++ } ++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { ++ // Paper end + BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); + Optional optional = this.findLightningRod(blockposition1); + +@@ -794,6 +799,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (!list.isEmpty()) { + return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); + } else { ++ if (returnNullWhenNoTarget) return null; // Paper + if (blockposition1.getY() == this.getMinBuildHeight() - 1) { + blockposition1 = blockposition1.above(2); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 2bed5b4eaeac4e48df606b755489a3ca5ffc895e..38a74d683aec58a419af84111844a205596524b2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -696,6 +696,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { + return (LightningStrike) lightning.getBukkitEntity(); + } + ++ // Paper start ++ @Override ++ public Location findLightningRod(Location location) { ++ return this.world.findLightningRod(net.minecraft.server.MCUtil.toBlockPosition(location)) ++ .map(blockPos -> net.minecraft.server.MCUtil.toLocation(this.world, blockPos) ++ // get the actual rod pos ++ .subtract(0, 1, 0)) ++ .orElse(null); ++ } ++ ++ @Override ++ public Location findLightningTarget(Location location) { ++ final BlockPos pos = this.world.findLightningTargetAround(net.minecraft.server.MCUtil.toBlockPosition(location), true); ++ return pos == null ? null : net.minecraft.server.MCUtil.toLocation(this.world, pos); ++ } ++ // Paper end ++ + @Override + public boolean generateTree(Location loc, TreeType type) { + return generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0709-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0709-Add-methods-to-find-targets-for-lightning-strikes.patch deleted file mode 100644 index 6880c6cd68..0000000000 --- a/patches/server/0709-Add-methods-to-find-targets-for-lightning-strikes.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jakub Zacek -Date: Mon, 4 Oct 2021 10:16:44 +0200 -Subject: [PATCH] Add methods to find targets for lightning strikes - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index d0c4c9c172c8caa3eaf6c0bf56a8be9f16d8c4e7..10421fdac40c756c52abc9430f7149a9f58efbfb 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -780,6 +780,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - protected BlockPos findLightningTargetAround(BlockPos pos) { -+ // Paper start -+ return this.findLightningTargetAround(pos, false); -+ } -+ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { -+ // Paper end - BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); - Optional optional = this.findLightningRod(blockposition1); - -@@ -794,6 +799,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!list.isEmpty()) { - return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); - } else { -+ if (returnNullWhenNoTarget) return null; // Paper - if (blockposition1.getY() == this.getMinBuildHeight() - 1) { - blockposition1 = blockposition1.above(2); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 2bed5b4eaeac4e48df606b755489a3ca5ffc895e..38a74d683aec58a419af84111844a205596524b2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -696,6 +696,23 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (LightningStrike) lightning.getBukkitEntity(); - } - -+ // Paper start -+ @Override -+ public Location findLightningRod(Location location) { -+ return this.world.findLightningRod(net.minecraft.server.MCUtil.toBlockPosition(location)) -+ .map(blockPos -> net.minecraft.server.MCUtil.toLocation(this.world, blockPos) -+ // get the actual rod pos -+ .subtract(0, 1, 0)) -+ .orElse(null); -+ } -+ -+ @Override -+ public Location findLightningTarget(Location location) { -+ final BlockPos pos = this.world.findLightningTargetAround(net.minecraft.server.MCUtil.toBlockPosition(location), true); -+ return pos == null ? null : net.minecraft.server.MCUtil.toLocation(this.world, pos); -+ } -+ // Paper end -+ - @Override - public boolean generateTree(Location loc, TreeType type) { - return generateTree(loc, CraftWorld.rand, type); diff --git a/patches/server/0709-Get-entity-default-attributes.patch b/patches/server/0709-Get-entity-default-attributes.patch new file mode 100644 index 0000000000..c2cbc0160d --- /dev/null +++ b/patches/server/0709-Get-entity-default-attributes.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 20 Aug 2021 13:03:21 -0700 +Subject: [PATCH] Get entity default attributes + + +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java +@@ -0,0 +1,30 @@ ++package io.papermc.paper.attribute; ++ ++import net.minecraft.world.entity.ai.attributes.AttributeInstance; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; ++ ++import java.util.Collection; ++ ++public class UnmodifiableAttributeInstance extends CraftAttributeInstance { ++ ++ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { ++ super(handle, attribute); ++ } ++ ++ @Override ++ public void setBaseValue(double d) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void addModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++ ++ @Override ++ public void removeModifier(AttributeModifier modifier) { ++ throw new UnsupportedOperationException("Cannot modify default attributes"); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ecba0b02c2813a890aecc558698787946d2ccb8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java +@@ -0,0 +1,43 @@ ++package io.papermc.paper.attribute; ++ ++import com.google.common.collect.Maps; ++import com.google.common.util.concurrent.Callables; ++import com.google.common.util.concurrent.Runnables; ++import net.minecraft.world.entity.ai.attributes.AttributeSupplier; ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; ++import org.bukkit.craftbukkit.attribute.CraftAttributeMap; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.Map; ++import java.util.function.Consumer; ++import java.util.function.Function; ++ ++public class UnmodifiableAttributeMap implements Attributable { ++ ++ ++ private final Map attributes = Maps.newHashMap(); ++ private final AttributeSupplier handle; ++ ++ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { ++ this.handle = handle; ++ } ++ ++ @Override ++ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { ++ var nmsAttribute = CraftAttributeMap.toMinecraft(attribute); ++ var nmsAttributeInstance = this.handle.instances.get(nmsAttribute); ++ if (nmsAttribute == null) { ++ return null; ++ } ++ return new UnmodifiableAttributeInstance(nmsAttributeInstance, attribute); ++ } ++ ++ @Override ++ public void registerAttribute(@NotNull Attribute attribute) { ++ throw new UnsupportedOperationException("Cannot register new attributes here"); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 6fd3bbc36cb6e270a10f778fe2764823f90cca9c..51ecfd4c4afe6dfc42c3aa85e6fc55d0e965a5dc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -562,6 +562,18 @@ public final class CraftMagicNumbers implements UnsafeValues { + public int getProtocolVersion() { + return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); + } ++ ++ @Override ++ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ } ++ ++ @Override ++ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { ++ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); ++ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); ++ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); ++ } + // Paper end + + /** +diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7b999deba66aa6d22cd7520f6c13550a44ca327d +--- /dev/null ++++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java +@@ -0,0 +1,39 @@ ++package io.papermc.paper.attribute; ++ ++import org.bukkit.attribute.Attributable; ++import org.bukkit.attribute.Attribute; ++import org.bukkit.attribute.AttributeInstance; ++import org.bukkit.attribute.AttributeModifier; ++import org.bukkit.entity.EntityType; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertFalse; ++import static org.junit.Assert.assertNotNull; ++import static org.junit.Assert.assertThrows; ++import static org.junit.Assert.assertTrue; ++ ++public class EntityTypeAttributesTest extends AbstractTestingBase { ++ ++ @Test ++ public void testIllegalEntity() { ++ assertFalse(EntityType.EGG.hasDefaultAttributes()); ++ assertThrows(IllegalArgumentException.class, () -> EntityType.EGG.getDefaultAttributes()); ++ } ++ ++ @Test ++ public void testLegalEntity() { ++ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); ++ EntityType.ZOMBIE.getDefaultAttributes(); ++ } ++ ++ @Test ++ public void testUnmodifiabilityOfAttributable() { ++ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); ++ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); ++ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); ++ assertNotNull(instance); ++ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); ++ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); ++ } ++} diff --git a/patches/server/0710-Get-entity-default-attributes.patch b/patches/server/0710-Get-entity-default-attributes.patch deleted file mode 100644 index c2cbc0160d..0000000000 --- a/patches/server/0710-Get-entity-default-attributes.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 20 Aug 2021 13:03:21 -0700 -Subject: [PATCH] Get entity default attributes - - -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java -@@ -0,0 +1,30 @@ -+package io.papermc.paper.attribute; -+ -+import net.minecraft.world.entity.ai.attributes.AttributeInstance; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; -+ -+import java.util.Collection; -+ -+public class UnmodifiableAttributeInstance extends CraftAttributeInstance { -+ -+ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) { -+ super(handle, attribute); -+ } -+ -+ @Override -+ public void setBaseValue(double d) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void addModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+ -+ @Override -+ public void removeModifier(AttributeModifier modifier) { -+ throw new UnsupportedOperationException("Cannot modify default attributes"); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ecba0b02c2813a890aecc558698787946d2ccb8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java -@@ -0,0 +1,43 @@ -+package io.papermc.paper.attribute; -+ -+import com.google.common.collect.Maps; -+import com.google.common.util.concurrent.Callables; -+import com.google.common.util.concurrent.Runnables; -+import net.minecraft.world.entity.ai.attributes.AttributeSupplier; -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; -+import org.bukkit.craftbukkit.attribute.CraftAttributeMap; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.Map; -+import java.util.function.Consumer; -+import java.util.function.Function; -+ -+public class UnmodifiableAttributeMap implements Attributable { -+ -+ -+ private final Map attributes = Maps.newHashMap(); -+ private final AttributeSupplier handle; -+ -+ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) { -+ this.handle = handle; -+ } -+ -+ @Override -+ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) { -+ var nmsAttribute = CraftAttributeMap.toMinecraft(attribute); -+ var nmsAttributeInstance = this.handle.instances.get(nmsAttribute); -+ if (nmsAttribute == null) { -+ return null; -+ } -+ return new UnmodifiableAttributeInstance(nmsAttributeInstance, attribute); -+ } -+ -+ @Override -+ public void registerAttribute(@NotNull Attribute attribute) { -+ throw new UnsupportedOperationException("Cannot register new attributes here"); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 6fd3bbc36cb6e270a10f778fe2764823f90cca9c..51ecfd4c4afe6dfc42c3aa85e6fc55d0e965a5dc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -562,6 +562,18 @@ public final class CraftMagicNumbers implements UnsafeValues { - public int getProtocolVersion() { - return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion(); - } -+ -+ @Override -+ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ } -+ -+ @Override -+ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) { -+ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes"); -+ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); -+ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); -+ } - // Paper end - - /** -diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7b999deba66aa6d22cd7520f6c13550a44ca327d ---- /dev/null -+++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java -@@ -0,0 +1,39 @@ -+package io.papermc.paper.attribute; -+ -+import org.bukkit.attribute.Attributable; -+import org.bukkit.attribute.Attribute; -+import org.bukkit.attribute.AttributeInstance; -+import org.bukkit.attribute.AttributeModifier; -+import org.bukkit.entity.EntityType; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertFalse; -+import static org.junit.Assert.assertNotNull; -+import static org.junit.Assert.assertThrows; -+import static org.junit.Assert.assertTrue; -+ -+public class EntityTypeAttributesTest extends AbstractTestingBase { -+ -+ @Test -+ public void testIllegalEntity() { -+ assertFalse(EntityType.EGG.hasDefaultAttributes()); -+ assertThrows(IllegalArgumentException.class, () -> EntityType.EGG.getDefaultAttributes()); -+ } -+ -+ @Test -+ public void testLegalEntity() { -+ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes()); -+ EntityType.ZOMBIE.getDefaultAttributes(); -+ } -+ -+ @Test -+ public void testUnmodifiabilityOfAttributable() { -+ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes(); -+ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.GENERIC_ATTACK_DAMAGE)); -+ AttributeInstance instance = attributable.getAttribute(Attribute.GENERIC_FOLLOW_RANGE); -+ assertNotNull(instance); -+ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER))); -+ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2)); -+ } -+} diff --git a/patches/server/0710-Left-handed-API.patch b/patches/server/0710-Left-handed-API.patch new file mode 100644 index 0000000000..51c41178e9 --- /dev/null +++ b/patches/server/0710-Left-handed-API.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 14 Oct 2021 12:36:58 -0500 +Subject: [PATCH] Left handed API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +index e19014a6e0d293973574c40c90c556aca17e0b0d..9187f92f1708478dfffcd9de65898fd409a1b0c1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java +@@ -139,5 +139,15 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { + public int getMaxHeadPitch() { + return getHandle().getMaxHeadXRot(); + } ++ ++ @Override ++ public boolean isLeftHanded() { ++ return getHandle().isLeftHanded(); ++ } ++ ++ @Override ++ public void setLeftHanded(boolean leftHanded) { ++ getHandle().setLeftHanded(leftHanded); ++ } + // Paper end + } diff --git a/patches/server/0711-Add-advancement-display-API.patch b/patches/server/0711-Add-advancement-display-API.patch new file mode 100644 index 0000000000..dd860e3b4d --- /dev/null +++ b/patches/server/0711-Add-advancement-display-API.patch @@ -0,0 +1,159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: syldium +Date: Fri, 9 Jul 2021 18:50:40 +0200 +Subject: [PATCH] Add advancement display API + + +diff --git a/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0567e500c40d3d424ddc19062c4f6da902e8586e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java +@@ -0,0 +1,63 @@ ++package io.papermc.paper.advancement; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.Component; ++import net.minecraft.advancements.DisplayInfo; ++import net.minecraft.advancements.FrameType; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.inventory.ItemStack; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public record PaperAdvancementDisplay(DisplayInfo handle) implements AdvancementDisplay { ++ ++ @Override ++ public @NotNull Frame frame() { ++ return asPaperFrame(this.handle.getFrame()); ++ } ++ ++ @Override ++ public @NotNull Component title() { ++ return PaperAdventure.asAdventure(this.handle.getTitle()); ++ } ++ ++ @Override ++ public @NotNull Component description() { ++ return PaperAdventure.asAdventure(this.handle.getDescription()); ++ } ++ ++ @Override ++ public @NotNull ItemStack icon() { ++ return CraftItemStack.asBukkitCopy(this.handle.getIcon()); ++ } ++ ++ @Override ++ public boolean doesShowToast() { ++ return this.handle.shouldShowToast(); ++ } ++ ++ @Override ++ public boolean doesAnnounceToChat() { ++ return this.handle.shouldAnnounceChat(); ++ } ++ ++ @Override ++ public boolean isHidden() { ++ return this.handle.isHidden(); ++ } ++ ++ @Override ++ public @Nullable NamespacedKey backgroundPath() { ++ return this.handle.getBackground() == null ? null : CraftNamespacedKey.fromMinecraft(this.handle.getBackground()); ++ } ++ ++ public static @NotNull Frame asPaperFrame(@NotNull FrameType frameType) { ++ return switch (frameType) { ++ case TASK -> Frame.TASK; ++ case CHALLENGE -> Frame.CHALLENGE; ++ case GOAL -> Frame.GOAL; ++ }; ++ } ++} +diff --git a/src/main/java/net/minecraft/advancements/DisplayInfo.java b/src/main/java/net/minecraft/advancements/DisplayInfo.java +index db939a754e9308ad68f1b09a970f7a1b00a673bf..538f19f15b553d14ad95f09b1c81359f4c68b17f 100644 +--- a/src/main/java/net/minecraft/advancements/DisplayInfo.java ++++ b/src/main/java/net/minecraft/advancements/DisplayInfo.java +@@ -28,6 +28,7 @@ public class DisplayInfo { + private final boolean hidden; + private float x; + private float y; ++ public final io.papermc.paper.advancement.AdvancementDisplay paper = new io.papermc.paper.advancement.PaperAdvancementDisplay(this); // Paper + + public DisplayInfo(ItemStack icon, Component title, Component description, @Nullable ResourceLocation background, FrameType frame, boolean showToast, boolean announceToChat, boolean hidden) { + this.title = title; +diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java +index c47cae84f3b6970247d78382f48ae8ddbc202b59..0a46eeefa7d704350321828166f0049d497e3e41 100644 +--- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java ++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java +@@ -29,12 +29,33 @@ public class CraftAdvancement implements org.bukkit.advancement.Advancement { + return Collections.unmodifiableCollection(this.handle.getCriteria().keySet()); + } + ++ // Paper start + @Override +- public AdvancementDisplay getDisplay() { +- if (this.handle.getDisplay() == null) { +- return null; ++ public io.papermc.paper.advancement.AdvancementDisplay getDisplay() { ++ return this.handle.getDisplay() == null ? null : this.handle.getDisplay().paper; ++ } ++ ++ @Override ++ public org.bukkit.advancement.Advancement getParent() { ++ return this.handle.getParent() == null ? null : this.handle.getParent().bukkit; ++ } ++ ++ @Override ++ public Collection getChildren() { ++ final var children = com.google.common.collect.ImmutableList.builder(); ++ for (Advancement advancement : this.handle.getChildren()) { ++ children.add(advancement.bukkit); + } ++ return children.build(); ++ } + +- return new CraftAdvancementDisplay(this.handle.getDisplay()); ++ @Override ++ public org.bukkit.advancement.Advancement getRoot() { ++ Advancement advancement = this.handle; ++ while (advancement.getParent() != null) { ++ advancement = advancement.getParent(); ++ } ++ return advancement.bukkit; + } ++ // Paper end + } +diff --git a/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4d043e0e43ef8bb75788e195f95b5a50a51a2a48 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.advancement; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.kyori.adventure.text.format.TextColor; ++import net.minecraft.advancements.FrameType; ++import net.minecraft.network.chat.contents.TranslatableContents; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertEquals; ++ ++public class AdvancementFrameTest { ++ ++ @Test ++ public void test() { ++ for (FrameType nmsFrameType : FrameType.values()) { ++ final TextColor expectedColor = PaperAdventure.asAdventure(nmsFrameType.getChatColor()); ++ final String expectedTranslationKey = ((TranslatableContents) nmsFrameType.getDisplayName().getContents()).getKey(); ++ final var frame = PaperAdvancementDisplay.asPaperFrame(nmsFrameType); ++ assertEquals("The translation keys should be the same", expectedTranslationKey, frame.translationKey()); ++ assertEquals("The frame colors should be the same", expectedColor, frame.color()); ++ assertEquals(nmsFrameType.getName(), AdvancementDisplay.Frame.NAMES.key(frame)); ++ } ++ } ++} diff --git a/patches/server/0711-Left-handed-API.patch b/patches/server/0711-Left-handed-API.patch deleted file mode 100644 index 8a6c26a272..0000000000 --- a/patches/server/0711-Left-handed-API.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 14 Oct 2021 12:36:58 -0500 -Subject: [PATCH] Left handed API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -index d9008049188c1933f2b6b39b9219983ff947b4bf..d775e19402188e35f79affb4ed636b6533f90ab5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java -@@ -130,5 +130,15 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob { - public int getMaxHeadPitch() { - return getHandle().getMaxHeadXRot(); - } -+ -+ @Override -+ public boolean isLeftHanded() { -+ return getHandle().isLeftHanded(); -+ } -+ -+ @Override -+ public void setLeftHanded(boolean leftHanded) { -+ getHandle().setLeftHanded(leftHanded); -+ } - // Paper end - } diff --git a/patches/server/0712-Add-ItemFactory-getMonsterEgg-API.patch b/patches/server/0712-Add-ItemFactory-getMonsterEgg-API.patch new file mode 100644 index 0000000000..1944914081 --- /dev/null +++ b/patches/server/0712-Add-ItemFactory-getMonsterEgg-API.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 14 Oct 2021 12:09:39 -0500 +Subject: [PATCH] Add ItemFactory#getMonsterEgg API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index f3a6a4d97b5be2e75c438a6f7010a8f588afe86c..4a8ac558d308c4e3bc63cdd8d7071a3f9ff3aa81 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -440,5 +440,17 @@ public final class CraftItemFactory implements ItemFactory { + entity.getUniqueId().toString(), + new net.md_5.bungee.api.chat.TextComponent(customName)); + } ++ ++ @Override ++ public ItemStack getSpawnEgg(org.bukkit.entity.EntityType type) { ++ if (type == null) { ++ return null; ++ } ++ String typeId = type.getKey().toString(); ++ net.minecraft.resources.ResourceLocation typeKey = new net.minecraft.resources.ResourceLocation(typeId); ++ net.minecraft.world.entity.EntityType nmsType = net.minecraft.core.Registry.ENTITY_TYPE.get(typeKey); ++ net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.BY_ID.get(nmsType); ++ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); ++ } + // Paper end + } diff --git a/patches/server/0712-Add-advancement-display-API.patch b/patches/server/0712-Add-advancement-display-API.patch deleted file mode 100644 index dd860e3b4d..0000000000 --- a/patches/server/0712-Add-advancement-display-API.patch +++ /dev/null @@ -1,159 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: syldium -Date: Fri, 9 Jul 2021 18:50:40 +0200 -Subject: [PATCH] Add advancement display API - - -diff --git a/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0567e500c40d3d424ddc19062c4f6da902e8586e ---- /dev/null -+++ b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java -@@ -0,0 +1,63 @@ -+package io.papermc.paper.advancement; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.Component; -+import net.minecraft.advancements.DisplayInfo; -+import net.minecraft.advancements.FrameType; -+import org.bukkit.NamespacedKey; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.bukkit.inventory.ItemStack; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public record PaperAdvancementDisplay(DisplayInfo handle) implements AdvancementDisplay { -+ -+ @Override -+ public @NotNull Frame frame() { -+ return asPaperFrame(this.handle.getFrame()); -+ } -+ -+ @Override -+ public @NotNull Component title() { -+ return PaperAdventure.asAdventure(this.handle.getTitle()); -+ } -+ -+ @Override -+ public @NotNull Component description() { -+ return PaperAdventure.asAdventure(this.handle.getDescription()); -+ } -+ -+ @Override -+ public @NotNull ItemStack icon() { -+ return CraftItemStack.asBukkitCopy(this.handle.getIcon()); -+ } -+ -+ @Override -+ public boolean doesShowToast() { -+ return this.handle.shouldShowToast(); -+ } -+ -+ @Override -+ public boolean doesAnnounceToChat() { -+ return this.handle.shouldAnnounceChat(); -+ } -+ -+ @Override -+ public boolean isHidden() { -+ return this.handle.isHidden(); -+ } -+ -+ @Override -+ public @Nullable NamespacedKey backgroundPath() { -+ return this.handle.getBackground() == null ? null : CraftNamespacedKey.fromMinecraft(this.handle.getBackground()); -+ } -+ -+ public static @NotNull Frame asPaperFrame(@NotNull FrameType frameType) { -+ return switch (frameType) { -+ case TASK -> Frame.TASK; -+ case CHALLENGE -> Frame.CHALLENGE; -+ case GOAL -> Frame.GOAL; -+ }; -+ } -+} -diff --git a/src/main/java/net/minecraft/advancements/DisplayInfo.java b/src/main/java/net/minecraft/advancements/DisplayInfo.java -index db939a754e9308ad68f1b09a970f7a1b00a673bf..538f19f15b553d14ad95f09b1c81359f4c68b17f 100644 ---- a/src/main/java/net/minecraft/advancements/DisplayInfo.java -+++ b/src/main/java/net/minecraft/advancements/DisplayInfo.java -@@ -28,6 +28,7 @@ public class DisplayInfo { - private final boolean hidden; - private float x; - private float y; -+ public final io.papermc.paper.advancement.AdvancementDisplay paper = new io.papermc.paper.advancement.PaperAdvancementDisplay(this); // Paper - - public DisplayInfo(ItemStack icon, Component title, Component description, @Nullable ResourceLocation background, FrameType frame, boolean showToast, boolean announceToChat, boolean hidden) { - this.title = title; -diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java -index c47cae84f3b6970247d78382f48ae8ddbc202b59..0a46eeefa7d704350321828166f0049d497e3e41 100644 ---- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java -+++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java -@@ -29,12 +29,33 @@ public class CraftAdvancement implements org.bukkit.advancement.Advancement { - return Collections.unmodifiableCollection(this.handle.getCriteria().keySet()); - } - -+ // Paper start - @Override -- public AdvancementDisplay getDisplay() { -- if (this.handle.getDisplay() == null) { -- return null; -+ public io.papermc.paper.advancement.AdvancementDisplay getDisplay() { -+ return this.handle.getDisplay() == null ? null : this.handle.getDisplay().paper; -+ } -+ -+ @Override -+ public org.bukkit.advancement.Advancement getParent() { -+ return this.handle.getParent() == null ? null : this.handle.getParent().bukkit; -+ } -+ -+ @Override -+ public Collection getChildren() { -+ final var children = com.google.common.collect.ImmutableList.builder(); -+ for (Advancement advancement : this.handle.getChildren()) { -+ children.add(advancement.bukkit); - } -+ return children.build(); -+ } - -- return new CraftAdvancementDisplay(this.handle.getDisplay()); -+ @Override -+ public org.bukkit.advancement.Advancement getRoot() { -+ Advancement advancement = this.handle; -+ while (advancement.getParent() != null) { -+ advancement = advancement.getParent(); -+ } -+ return advancement.bukkit; - } -+ // Paper end - } -diff --git a/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4d043e0e43ef8bb75788e195f95b5a50a51a2a48 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java -@@ -0,0 +1,24 @@ -+package io.papermc.paper.advancement; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.format.TextColor; -+import net.minecraft.advancements.FrameType; -+import net.minecraft.network.chat.contents.TranslatableContents; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertEquals; -+ -+public class AdvancementFrameTest { -+ -+ @Test -+ public void test() { -+ for (FrameType nmsFrameType : FrameType.values()) { -+ final TextColor expectedColor = PaperAdventure.asAdventure(nmsFrameType.getChatColor()); -+ final String expectedTranslationKey = ((TranslatableContents) nmsFrameType.getDisplayName().getContents()).getKey(); -+ final var frame = PaperAdvancementDisplay.asPaperFrame(nmsFrameType); -+ assertEquals("The translation keys should be the same", expectedTranslationKey, frame.translationKey()); -+ assertEquals("The frame colors should be the same", expectedColor, frame.color()); -+ assertEquals(nmsFrameType.getName(), AdvancementDisplay.Frame.NAMES.key(frame)); -+ } -+ } -+} diff --git a/patches/server/0713-Add-ItemFactory-getMonsterEgg-API.patch b/patches/server/0713-Add-ItemFactory-getMonsterEgg-API.patch deleted file mode 100644 index 1944914081..0000000000 --- a/patches/server/0713-Add-ItemFactory-getMonsterEgg-API.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: William Blake Galbreath -Date: Thu, 14 Oct 2021 12:09:39 -0500 -Subject: [PATCH] Add ItemFactory#getMonsterEgg API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index f3a6a4d97b5be2e75c438a6f7010a8f588afe86c..4a8ac558d308c4e3bc63cdd8d7071a3f9ff3aa81 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -440,5 +440,17 @@ public final class CraftItemFactory implements ItemFactory { - entity.getUniqueId().toString(), - new net.md_5.bungee.api.chat.TextComponent(customName)); - } -+ -+ @Override -+ public ItemStack getSpawnEgg(org.bukkit.entity.EntityType type) { -+ if (type == null) { -+ return null; -+ } -+ String typeId = type.getKey().toString(); -+ net.minecraft.resources.ResourceLocation typeKey = new net.minecraft.resources.ResourceLocation(typeId); -+ net.minecraft.world.entity.EntityType nmsType = net.minecraft.core.Registry.ENTITY_TYPE.get(typeKey); -+ net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.BY_ID.get(nmsType); -+ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror(); -+ } - // Paper end - } diff --git a/patches/server/0713-Add-critical-damage-API.patch b/patches/server/0713-Add-critical-damage-API.patch new file mode 100644 index 0000000000..980b435d42 --- /dev/null +++ b/patches/server/0713-Add-critical-damage-API.patch @@ -0,0 +1,135 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: dodison +Date: Mon, 26 Jul 2021 17:32:36 +0200 +Subject: [PATCH] Add critical damage API + + +diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +index 752f9f11227a47b7bed675b93e95af89c6732f63..67bce77093dcc126098731047447da2031e3388d 100644 +--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java ++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java +@@ -64,6 +64,19 @@ public class DamageSource { + return this; + } + // CraftBukkit end ++ // Paper start - add critical damage API ++ private boolean critical; ++ public boolean isCritical() { ++ return this.critical; ++ } ++ public DamageSource critical() { ++ return this.critical(true); ++ } ++ public DamageSource critical(boolean critical) { ++ this.critical = critical; ++ return this; ++ } ++ // Paper end + + public static DamageSource sting(LivingEntity attacker) { + return new EntityDamageSource("sting", attacker); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 92a5aadef076cb905962dab86f32d4ff253fef93..5451b1d61ae2ee4fa461c2a334bfe8f794868030 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -1274,7 +1274,7 @@ public abstract class Player extends LivingEntity { + flag1 = true; + } + +- boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; ++ boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; // Paper - Add critical damage API - conflict on change + + flag2 = flag2 && !level.paperConfig().entities.behavior.disablePlayerCrits; // Paper + flag2 = flag2 && !this.isSprinting(); +@@ -1314,7 +1314,7 @@ public abstract class Player extends LivingEntity { + } + + Vec3 vec3d = target.getDeltaMovement(); +- boolean flag5 = target.hurt(DamageSource.playerAttack(this), f); ++ boolean flag5 = target.hurt(DamageSource.playerAttack(this).critical(flag2), f); // Paper - add critical damage API + + if (flag5) { + if (i > 0) { +@@ -1342,7 +1342,7 @@ public abstract class Player extends LivingEntity { + + if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { + // CraftBukkit start - Only apply knockback if the damage hits +- if (entityliving.hurt(DamageSource.playerAttack(this).sweep(), f4)) { ++ if (entityliving.hurt(DamageSource.playerAttack(this).sweep().critical(flag2), f4)) { // Paper - add critical damage API + entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper + } + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index f02fb03c63975e5c1ccdd848f5727559929cce00..8564ecd20578d907bcfa1b9c149da22e424e254a 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -382,6 +382,7 @@ public abstract class AbstractArrow extends Projectile { + } + } + ++ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API + boolean flag = entity.getType() == EntityType.ENDERMAN; + int k = entity.getRemainingFireTicks(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index 82d8a8c2199673315c7b52e694f798cc59c5f96c..03d389f3458cd77166a0319fa38c7207e8714e6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -979,7 +979,7 @@ public class CraftEventFactory { + } else { + damageCause = DamageCause.ENTITY_EXPLOSION; + } +- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions); ++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API + } + event.setCancelled(cancelled); + +@@ -1008,7 +1008,7 @@ public class CraftEventFactory { + cause = DamageCause.SONIC_BOOM; + } + +- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } else if (source == DamageSource.OUT_OF_WORLD) { + EntityDamageEvent event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.VOID, modifiers, modifierFunctions); + event.setCancelled(cancelled); +@@ -1078,7 +1078,7 @@ public class CraftEventFactory { + } else { + throw new IllegalStateException(String.format("Unhandled damage of %s by %s from %s", entity, damager.getHandle(), source.msgId)); + } +- EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions); ++ EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API + event.setCancelled(cancelled); + CraftEventFactory.callEvent(event); + if (!event.isCancelled()) { +@@ -1123,20 +1123,28 @@ public class CraftEventFactory { + } + + if (cause != null) { +- return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled); ++ return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API + } + + throw new IllegalStateException(String.format("Unhandled damage of %s from %s", entity, source.msgId)); + } + ++ @Deprecated // Paper - Add critical damage API + private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions) { + return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, false); + } + ++ // Paper start - Add critical damage API ++ @Deprecated + private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled) { ++ return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, false, false); ++ } ++ ++ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { ++ // Paper end + EntityDamageEvent event; + if (damager != null) { +- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); ++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API + } else { + event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); + } diff --git a/patches/server/0714-Add-critical-damage-API.patch b/patches/server/0714-Add-critical-damage-API.patch deleted file mode 100644 index 980b435d42..0000000000 --- a/patches/server/0714-Add-critical-damage-API.patch +++ /dev/null @@ -1,135 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: dodison -Date: Mon, 26 Jul 2021 17:32:36 +0200 -Subject: [PATCH] Add critical damage API - - -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index 752f9f11227a47b7bed675b93e95af89c6732f63..67bce77093dcc126098731047447da2031e3388d 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -64,6 +64,19 @@ public class DamageSource { - return this; - } - // CraftBukkit end -+ // Paper start - add critical damage API -+ private boolean critical; -+ public boolean isCritical() { -+ return this.critical; -+ } -+ public DamageSource critical() { -+ return this.critical(true); -+ } -+ public DamageSource critical(boolean critical) { -+ this.critical = critical; -+ return this; -+ } -+ // Paper end - - public static DamageSource sting(LivingEntity attacker) { - return new EntityDamageSource("sting", attacker); -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 92a5aadef076cb905962dab86f32d4ff253fef93..5451b1d61ae2ee4fa461c2a334bfe8f794868030 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1274,7 +1274,7 @@ public abstract class Player extends LivingEntity { - flag1 = true; - } - -- boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; -+ boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity; // Paper - Add critical damage API - conflict on change - - flag2 = flag2 && !level.paperConfig().entities.behavior.disablePlayerCrits; // Paper - flag2 = flag2 && !this.isSprinting(); -@@ -1314,7 +1314,7 @@ public abstract class Player extends LivingEntity { - } - - Vec3 vec3d = target.getDeltaMovement(); -- boolean flag5 = target.hurt(DamageSource.playerAttack(this), f); -+ boolean flag5 = target.hurt(DamageSource.playerAttack(this).critical(flag2), f); // Paper - add critical damage API - - if (flag5) { - if (i > 0) { -@@ -1342,7 +1342,7 @@ public abstract class Player extends LivingEntity { - - if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) { - // CraftBukkit start - Only apply knockback if the damage hits -- if (entityliving.hurt(DamageSource.playerAttack(this).sweep(), f4)) { -+ if (entityliving.hurt(DamageSource.playerAttack(this).sweep().critical(flag2), f4)) { // Paper - add critical damage API - entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)), this); // Paper - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index f02fb03c63975e5c1ccdd848f5727559929cce00..8564ecd20578d907bcfa1b9c149da22e424e254a 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -382,6 +382,7 @@ public abstract class AbstractArrow extends Projectile { - } - } - -+ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API - boolean flag = entity.getType() == EntityType.ENDERMAN; - int k = entity.getRemainingFireTicks(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 82d8a8c2199673315c7b52e694f798cc59c5f96c..03d389f3458cd77166a0319fa38c7207e8714e6f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -979,7 +979,7 @@ public class CraftEventFactory { - } else { - damageCause = DamageCause.ENTITY_EXPLOSION; - } -- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions); -+ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API - } - event.setCancelled(cancelled); - -@@ -1008,7 +1008,7 @@ public class CraftEventFactory { - cause = DamageCause.SONIC_BOOM; - } - -- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } else if (source == DamageSource.OUT_OF_WORLD) { - EntityDamageEvent event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.VOID, modifiers, modifierFunctions); - event.setCancelled(cancelled); -@@ -1078,7 +1078,7 @@ public class CraftEventFactory { - } else { - throw new IllegalStateException(String.format("Unhandled damage of %s by %s from %s", entity, damager.getHandle(), source.msgId)); - } -- EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions); -+ EntityDamageEvent event = new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API - event.setCancelled(cancelled); - CraftEventFactory.callEvent(event); - if (!event.isCancelled()) { -@@ -1123,20 +1123,28 @@ public class CraftEventFactory { - } - - if (cause != null) { -- return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled); -+ return CraftEventFactory.callEntityDamageEvent(null, entity, cause, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API - } - - throw new IllegalStateException(String.format("Unhandled damage of %s from %s", entity, source.msgId)); - } - -+ @Deprecated // Paper - Add critical damage API - private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions) { - return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, false); - } - -+ // Paper start - Add critical damage API -+ @Deprecated - private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled) { -+ return CraftEventFactory.callEntityDamageEvent(damager, damagee, cause, modifiers, modifierFunctions, false, false); -+ } -+ -+ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, Map modifiers, Map> modifierFunctions, boolean cancelled, boolean critical) { -+ // Paper end - EntityDamageEvent event; - if (damager != null) { -- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); -+ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); - } diff --git a/patches/server/0714-Fix-issues-with-mob-conversion.patch b/patches/server/0714-Fix-issues-with-mob-conversion.patch new file mode 100644 index 0000000000..69feb4b3a4 --- /dev/null +++ b/patches/server/0714-Fix-issues-with-mob-conversion.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 24 Oct 2021 20:29:45 -0700 +Subject: [PATCH] Fix issues with mob conversion + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +index ded88c78c9000d4401d293d18b89b07ea46088dd..3a3f3358c4bbd16bdcadc56c6a865ecfb942ad54 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +@@ -86,10 +86,15 @@ public class Skeleton extends AbstractSkeleton { + } + + protected void doFreezeConversion() { +- this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons +- if (!this.isSilent()) { ++ Stray stray = this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons // Paper - track result of conversion ++ if (stray != null && !this.isSilent()) { // Paper - only send event if conversion succeeded + this.level.levelEvent((Player) null, 1048, this.blockPosition(), 0); + } ++ // Paper start - reset conversion time to prevent event spam ++ if (stray == null) { ++ this.conversionTime = 300; ++ } ++ // Paper end + + } + +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java +index 38f56cb4adeac0d7dcad63d0fbd98f17ab6e3b46..5e0e6ab7eaa9825b2f8c90353c30673dd43dd8b8 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java +@@ -105,6 +105,11 @@ public abstract class AbstractPiglin extends Monster { + if (entitypigzombie != null) { + entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); + } ++ // Paper start - reset to prevent event spam ++ else { ++ this.timeInOverworld = 0; ++ } ++ // Paper end + + } + diff --git a/patches/server/0715-Add-isCollidable-methods-to-various-places.patch b/patches/server/0715-Add-isCollidable-methods-to-various-places.patch new file mode 100644 index 0000000000..2b95e405a3 --- /dev/null +++ b/patches/server/0715-Add-isCollidable-methods-to-various-places.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 4 Nov 2021 11:50:40 -0700 +Subject: [PATCH] Add isCollidable methods to various places + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index b2628b1698ef2a235e7b465f09747cafbb133b7a..2881bcb570dc86a7602309e1f540ecd1c7f00284 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -480,6 +480,11 @@ public class CraftBlock implements Block { + public boolean isSolid() { + return getNMS().getMaterial().blocksMotion(); + } ++ ++ @Override ++ public boolean isCollidable() { ++ return getNMS().getBlock().hasCollision; ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index 7b9e943b391c061782fccd2b8d705ceec8db50fe..966ac60daebb7bb211ab8096fc0c5f33db67320a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -324,4 +324,11 @@ public class CraftBlockState implements BlockState { + throw new IllegalStateException("The blockState must be placed to call this method"); + } + } ++ ++ // Paper start ++ @Override ++ public boolean isCollidable() { ++ return this.data.getBlock().hasCollision; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 51ecfd4c4afe6dfc42c3aa85e6fc55d0e965a5dc..0a3d447f87698dc786d6cab6ded27eb1b5780204 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -574,6 +574,12 @@ public final class CraftMagicNumbers implements UnsafeValues { + var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); + return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); + } ++ ++ @Override ++ public boolean isCollidable(Material material) { ++ Preconditions.checkArgument(material.isBlock(), material + " is not a block"); ++ return getBlock(material).hasCollision; ++ } + // Paper end + + /** diff --git a/patches/server/0715-Fix-issues-with-mob-conversion.patch b/patches/server/0715-Fix-issues-with-mob-conversion.patch deleted file mode 100644 index 69feb4b3a4..0000000000 --- a/patches/server/0715-Fix-issues-with-mob-conversion.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 24 Oct 2021 20:29:45 -0700 -Subject: [PATCH] Fix issues with mob conversion - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index ded88c78c9000d4401d293d18b89b07ea46088dd..3a3f3358c4bbd16bdcadc56c6a865ecfb942ad54 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -86,10 +86,15 @@ public class Skeleton extends AbstractSkeleton { - } - - protected void doFreezeConversion() { -- this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons -- if (!this.isSilent()) { -+ Stray stray = this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons // Paper - track result of conversion -+ if (stray != null && !this.isSilent()) { // Paper - only send event if conversion succeeded - this.level.levelEvent((Player) null, 1048, this.blockPosition(), 0); - } -+ // Paper start - reset conversion time to prevent event spam -+ if (stray == null) { -+ this.conversionTime = 300; -+ } -+ // Paper end - - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -index 38f56cb4adeac0d7dcad63d0fbd98f17ab6e3b46..5e0e6ab7eaa9825b2f8c90353c30673dd43dd8b8 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java -@@ -105,6 +105,11 @@ public abstract class AbstractPiglin extends Monster { - if (entitypigzombie != null) { - entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0)); - } -+ // Paper start - reset to prevent event spam -+ else { -+ this.timeInOverworld = 0; -+ } -+ // Paper end - - } - diff --git a/patches/server/0716-Add-isCollidable-methods-to-various-places.patch b/patches/server/0716-Add-isCollidable-methods-to-various-places.patch deleted file mode 100644 index 2b95e405a3..0000000000 --- a/patches/server/0716-Add-isCollidable-methods-to-various-places.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 4 Nov 2021 11:50:40 -0700 -Subject: [PATCH] Add isCollidable methods to various places - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index b2628b1698ef2a235e7b465f09747cafbb133b7a..2881bcb570dc86a7602309e1f540ecd1c7f00284 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -480,6 +480,11 @@ public class CraftBlock implements Block { - public boolean isSolid() { - return getNMS().getMaterial().blocksMotion(); - } -+ -+ @Override -+ public boolean isCollidable() { -+ return getNMS().getBlock().hasCollision; -+ } - // Paper end - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 7b9e943b391c061782fccd2b8d705ceec8db50fe..966ac60daebb7bb211ab8096fc0c5f33db67320a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -324,4 +324,11 @@ public class CraftBlockState implements BlockState { - throw new IllegalStateException("The blockState must be placed to call this method"); - } - } -+ -+ // Paper start -+ @Override -+ public boolean isCollidable() { -+ return this.data.getBlock().hasCollision; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 51ecfd4c4afe6dfc42c3aa85e6fc55d0e965a5dc..0a3d447f87698dc786d6cab6ded27eb1b5780204 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -574,6 +574,12 @@ public final class CraftMagicNumbers implements UnsafeValues { - var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType) net.minecraft.core.Registry.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(bukkitEntityKey))); - return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier); - } -+ -+ @Override -+ public boolean isCollidable(Material material) { -+ Preconditions.checkArgument(material.isBlock(), material + " is not a block"); -+ return getBlock(material).hasCollision; -+ } - // Paper end - - /** diff --git a/patches/server/0716-Goat-ram-API.patch b/patches/server/0716-Goat-ram-API.patch new file mode 100644 index 0000000000..426746ea8f --- /dev/null +++ b/patches/server/0716-Goat-ram-API.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seggan +Date: Thu, 5 Aug 2021 13:10:27 -0400 +Subject: [PATCH] Goat ram API + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 1935db7bd6d0976fd0bb9e482cd8044b79b0a452..56dd01801f56c56d07101e7e22b58ac059f5f07f 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -359,4 +359,15 @@ public class Goat extends Animal { + public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { + return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); + } ++ ++ // Paper start - Goat ram API ++ public void ram(net.minecraft.world.entity.LivingEntity entity) { ++ Brain brain = this.getBrain(); ++ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); ++ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); ++ brain.eraseMemory(MemoryModuleType.BREED_TARGET); ++ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); ++ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +index 9142b132f045af55b6bb436a39a9ca416bcfc698..e4be28b130e35ea263f85b3157898cd3a7e80561 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java +@@ -54,4 +54,11 @@ public class CraftGoat extends CraftAnimals implements Goat { + public void setScreaming(boolean screaming) { + this.getHandle().setScreamingGoat(screaming); + } ++ ++ // Paper start - Goat ram API ++ @Override ++ public void ram(@org.jetbrains.annotations.NotNull org.bukkit.entity.LivingEntity entity) { ++ this.getHandle().ram(((CraftLivingEntity) entity).getHandle()); ++ } ++ // Paper end + } diff --git a/patches/server/0717-Add-API-for-resetting-a-single-score.patch b/patches/server/0717-Add-API-for-resetting-a-single-score.patch new file mode 100644 index 0000000000..c2e1fd93b9 --- /dev/null +++ b/patches/server/0717-Add-API-for-resetting-a-single-score.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: booky10 +Date: Fri, 5 Nov 2021 21:01:36 +0100 +Subject: [PATCH] Add API for resetting a single score + +It was only possible to reset all scores for a specific entry, instead of resetting only specific scores. + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java +index 2c4ffed5e828f051c44f494a8ed599a8197d7450..3b26793b67282c3a20c023b9c13a2a9b54d5d932 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java +@@ -68,4 +68,12 @@ final class CraftScore implements Score { + public CraftScoreboard getScoreboard() { + return this.objective.getScoreboard(); + } ++ ++ // Paper start ++ @Override ++ public void resetScore() { ++ Scoreboard board = this.objective.checkState().board; ++ board.resetPlayerScore(entry, this.objective.getHandle()); ++ } ++ // Paper end + } diff --git a/patches/server/0717-Goat-ram-API.patch b/patches/server/0717-Goat-ram-API.patch deleted file mode 100644 index 426746ea8f..0000000000 --- a/patches/server/0717-Goat-ram-API.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Seggan -Date: Thu, 5 Aug 2021 13:10:27 -0400 -Subject: [PATCH] Goat ram API - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 1935db7bd6d0976fd0bb9e482cd8044b79b0a452..56dd01801f56c56d07101e7e22b58ac059f5f07f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -359,4 +359,15 @@ public class Goat extends Animal { - public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); - } -+ -+ // Paper start - Goat ram API -+ public void ram(net.minecraft.world.entity.LivingEntity entity) { -+ Brain brain = this.getBrain(); -+ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); -+ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); -+ brain.eraseMemory(MemoryModuleType.BREED_TARGET); -+ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER); -+ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -index 9142b132f045af55b6bb436a39a9ca416bcfc698..e4be28b130e35ea263f85b3157898cd3a7e80561 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java -@@ -54,4 +54,11 @@ public class CraftGoat extends CraftAnimals implements Goat { - public void setScreaming(boolean screaming) { - this.getHandle().setScreamingGoat(screaming); - } -+ -+ // Paper start - Goat ram API -+ @Override -+ public void ram(@org.jetbrains.annotations.NotNull org.bukkit.entity.LivingEntity entity) { -+ this.getHandle().ram(((CraftLivingEntity) entity).getHandle()); -+ } -+ // Paper end - } diff --git a/patches/server/0718-Add-API-for-resetting-a-single-score.patch b/patches/server/0718-Add-API-for-resetting-a-single-score.patch deleted file mode 100644 index c2e1fd93b9..0000000000 --- a/patches/server/0718-Add-API-for-resetting-a-single-score.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: booky10 -Date: Fri, 5 Nov 2021 21:01:36 +0100 -Subject: [PATCH] Add API for resetting a single score - -It was only possible to reset all scores for a specific entry, instead of resetting only specific scores. - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java -index 2c4ffed5e828f051c44f494a8ed599a8197d7450..3b26793b67282c3a20c023b9c13a2a9b54d5d932 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java -@@ -68,4 +68,12 @@ final class CraftScore implements Score { - public CraftScoreboard getScoreboard() { - return this.objective.getScoreboard(); - } -+ -+ // Paper start -+ @Override -+ public void resetScore() { -+ Scoreboard board = this.objective.checkState().board; -+ board.resetPlayerScore(entry, this.objective.getHandle()); -+ } -+ // Paper end - } diff --git a/patches/server/0718-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0718-Add-Raw-Byte-Entity-Serialization.patch new file mode 100644 index 0000000000..cf158a3d0e --- /dev/null +++ b/patches/server/0718-Add-Raw-Byte-Entity-Serialization.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mariell Hoversholm +Date: Sun, 24 Oct 2021 16:20:31 -0400 +Subject: [PATCH] Add Raw Byte Entity Serialization + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f212451892c73320cc9fa0e08b059948cc1b3162..5936fb0d27266e0e91eae5cf49e15f67aeb208bf 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1935,6 +1935,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + } + ++ // Paper start - Entity serialization api ++ public boolean serializeEntity(CompoundTag compound) { ++ List pass = new java.util.ArrayList<>(this.getPassengers()); ++ this.passengers = ImmutableList.of(); ++ boolean result = save(compound); ++ this.passengers = ImmutableList.copyOf(pass); ++ return result; ++ } ++ // Paper end + public boolean save(CompoundTag nbt) { + return this.isPassenger() ? false : this.saveAsPassenger(nbt); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index b30fb13db5524dcd05a155b014b93089af05c994..a1ef4ba683b1721359fb162b5f97ceefd7207184 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1312,5 +1312,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + return set; + } ++ ++ @Override ++ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ Preconditions.checkNotNull(location, "location cannot be null"); ++ Preconditions.checkNotNull(reason, "reason cannot be null"); ++ entity.level = ((CraftWorld) location.getWorld()).getHandle(); ++ entity.setPos(location.getX(), location.getY(), location.getZ()); ++ entity.setRot(location.getYaw(), location.getPitch()); ++ return !entity.valid && entity.level.addFreshEntity(entity, reason); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 0a3d447f87698dc786d6cab6ded27eb1b5780204..d3b37adfe6d66e82db18d94f143af3aba4543f79 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -457,6 +457,30 @@ public final class CraftMagicNumbers implements UnsafeValues { + return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue())); + } + ++ @Override ++ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { ++ Preconditions.checkNotNull(entity, "null cannot be serialized"); ++ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); ++ ++ CompoundTag compound = new CompoundTag(); ++ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); ++ return serializeNbtToBytes(compound); ++ } ++ ++ @Override ++ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { ++ Preconditions.checkNotNull(data, "null cannot be deserialized"); ++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); ++ ++ CompoundTag compound = deserializeNbtFromBytes(data); ++ int dataVersion = compound.getInt("DataVersion"); ++ Dynamic converted = DataFixers.getDataFixer().update(References.ENTITY_TREE, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); ++ compound = (CompoundTag) converted.getValue(); ++ if (!preserveUUID) compound.remove("UUID"); // Generate a new UUID so we don't have to worry about deserializing the same entity twice ++ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) ++ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); ++ } ++ + private byte[] serializeNbtToBytes(CompoundTag compound) { + compound.putInt("DataVersion", getDataVersion()); + java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); diff --git a/patches/server/0719-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0719-Add-Raw-Byte-Entity-Serialization.patch deleted file mode 100644 index a4fbf9a562..0000000000 --- a/patches/server/0719-Add-Raw-Byte-Entity-Serialization.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm -Date: Sun, 24 Oct 2021 16:20:31 -0400 -Subject: [PATCH] Add Raw Byte Entity Serialization - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 3f4436a2257376f604926ff35c8589ba59c859e2..6f3147713b5bec3b2771e1ec52917fd4aee681bc 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1921,6 +1921,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - } - -+ // Paper start - Entity serialization api -+ public boolean serializeEntity(CompoundTag compound) { -+ List pass = new java.util.ArrayList<>(this.getPassengers()); -+ this.passengers = ImmutableList.of(); -+ boolean result = save(compound); -+ this.passengers = ImmutableList.copyOf(pass); -+ return result; -+ } -+ // Paper end - public boolean save(CompoundTag nbt) { - return this.isPassenger() ? false : this.saveAsPassenger(nbt); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index e9828bab16ac05babccfb1fefad85860c1a4768c..f04dcefde38c03403f05305ef044b0ee7608eaa3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1295,5 +1295,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - return set; - } -+ -+ @Override -+ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { -+ Preconditions.checkNotNull(location, "location cannot be null"); -+ Preconditions.checkNotNull(reason, "reason cannot be null"); -+ entity.level = ((CraftWorld) location.getWorld()).getHandle(); -+ entity.setPos(location.getX(), location.getY(), location.getZ()); -+ entity.setRot(location.getYaw(), location.getPitch()); -+ return !entity.valid && entity.level.addFreshEntity(entity, reason); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 0a3d447f87698dc786d6cab6ded27eb1b5780204..d3b37adfe6d66e82db18d94f143af3aba4543f79 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -457,6 +457,30 @@ public final class CraftMagicNumbers implements UnsafeValues { - return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.of((CompoundTag) converted.getValue())); - } - -+ @Override -+ public byte[] serializeEntity(org.bukkit.entity.Entity entity) { -+ Preconditions.checkNotNull(entity, "null cannot be serialized"); -+ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized"); -+ -+ CompoundTag compound = new CompoundTag(); -+ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound); -+ return serializeNbtToBytes(compound); -+ } -+ -+ @Override -+ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) { -+ Preconditions.checkNotNull(data, "null cannot be deserialized"); -+ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing"); -+ -+ CompoundTag compound = deserializeNbtFromBytes(data); -+ int dataVersion = compound.getInt("DataVersion"); -+ Dynamic converted = DataFixers.getDataFixer().update(References.ENTITY_TREE, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, getDataVersion()); -+ compound = (CompoundTag) converted.getValue(); -+ if (!preserveUUID) compound.remove("UUID"); // Generate a new UUID so we don't have to worry about deserializing the same entity twice -+ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle()) -+ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity(); -+ } -+ - private byte[] serializeNbtToBytes(CompoundTag compound) { - compound.putInt("DataVersion", getDataVersion()); - java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); diff --git a/patches/server/0719-Vanilla-command-permission-fixes.patch b/patches/server/0719-Vanilla-command-permission-fixes.patch new file mode 100644 index 0000000000..603bc9d5f0 --- /dev/null +++ b/patches/server/0719-Vanilla-command-permission-fixes.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 25 Aug 2021 13:19:53 -0700 +Subject: [PATCH] Vanilla command permission fixes + +Fixes permission checks for vanilla commands which don't have a +requirement, as well as for namespaced vanilla commands. + +diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java +index 899008b2980d13f1be6280cd8cb959c53a29bebf..f875507241ac6769545e91cd3285232b75b892f0 100644 +--- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java ++++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java +@@ -14,9 +14,17 @@ import java.util.Collections; + import java.util.function.Predicate; + + public abstract class ArgumentBuilder> { ++ // Paper start ++ private static final Predicate DEFAULT_REQUIREMENT = s -> true; ++ ++ @SuppressWarnings("unchecked") ++ public static Predicate defaultRequirement() { ++ return (Predicate) DEFAULT_REQUIREMENT; ++ } ++ // Paper end + private final RootCommandNode arguments = new RootCommandNode<>(); + private Command command; +- private Predicate requirement = s -> true; ++ private Predicate requirement = defaultRequirement(); // Paper + private CommandNode target; + private RedirectModifier modifier = null; + private boolean forks; +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 9c0b2679964f864671ff4041163d1065c8d9cf84..27093aed1f4112a5414671fd5d9c4e683011506d 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -212,7 +212,13 @@ public class Commands { + if (environment.includeIntegrated) { + PublishCommand.register(this.dispatcher); + } +- ++ // Paper start ++ for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { ++ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { ++ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node)); ++ } ++ } ++ // Paper end + // CraftBukkit start + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 5ed34b60a32a2aac214de84c44689fd5a0b00a10..8dca2ad7d25f740941187698d77819af8ebc2805 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -87,7 +87,23 @@ public final class VanillaCommandWrapper extends BukkitCommand { + } + + public static String getPermission(CommandNode vanillaCommand) { +- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName()); ++ // Paper start ++ final String commandName; ++ if (vanillaCommand.getRedirect() == null) { ++ commandName = vanillaCommand.getName(); ++ } else { ++ commandName = vanillaCommand.getRedirect().getName(); ++ } ++ return "minecraft.command." + stripDefaultNamespace(commandName); ++ } ++ ++ private static String stripDefaultNamespace(final String maybeNamespaced) { ++ final String prefix = "minecraft:"; ++ if (maybeNamespaced.startsWith(prefix)) { ++ return maybeNamespaced.substring(prefix.length()); ++ } ++ return maybeNamespaced; ++ // Paper end + } + + private String toDispatcher(String[] args, String name) { diff --git a/patches/server/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch b/patches/server/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch new file mode 100644 index 0000000000..ce98eec585 --- /dev/null +++ b/patches/server/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 02:32:30 -0800 +Subject: [PATCH] Do not allow the server to unload chunks at request of + plugins + +In general the chunk system is not well suited for this behavior, +especially if it is called during a chunk load. The chunks pushed +to be unloaded will simply be unloaded next tick, rather than +immediately. + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index b5f46703e536f8138ff4e6769485c45b35941f9f..f3ab1691948c46477888776d28791ce24e7aa93d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -696,6 +696,7 @@ public class ServerChunkCache extends ChunkSource { + + // CraftBukkit start - modelled on below + public void purgeUnload() { ++ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system + this.level.getProfiler().push("purge"); + this.distanceManager.purgeStaleTickets(); + this.runDistanceManagerUpdates(); diff --git a/patches/server/0720-Vanilla-command-permission-fixes.patch b/patches/server/0720-Vanilla-command-permission-fixes.patch deleted file mode 100644 index 603bc9d5f0..0000000000 --- a/patches/server/0720-Vanilla-command-permission-fixes.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 25 Aug 2021 13:19:53 -0700 -Subject: [PATCH] Vanilla command permission fixes - -Fixes permission checks for vanilla commands which don't have a -requirement, as well as for namespaced vanilla commands. - -diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -index 899008b2980d13f1be6280cd8cb959c53a29bebf..f875507241ac6769545e91cd3285232b75b892f0 100644 ---- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -+++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java -@@ -14,9 +14,17 @@ import java.util.Collections; - import java.util.function.Predicate; - - public abstract class ArgumentBuilder> { -+ // Paper start -+ private static final Predicate DEFAULT_REQUIREMENT = s -> true; -+ -+ @SuppressWarnings("unchecked") -+ public static Predicate defaultRequirement() { -+ return (Predicate) DEFAULT_REQUIREMENT; -+ } -+ // Paper end - private final RootCommandNode arguments = new RootCommandNode<>(); - private Command command; -- private Predicate requirement = s -> true; -+ private Predicate requirement = defaultRequirement(); // Paper - private CommandNode target; - private RedirectModifier modifier = null; - private boolean forks; -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 9c0b2679964f864671ff4041163d1065c8d9cf84..27093aed1f4112a5414671fd5d9c4e683011506d 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -212,7 +212,13 @@ public class Commands { - if (environment.includeIntegrated) { - PublishCommand.register(this.dispatcher); - } -- -+ // Paper start -+ for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { -+ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { -+ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node)); -+ } -+ } -+ // Paper end - // CraftBukkit start - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index 5ed34b60a32a2aac214de84c44689fd5a0b00a10..8dca2ad7d25f740941187698d77819af8ebc2805 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -87,7 +87,23 @@ public final class VanillaCommandWrapper extends BukkitCommand { - } - - public static String getPermission(CommandNode vanillaCommand) { -- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName()); -+ // Paper start -+ final String commandName; -+ if (vanillaCommand.getRedirect() == null) { -+ commandName = vanillaCommand.getName(); -+ } else { -+ commandName = vanillaCommand.getRedirect().getName(); -+ } -+ return "minecraft.command." + stripDefaultNamespace(commandName); -+ } -+ -+ private static String stripDefaultNamespace(final String maybeNamespaced) { -+ final String prefix = "minecraft:"; -+ if (maybeNamespaced.startsWith(prefix)) { -+ return maybeNamespaced.substring(prefix.length()); -+ } -+ return maybeNamespaced; -+ // Paper end - } - - private String toDispatcher(String[] args, String name) { diff --git a/patches/server/0721-Do-not-allow-the-server-to-unload-chunks-at-request-.patch b/patches/server/0721-Do-not-allow-the-server-to-unload-chunks-at-request-.patch deleted file mode 100644 index ce98eec585..0000000000 --- a/patches/server/0721-Do-not-allow-the-server-to-unload-chunks-at-request-.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 02:32:30 -0800 -Subject: [PATCH] Do not allow the server to unload chunks at request of - plugins - -In general the chunk system is not well suited for this behavior, -especially if it is called during a chunk load. The chunks pushed -to be unloaded will simply be unloaded next tick, rather than -immediately. - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index b5f46703e536f8138ff4e6769485c45b35941f9f..f3ab1691948c46477888776d28791ce24e7aa93d 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -696,6 +696,7 @@ public class ServerChunkCache extends ChunkSource { - - // CraftBukkit start - modelled on below - public void purgeUnload() { -+ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system - this.level.getProfiler().push("purge"); - this.distanceManager.purgeStaleTickets(); - this.runDistanceManagerUpdates(); diff --git a/patches/server/0721-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0721-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch new file mode 100644 index 0000000000..c4c28b34ad --- /dev/null +++ b/patches/server/0721-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 03:03:32 -0800 +Subject: [PATCH] Do not run close logic for inventories on chunk unload + +Still call the event and change the active container though. We +want to avoid close logic because it's possible to load the +chunk through it. This should also be OK from a leak prevention/ +state desync POV because the TE is getting unloaded anyways. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 10421fdac40c756c52abc9430f7149a9f58efbfb..39d96b4e3dce6d67b568b7b00456de164f6a7241 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1338,9 +1338,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Spigot Start + for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { + if (tileentity instanceof net.minecraft.world.Container) { ++ // Paper start - this area looks like it can load chunks, change the behavior ++ // chests for example can apply physics to the world ++ // so instead we just change the active container and call the event + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { +- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper ++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity)h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper + } ++ // Paper end + } + } + // Spigot End +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 80e1970f568a74a43e624188a77cfbd28cfa52dd..951ccf3526dc2f5e4e2f16952036683ad132fbe0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1588,6 +1588,18 @@ public class ServerPlayer extends Player { + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } ++ // Paper start - special close for unloaded inventory ++ @Override ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // copied from above ++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end ++ // copied from below ++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); ++ this.containerMenu = this.inventoryMenu; ++ // do not run close logic ++ } ++ // Paper end - special close for unloaded inventory + + public void doCloseContainer() { + this.containerMenu.removed(this); +diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java +index 5451b1d61ae2ee4fa461c2a334bfe8f794868030..9b131f0a827413e9f5d6d0f7491c5481576cb8b1 100644 +--- a/src/main/java/net/minecraft/world/entity/player/Player.java ++++ b/src/main/java/net/minecraft/world/entity/player/Player.java +@@ -506,6 +506,11 @@ public abstract class Player extends LivingEntity { + this.containerMenu = this.inventoryMenu; + } + // Paper end ++ // Paper start - special close for unloaded inventory ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ this.containerMenu = this.inventoryMenu; ++ } ++ // Paper end - special close for unloaded inventory + + public void closeContainer() { + this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0722-Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/server/0722-Correctly-handle-recursion-for-chunkholder-updates.patch new file mode 100644 index 0000000000..17b34c16ac --- /dev/null +++ b/patches/server/0722-Correctly-handle-recursion-for-chunkholder-updates.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 21 Mar 2021 17:32:47 -0700 +Subject: [PATCH] Correctly handle recursion for chunkholder updates + +If a chunk ticket level is brought up while unloading it would +cause a recursive call which would handle the increase but then +the caller would think the chunk would be unloaded. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index c4046b364d1896b781e23c92b241ec73c239d3a0..9c0bf31c3c362632241c95338a3f8d67bbd4fdc5 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -472,8 +472,10 @@ public class ChunkHolder { + playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); + } + ++ protected long updateCount; // Paper - correctly handle recursion + protected void updateFutures(ChunkMap chunkStorage, Executor executor) { + io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper ++ long updateCount = ++this.updateCount; // Paper - correctly handle recursion + ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); + ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); + boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; +@@ -515,6 +517,12 @@ public class ChunkHolder { + + // Run callback right away if the future was already done + chunkStorage.callbackExecutor.run(); ++ // Paper start - correctly handle recursion ++ if (this.updateCount != updateCount) { ++ // something else updated ticket level for us. ++ return; ++ } ++ // Paper end - correctly handle recursion + } + // CraftBukkit end + diff --git a/patches/server/0722-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0722-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch deleted file mode 100644 index c4c28b34ad..0000000000 --- a/patches/server/0722-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 03:03:32 -0800 -Subject: [PATCH] Do not run close logic for inventories on chunk unload - -Still call the event and change the active container though. We -want to avoid close logic because it's possible to load the -chunk through it. This should also be OK from a leak prevention/ -state desync POV because the TE is getting unloaded anyways. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 10421fdac40c756c52abc9430f7149a9f58efbfb..39d96b4e3dce6d67b568b7b00456de164f6a7241 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1338,9 +1338,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot Start - for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { -+ // Paper start - this area looks like it can load chunks, change the behavior -+ // chests for example can apply physics to the world -+ // so instead we just change the active container and call the event - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper -+ ((org.bukkit.craftbukkit.entity.CraftHumanEntity)h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - } -+ // Paper end - } - } - // Spigot End -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 80e1970f568a74a43e624188a77cfbd28cfa52dd..951ccf3526dc2f5e4e2f16952036683ad132fbe0 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1588,6 +1588,18 @@ public class ServerPlayer extends Player { - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -+ // Paper start - special close for unloaded inventory -+ @Override -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // copied from above -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end -+ // copied from below -+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); -+ this.containerMenu = this.inventoryMenu; -+ // do not run close logic -+ } -+ // Paper end - special close for unloaded inventory - - public void doCloseContainer() { - this.containerMenu.removed(this); -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 5451b1d61ae2ee4fa461c2a334bfe8f794868030..9b131f0a827413e9f5d6d0f7491c5481576cb8b1 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -506,6 +506,11 @@ public abstract class Player extends LivingEntity { - this.containerMenu = this.inventoryMenu; - } - // Paper end -+ // Paper start - special close for unloaded inventory -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Paper end - special close for unloaded inventory - - public void closeContainer() { - this.containerMenu = this.inventoryMenu; diff --git a/patches/server/0723-Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/server/0723-Correctly-handle-recursion-for-chunkholder-updates.patch deleted file mode 100644 index 17b34c16ac..0000000000 --- a/patches/server/0723-Correctly-handle-recursion-for-chunkholder-updates.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 21 Mar 2021 17:32:47 -0700 -Subject: [PATCH] Correctly handle recursion for chunkholder updates - -If a chunk ticket level is brought up while unloading it would -cause a recursive call which would handle the increase but then -the caller would think the chunk would be unloaded. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index c4046b364d1896b781e23c92b241ec73c239d3a0..9c0bf31c3c362632241c95338a3f8d67bbd4fdc5 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -472,8 +472,10 @@ public class ChunkHolder { - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); - } - -+ protected long updateCount; // Paper - correctly handle recursion - protected void updateFutures(ChunkMap chunkStorage, Executor executor) { - io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper -+ long updateCount = ++this.updateCount; // Paper - correctly handle recursion - ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); - ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); - boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; -@@ -515,6 +517,12 @@ public class ChunkHolder { - - // Run callback right away if the future was already done - chunkStorage.callbackExecutor.run(); -+ // Paper start - correctly handle recursion -+ if (this.updateCount != updateCount) { -+ // something else updated ticket level for us. -+ return; -+ } -+ // Paper end - correctly handle recursion - } - // CraftBukkit end - diff --git a/patches/server/0723-Fix-GameProfileCache-concurrency.patch b/patches/server/0723-Fix-GameProfileCache-concurrency.patch new file mode 100644 index 0000000000..68ce86c24e --- /dev/null +++ b/patches/server/0723-Fix-GameProfileCache-concurrency.patch @@ -0,0 +1,128 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 11 Jul 2020 05:09:28 -0700 +Subject: [PATCH] Fix GameProfileCache concurrency + +Separate lookup and state access locks prevent lookups +from stalling simple state access/write calls + +diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java +index 5e3bc0590e59770490b1c6c818d99be054214a8a..84bdf4a849b09a90da6c22f4080386e85a53f6b3 100644 +--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java ++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java +@@ -62,6 +62,11 @@ public class GameProfileCache { + @Nullable + private Executor executor; + ++ // Paper start ++ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); ++ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); ++ // Paper end ++ + public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { + this.profileRepository = profileRepository; + this.file = cacheFile; +@@ -69,6 +74,7 @@ public class GameProfileCache { + } + + private void safeAdd(GameProfileCache.GameProfileInfo entry) { ++ try { this.stateLock.lock(); // Paper - allow better concurrency + GameProfile gameprofile = entry.getProfile(); + + entry.setLastAccess(this.getNextOperation()); +@@ -83,6 +89,7 @@ public class GameProfileCache { + if (uuid != null) { + this.profilesByUUID.put(uuid, entry); + } ++ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency + + } + +@@ -138,17 +145,20 @@ public class GameProfileCache { + + // Paper start + public @Nullable GameProfile getProfileIfCached(String name) { ++ try { this.stateLock.lock(); // Paper - allow better concurrency + GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); + if (entry == null) { + return null; + } + entry.setLastAccess(this.getNextOperation()); + return entry.getProfile(); ++ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency + } + // Paper end + + public Optional get(String name) { + String s1 = name.toLowerCase(Locale.ROOT); ++ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - allow better concurrency + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); + boolean flag = false; + +@@ -164,8 +174,12 @@ public class GameProfileCache { + if (usercache_usercacheentry != null) { + usercache_usercacheentry.setLastAccess(this.getNextOperation()); + optional = Optional.of(usercache_usercacheentry.getProfile()); ++ stateLocked = false; this.stateLock.unlock(); // Paper - allow better concurrency + } else { ++ stateLocked = false; this.stateLock.unlock(); // Paper - allow better concurrency ++ try { this.lookupLock.lock(); // Paper - allow better concurrency + optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // Spigot - use correct case for offline players ++ } finally { this.lookupLock.unlock(); } // Paper - allow better concurrency + if (optional.isPresent()) { + this.add((GameProfile) optional.get()); + flag = false; +@@ -177,6 +191,7 @@ public class GameProfileCache { + } + + return optional; ++ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - allow better concurrency + } + + public void getAsync(String username, Consumer> consumer) { +@@ -203,6 +218,7 @@ public class GameProfileCache { + } + + public Optional get(UUID uuid) { ++ try { this.stateLock.lock(); // Paper - allow better concurrency + GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); + + if (usercache_usercacheentry == null) { +@@ -211,6 +227,7 @@ public class GameProfileCache { + usercache_usercacheentry.setLastAccess(this.getNextOperation()); + return Optional.of(usercache_usercacheentry.getProfile()); + } ++ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency + } + + public void setExecutor(Executor executor) { +@@ -291,7 +308,7 @@ public class GameProfileCache { + JsonArray jsonarray = new JsonArray(); + DateFormat dateformat = GameProfileCache.createDateFormat(); + +- this.getTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot ++ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - allow better concurrency + jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat)); + }); + String s = this.gson.toJson(jsonarray); +@@ -332,8 +349,19 @@ public class GameProfileCache { + } + + private Stream getTopMRUProfiles(int limit) { +- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); ++ // Paper start - allow better concurrency ++ return this.listTopMRUProfiles(limit).stream(); ++ } ++ ++ private List listTopMRUProfiles(int limit) { ++ try { ++ this.stateLock.lock(); ++ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList(); ++ } finally { ++ this.stateLock.unlock(); ++ } + } ++ // Paper end + + private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { + JsonObject jsonobject = new JsonObject(); diff --git a/patches/server/0724-Fix-GameProfileCache-concurrency.patch b/patches/server/0724-Fix-GameProfileCache-concurrency.patch deleted file mode 100644 index 68ce86c24e..0000000000 --- a/patches/server/0724-Fix-GameProfileCache-concurrency.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 11 Jul 2020 05:09:28 -0700 -Subject: [PATCH] Fix GameProfileCache concurrency - -Separate lookup and state access locks prevent lookups -from stalling simple state access/write calls - -diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index 5e3bc0590e59770490b1c6c818d99be054214a8a..84bdf4a849b09a90da6c22f4080386e85a53f6b3 100644 ---- a/src/main/java/net/minecraft/server/players/GameProfileCache.java -+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java -@@ -62,6 +62,11 @@ public class GameProfileCache { - @Nullable - private Executor executor; - -+ // Paper start -+ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); -+ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); -+ // Paper end -+ - public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { - this.profileRepository = profileRepository; - this.file = cacheFile; -@@ -69,6 +74,7 @@ public class GameProfileCache { - } - - private void safeAdd(GameProfileCache.GameProfileInfo entry) { -+ try { this.stateLock.lock(); // Paper - allow better concurrency - GameProfile gameprofile = entry.getProfile(); - - entry.setLastAccess(this.getNextOperation()); -@@ -83,6 +89,7 @@ public class GameProfileCache { - if (uuid != null) { - this.profilesByUUID.put(uuid, entry); - } -+ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency - - } - -@@ -138,17 +145,20 @@ public class GameProfileCache { - - // Paper start - public @Nullable GameProfile getProfileIfCached(String name) { -+ try { this.stateLock.lock(); // Paper - allow better concurrency - GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT)); - if (entry == null) { - return null; - } - entry.setLastAccess(this.getNextOperation()); - return entry.getProfile(); -+ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency - } - // Paper end - - public Optional get(String name) { - String s1 = name.toLowerCase(Locale.ROOT); -+ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - allow better concurrency - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); - boolean flag = false; - -@@ -164,8 +174,12 @@ public class GameProfileCache { - if (usercache_usercacheentry != null) { - usercache_usercacheentry.setLastAccess(this.getNextOperation()); - optional = Optional.of(usercache_usercacheentry.getProfile()); -+ stateLocked = false; this.stateLock.unlock(); // Paper - allow better concurrency - } else { -+ stateLocked = false; this.stateLock.unlock(); // Paper - allow better concurrency -+ try { this.lookupLock.lock(); // Paper - allow better concurrency - optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // Spigot - use correct case for offline players -+ } finally { this.lookupLock.unlock(); } // Paper - allow better concurrency - if (optional.isPresent()) { - this.add((GameProfile) optional.get()); - flag = false; -@@ -177,6 +191,7 @@ public class GameProfileCache { - } - - return optional; -+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - allow better concurrency - } - - public void getAsync(String username, Consumer> consumer) { -@@ -203,6 +218,7 @@ public class GameProfileCache { - } - - public Optional get(UUID uuid) { -+ try { this.stateLock.lock(); // Paper - allow better concurrency - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid); - - if (usercache_usercacheentry == null) { -@@ -211,6 +227,7 @@ public class GameProfileCache { - usercache_usercacheentry.setLastAccess(this.getNextOperation()); - return Optional.of(usercache_usercacheentry.getProfile()); - } -+ } finally { this.stateLock.unlock(); } // Paper - allow better concurrency - } - - public void setExecutor(Executor executor) { -@@ -291,7 +308,7 @@ public class GameProfileCache { - JsonArray jsonarray = new JsonArray(); - DateFormat dateformat = GameProfileCache.createDateFormat(); - -- this.getTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot -+ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - allow better concurrency - jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat)); - }); - String s = this.gson.toJson(jsonarray); -@@ -332,8 +349,19 @@ public class GameProfileCache { - } - - private Stream getTopMRUProfiles(int limit) { -- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); -+ // Paper start - allow better concurrency -+ return this.listTopMRUProfiles(limit).stream(); -+ } -+ -+ private List listTopMRUProfiles(int limit) { -+ try { -+ this.stateLock.lock(); -+ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList(); -+ } finally { -+ this.stateLock.unlock(); -+ } - } -+ // Paper end - - private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { - JsonObject jsonobject = new JsonObject(); diff --git a/patches/server/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch b/patches/server/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch new file mode 100644 index 0000000000..27155400d9 --- /dev/null +++ b/patches/server/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 1 Feb 2021 15:35:14 -0800 +Subject: [PATCH] Fix chunks refusing to unload at low TPS + +The full chunk future is appended to the chunk save future, but +when moving to unloaded ticket level it is not being completed with +the empty chunk access, so the chunk save must wait for the full +chunk future to complete. We can simply schedule to the immediate +executor to get this effect, rather than the main mailbox. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea..a3fceb2608b3be80941dfe2570999b270429e0c6 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1352,9 +1352,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + return chunk; + }); +- }, (runnable) -> { +- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +- }); ++ }, this.mainThreadExecutor); // Paper - queue to execute immediately so this doesn't delay chunk unloading + } + + public int getTickingGenerated() { diff --git a/patches/server/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/server/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch new file mode 100644 index 0000000000..302867667a --- /dev/null +++ b/patches/server/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Sep 2020 15:29:16 -0700 +Subject: [PATCH] Do not allow ticket level changes while unloading + playerchunks + +Sync loading the chunk at this stage would cause it to load +older data, as well as screwing our region state. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index a3fceb2608b3be80941dfe2570999b270429e0c6..b34c90497a5492c289839ba74df9f2f27e29370e 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -316,6 +316,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end + ++ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); + // Paper - don't copy +@@ -731,6 +732,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + @Nullable + ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { ++ if (this.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper + if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) { + return holder; + } else { +@@ -945,6 +947,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (completablefuture1 != completablefuture) { + this.scheduleUnload(pos, holder); + } else { ++ // Paper start - do not allow ticket level changes while unloading chunks ++ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload"); ++ boolean unloadingBefore = this.unloadingPlayerChunk; ++ this.unloadingPlayerChunk = true; ++ try { ++ // Paper end - do not allow ticket level changes while unloading chunks + // Paper start + boolean removed; + if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { +@@ -978,6 +986,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else if (removed) { // Paper start + net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder); + } // Paper end ++ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks + + } + }; +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index f3ab1691948c46477888776d28791ce24e7aa93d..29ba8971ceffbac68290f6063a69c98065e9bcba 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -632,6 +632,7 @@ public class ServerChunkCache extends ChunkSource { + + public boolean runDistanceManagerUpdates() { + if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority ++ if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = this.chunkMap.promoteChunkMap(); + diff --git a/patches/server/0725-Fix-chunks-refusing-to-unload-at-low-TPS.patch b/patches/server/0725-Fix-chunks-refusing-to-unload-at-low-TPS.patch deleted file mode 100644 index 27155400d9..0000000000 --- a/patches/server/0725-Fix-chunks-refusing-to-unload-at-low-TPS.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 1 Feb 2021 15:35:14 -0800 -Subject: [PATCH] Fix chunks refusing to unload at low TPS - -The full chunk future is appended to the chunk save future, but -when moving to unloaded ticket level it is not being completed with -the empty chunk access, so the chunk save must wait for the full -chunk future to complete. We can simply schedule to the immediate -executor to get this effect, rather than the main mailbox. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea..a3fceb2608b3be80941dfe2570999b270429e0c6 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1352,9 +1352,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - return chunk; - }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -- }); -+ }, this.mainThreadExecutor); // Paper - queue to execute immediately so this doesn't delay chunk unloading - } - - public int getTickingGenerated() { diff --git a/patches/server/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch b/patches/server/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch new file mode 100644 index 0000000000..d3e3677642 --- /dev/null +++ b/patches/server/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 00:08:13 -0700 +Subject: [PATCH] Do not allow ticket level changes when updating chunk ticking + state + +This WILL cause state corruption if it happens. So, don't +allow it. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 9c0bf31c3c362632241c95338a3f8d67bbd4fdc5..a2b5f6457b08e4e02544dc71fbf383b5a67a2d69 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -452,7 +452,13 @@ public class ChunkHolder { + CompletableFuture completablefuture1 = new CompletableFuture(); + + completablefuture1.thenRunAsync(() -> { ++ // Paper start - do not allow ticket level changes ++ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; ++ this.chunkMap.unloadingPlayerChunk = true; ++ try { ++ // Paper end - do not allow ticket level changes + playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); ++ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes + }, executor); + this.pendingFullStateConfirmation = completablefuture1; + completablefuture.thenAccept((either) -> { +@@ -469,7 +475,12 @@ public class ChunkHolder { + + private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { + this.pendingFullStateConfirmation.cancel(false); ++ // Paper start - do not allow ticket level changes ++ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; ++ this.chunkMap.unloadingPlayerChunk = true; ++ try { // Paper end - do not allow ticket level changes + playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); ++ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes + } + + protected long updateCount; // Paper - correctly handle recursion diff --git a/patches/server/0726-Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/server/0726-Do-not-allow-ticket-level-changes-while-unloading-pl.patch deleted file mode 100644 index 302867667a..0000000000 --- a/patches/server/0726-Do-not-allow-ticket-level-changes-while-unloading-pl.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 19 Sep 2020 15:29:16 -0700 -Subject: [PATCH] Do not allow ticket level changes while unloading - playerchunks - -Sync loading the chunk at this stage would cause it to load -older data, as well as screwing our region state. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index a3fceb2608b3be80941dfe2570999b270429e0c6..b34c90497a5492c289839ba74df9f2f27e29370e 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -316,6 +316,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - -+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); - // Paper - don't copy -@@ -731,6 +732,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Nullable - ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { -+ if (this.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper - if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) { - return holder; - } else { -@@ -945,6 +947,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (completablefuture1 != completablefuture) { - this.scheduleUnload(pos, holder); - } else { -+ // Paper start - do not allow ticket level changes while unloading chunks -+ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload"); -+ boolean unloadingBefore = this.unloadingPlayerChunk; -+ this.unloadingPlayerChunk = true; -+ try { -+ // Paper end - do not allow ticket level changes while unloading chunks - // Paper start - boolean removed; - if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { -@@ -978,6 +986,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else if (removed) { // Paper start - net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder); - } // Paper end -+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks - - } - }; -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index f3ab1691948c46477888776d28791ce24e7aa93d..29ba8971ceffbac68290f6063a69c98065e9bcba 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -632,6 +632,7 @@ public class ServerChunkCache extends ChunkSource { - - public boolean runDistanceManagerUpdates() { - if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority -+ if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper - boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); - boolean flag1 = this.chunkMap.promoteChunkMap(); - diff --git a/patches/server/0727-Do-not-allow-ticket-level-changes-when-updating-chun.patch b/patches/server/0727-Do-not-allow-ticket-level-changes-when-updating-chun.patch deleted file mode 100644 index d3e3677642..0000000000 --- a/patches/server/0727-Do-not-allow-ticket-level-changes-when-updating-chun.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 00:08:13 -0700 -Subject: [PATCH] Do not allow ticket level changes when updating chunk ticking - state - -This WILL cause state corruption if it happens. So, don't -allow it. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 9c0bf31c3c362632241c95338a3f8d67bbd4fdc5..a2b5f6457b08e4e02544dc71fbf383b5a67a2d69 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -452,7 +452,13 @@ public class ChunkHolder { - CompletableFuture completablefuture1 = new CompletableFuture(); - - completablefuture1.thenRunAsync(() -> { -+ // Paper start - do not allow ticket level changes -+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; -+ this.chunkMap.unloadingPlayerChunk = true; -+ try { -+ // Paper end - do not allow ticket level changes - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); -+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes - }, executor); - this.pendingFullStateConfirmation = completablefuture1; - completablefuture.thenAccept((either) -> { -@@ -469,7 +475,12 @@ public class ChunkHolder { - - private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { - this.pendingFullStateConfirmation.cancel(false); -+ // Paper start - do not allow ticket level changes -+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; -+ this.chunkMap.unloadingPlayerChunk = true; -+ try { // Paper end - do not allow ticket level changes - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); -+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes - } - - protected long updateCount; // Paper - correctly handle recursion diff --git a/patches/server/0727-Log-when-the-async-catcher-is-tripped.patch b/patches/server/0727-Log-when-the-async-catcher-is-tripped.patch new file mode 100644 index 0000000000..bede76faf6 --- /dev/null +++ b/patches/server/0727-Log-when-the-async-catcher-is-tripped.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 25 Aug 2021 20:17:12 -0700 +Subject: [PATCH] Log when the async catcher is tripped + +The chunk system can swallow the exception given it's all +built with completablefuture, so ensure it is at least printed. + +diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java +index 7585a30e8f063ac2656b5de519b1e9edaceffbc7..0c41413ad32f8f6a094462fcd637dd3229abda45 100644 +--- a/src/main/java/org/spigotmc/AsyncCatcher.java ++++ b/src/main/java/org/spigotmc/AsyncCatcher.java +@@ -12,6 +12,7 @@ public class AsyncCatcher + { + if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper + { ++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper + throw new IllegalStateException( "Asynchronous " + reason + "!" ); + } + } diff --git a/patches/server/0728-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0728-Add-paper-mobcaps-and-paper-playermobcaps.patch new file mode 100644 index 0000000000..07f2b36a13 --- /dev/null +++ b/patches/server/0728-Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -0,0 +1,349 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 16 Aug 2021 01:31:54 -0500 +Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' + +Add commands to get the mobcaps for a world, as well as the mobcaps for +each player when per-player mob spawning is enabled. + +Also has a hover text on each mob category listing what entity types are +in said category + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index d2536f4ffae721f4df714b5345fa3329c3b8e3f5..60b0ce4557390ee7030efe4c90933402c57bab59 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.DumpItemCommand; + import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.FixLightCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; ++import io.papermc.paper.command.subcommands.MobcapsCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; + import io.papermc.paper.command.subcommands.VersionCommand; +@@ -48,6 +49,7 @@ public final class PaperCommand extends Command { + commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); + commands.put(Set.of("dumpitem"), new DumpItemCommand()); ++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2e02d94e2903c48f6d08e743c1cf8bad9f9662df +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +@@ -0,0 +1,229 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.common.collect.ImmutableMap; ++import io.papermc.paper.command.CommandUtil; ++import io.papermc.paper.command.PaperSubcommand; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.List; ++import java.util.Map; ++import java.util.function.ToIntFunction; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.ComponentLike; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import net.minecraft.core.Registry; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.entity.MobCategory; ++import net.minecraft.world.level.NaturalSpawner; ++import org.bukkit.Bukkit; ++import org.bukkit.World; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class MobcapsCommand implements PaperSubcommand { ++ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() ++ .put(MobCategory.MONSTER, NamedTextColor.RED) ++ .put(MobCategory.CREATURE, NamedTextColor.GREEN) ++ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) ++ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) ++ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) ++ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) ++ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) ++ .put(MobCategory.MISC, TextColor.color(0x636363)) ++ .build(); ++ ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ switch (subCommand) { ++ case "mobcaps" -> this.printMobcaps(sender, args); ++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); ++ } ++ return true; ++ } ++ ++ @Override ++ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { ++ return switch (subCommand) { ++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); ++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); ++ default -> throw new IllegalArgumentException(); ++ }; ++ } ++ ++ private List suggestMobcaps(final String[] args) { ++ if (args.length == 1) { ++ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); ++ worlds.add("*"); ++ return worlds; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { ++ if (args.length == 1) { ++ final List list = new ArrayList<>(); ++ for (final Player player : Bukkit.getOnlinePlayers()) { ++ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { ++ list.add(player.getName()); ++ } ++ } ++ return list; ++ } ++ ++ return Collections.emptyList(); ++ } ++ ++ private void printMobcaps(final CommandSender sender, final String[] args) { ++ final List worlds; ++ if (args.length == 0) { ++ if (sender instanceof Player player) { ++ worlds = List.of(player.getWorld()); ++ } else { ++ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ if (input.equals("*")) { ++ worlds = Bukkit.getWorlds(); ++ } else { ++ final @Nullable World world = Bukkit.getWorld(input); ++ if (world == null) { ++ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); ++ return; ++ } else { ++ worlds = List.of(world); ++ } ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ for (final World world : worlds) { ++ final ServerLevel level = ((CraftWorld) world).getHandle(); ++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); ++ ++ final int chunks; ++ if (state == null) { ++ chunks = 0; ++ } else { ++ chunks = state.getSpawnableChunkCount(); ++ } ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Mobcaps for world: "), ++ Component.text(world.getName(), NamedTextColor.AQUA), ++ Component.text(" (" + chunks + " spawnable chunks)") ++ )); ++ ++ sender.sendMessage(createMobcapsComponent( ++ category -> { ++ if (state == null) { ++ return 0; ++ } else { ++ return state.getMobCategoryCounts().getOrDefault(category, 0); ++ } ++ }, ++ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) ++ )); ++ } ++ } ++ ++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { ++ final @Nullable Player player; ++ if (args.length == 0) { ++ if (sender instanceof Player pl) { ++ player = pl; ++ } else { ++ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); ++ return; ++ } ++ } else if (args.length == 1) { ++ final String input = args[0]; ++ player = Bukkit.getPlayerExact(input); ++ if (player == null) { ++ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); ++ return; ++ } ++ } else { ++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); ++ return; ++ } ++ ++ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); ++ final ServerLevel level = serverPlayer.getLevel(); ++ ++ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); ++ return; ++ } ++ ++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); ++ sender.sendMessage(createMobcapsComponent( ++ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), ++ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) ++ )); ++ } ++ ++ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { ++ return MOB_CATEGORY_COLORS.entrySet().stream() ++ .map(entry -> { ++ final MobCategory category = entry.getKey(); ++ final TextColor color = entry.getValue(); ++ ++ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), ++ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), ++ Component.text(category.getName(), color), ++ Component.text(':', NamedTextColor.GRAY), ++ Component.newline(), ++ Component.newline(), ++ Registry.ENTITY_TYPE.entrySet().stream() ++ .filter(it -> it.getValue().getCategory() == category) ++ .map(it -> Component.translatable(it.getValue().getDescriptionId())) ++ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) ++ ); ++ ++ final Component categoryComponent = Component.text() ++ .content(" " + category.getName()) ++ .color(color) ++ .hoverEvent(categoryHover) ++ .build(); ++ ++ final TextComponent.Builder builder = Component.text() ++ .append( ++ categoryComponent, ++ Component.text(": ", NamedTextColor.GRAY) ++ ); ++ final int limit = limitGetter.applyAsInt(category); ++ if (limit != -1) { ++ builder.append( ++ Component.text(countGetter.applyAsInt(category)), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text(limit) ++ ); ++ } else { ++ builder.append(Component.text() ++ .append( ++ Component.text('n'), ++ Component.text("/", NamedTextColor.GRAY), ++ Component.text('a') ++ ) ++ .hoverEvent(Component.text("This category does not naturally spawn."))); ++ } ++ return builder; ++ }) ++ .map(ComponentLike::asComponent) ++ .collect(Component.toComponent(Component.newline())); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87..4150e8cd7197eac53042d56f0a53a4951f8824ce 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -191,6 +191,16 @@ public final class NaturalSpawner { + world.getProfiler().pop(); + } + ++ // Paper start ++ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { ++ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); ++ if (categoryLimit < 1) { ++ return categoryLimit; ++ } ++ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; ++ } ++ // Paper end ++ + public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { + // Paper start - add parameters and int ret type + spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index e7863390c8394a6afcfa25099b4ce49c5e010f13..4adacf6f849fe41918690fb8f195727a9c880b53 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2156,6 +2156,11 @@ public final class CraftServer implements Server { + + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { ++ // Paper start ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { ++ // Paper end + return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 38a74d683aec58a419af84111844a205596524b2..5138182d8004aec69848d10b8cc63453c1e8808f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1711,9 +1711,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); + Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " are not supported."); + ++ // Paper start ++ return this.getSpawnLimitUnsafe(spawnCategory); ++ } ++ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { + int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); + if (limit < 0) { +- limit = this.server.getSpawnLimit(spawnCategory); ++ limit = this.server.getSpawnLimitUnsafe(spawnCategory); ++ // Paper end + } + return limit; + } +diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f1dd3bca7fa0df8b6ed177bb435877229af1c0c5 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java +@@ -0,0 +1,20 @@ ++package io.papermc.paper.command.subcommands; ++ ++import java.util.HashSet; ++import java.util.Set; ++import net.minecraft.world.entity.MobCategory; ++import org.junit.Assert; ++import org.junit.Test; ++ ++public class MobcapsCommandTest { ++ @Test ++ public void testMobCategoryColors() { ++ final Set missing = new HashSet<>(); ++ for (final MobCategory value : MobCategory.values()) { ++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { ++ missing.add(value.getName()); ++ } ++ } ++ Assert.assertTrue("MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); ++ } ++} diff --git a/patches/server/0728-Log-when-the-async-catcher-is-tripped.patch b/patches/server/0728-Log-when-the-async-catcher-is-tripped.patch deleted file mode 100644 index bede76faf6..0000000000 --- a/patches/server/0728-Log-when-the-async-catcher-is-tripped.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 25 Aug 2021 20:17:12 -0700 -Subject: [PATCH] Log when the async catcher is tripped - -The chunk system can swallow the exception given it's all -built with completablefuture, so ensure it is at least printed. - -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index 7585a30e8f063ac2656b5de519b1e9edaceffbc7..0c41413ad32f8f6a094462fcd637dd3229abda45 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -12,6 +12,7 @@ public class AsyncCatcher - { - if ( (AsyncCatcher.enabled || io.papermc.paper.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Paper - { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper - throw new IllegalStateException( "Asynchronous " + reason + "!" ); - } - } diff --git a/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch deleted file mode 100644 index 07f2b36a13..0000000000 --- a/patches/server/0729-Add-paper-mobcaps-and-paper-playermobcaps.patch +++ /dev/null @@ -1,349 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 16 Aug 2021 01:31:54 -0500 -Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps' - -Add commands to get the mobcaps for a world, as well as the mobcaps for -each player when per-player mob spawning is enabled. - -Also has a hover text on each mob category listing what entity types are -in said category - -diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java -index d2536f4ffae721f4df714b5345fa3329c3b8e3f5..60b0ce4557390ee7030efe4c90933402c57bab59 100644 ---- a/src/main/java/io/papermc/paper/command/PaperCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java -@@ -5,6 +5,7 @@ import io.papermc.paper.command.subcommands.DumpItemCommand; - import io.papermc.paper.command.subcommands.EntityCommand; - import io.papermc.paper.command.subcommands.FixLightCommand; - import io.papermc.paper.command.subcommands.HeapDumpCommand; -+import io.papermc.paper.command.subcommands.MobcapsCommand; - import io.papermc.paper.command.subcommands.ReloadCommand; - import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; - import io.papermc.paper.command.subcommands.VersionCommand; -@@ -48,6 +49,7 @@ public final class PaperCommand extends Command { - commands.put(Set.of("fixlight"), new FixLightCommand()); - commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); - commands.put(Set.of("dumpitem"), new DumpItemCommand()); -+ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); - - return commands.entrySet().stream() - .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) -diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2e02d94e2903c48f6d08e743c1cf8bad9f9662df ---- /dev/null -+++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -@@ -0,0 +1,229 @@ -+package io.papermc.paper.command.subcommands; -+ -+import com.google.common.collect.ImmutableMap; -+import io.papermc.paper.command.CommandUtil; -+import io.papermc.paper.command.PaperSubcommand; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.List; -+import java.util.Map; -+import java.util.function.ToIntFunction; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.ComponentLike; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.TextComponent; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextColor; -+import net.minecraft.core.Registry; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.MobCategory; -+import net.minecraft.world.level.NaturalSpawner; -+import org.bukkit.Bukkit; -+import org.bukkit.World; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class MobcapsCommand implements PaperSubcommand { -+ static final Map MOB_CATEGORY_COLORS = ImmutableMap.builder() -+ .put(MobCategory.MONSTER, NamedTextColor.RED) -+ .put(MobCategory.CREATURE, NamedTextColor.GREEN) -+ .put(MobCategory.AMBIENT, NamedTextColor.GRAY) -+ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF)) -+ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6)) -+ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF)) -+ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF)) -+ .put(MobCategory.MISC, TextColor.color(0x636363)) -+ .build(); -+ -+ @Override -+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { -+ switch (subCommand) { -+ case "mobcaps" -> this.printMobcaps(sender, args); -+ case "playermobcaps" -> this.printPlayerMobcaps(sender, args); -+ } -+ return true; -+ } -+ -+ @Override -+ public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { -+ return switch (subCommand) { -+ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args)); -+ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args)); -+ default -> throw new IllegalArgumentException(); -+ }; -+ } -+ -+ private List suggestMobcaps(final String[] args) { -+ if (args.length == 1) { -+ final List worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList()); -+ worlds.add("*"); -+ return worlds; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private List suggestPlayerMobcaps(final CommandSender sender, final String[] args) { -+ if (args.length == 1) { -+ final List list = new ArrayList<>(); -+ for (final Player player : Bukkit.getOnlinePlayers()) { -+ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) { -+ list.add(player.getName()); -+ } -+ } -+ return list; -+ } -+ -+ return Collections.emptyList(); -+ } -+ -+ private void printMobcaps(final CommandSender sender, final String[] args) { -+ final List worlds; -+ if (args.length == 0) { -+ if (sender instanceof Player player) { -+ worlds = List.of(player.getWorld()); -+ } else { -+ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ if (input.equals("*")) { -+ worlds = Bukkit.getWorlds(); -+ } else { -+ final @Nullable World world = Bukkit.getWorld(input); -+ if (world == null) { -+ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED)); -+ return; -+ } else { -+ worlds = List.of(world); -+ } -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ for (final World world : worlds) { -+ final ServerLevel level = ((CraftWorld) world).getHandle(); -+ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState(); -+ -+ final int chunks; -+ if (state == null) { -+ chunks = 0; -+ } else { -+ chunks = state.getSpawnableChunkCount(); -+ } -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Mobcaps for world: "), -+ Component.text(world.getName(), NamedTextColor.AQUA), -+ Component.text(" (" + chunks + " spawnable chunks)") -+ )); -+ -+ sender.sendMessage(createMobcapsComponent( -+ category -> { -+ if (state == null) { -+ return 0; -+ } else { -+ return state.getMobCategoryCounts().getOrDefault(category, 0); -+ } -+ }, -+ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks) -+ )); -+ } -+ } -+ -+ private void printPlayerMobcaps(final CommandSender sender, final String[] args) { -+ final @Nullable Player player; -+ if (args.length == 0) { -+ if (sender instanceof Player pl) { -+ player = pl; -+ } else { -+ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED)); -+ return; -+ } -+ } else if (args.length == 1) { -+ final String input = args[0]; -+ player = Bukkit.getPlayerExact(input); -+ if (player == null) { -+ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED)); -+ return; -+ } -+ } else { -+ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED)); -+ return; -+ } -+ -+ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -+ final ServerLevel level = serverPlayer.getLevel(); -+ -+ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); -+ return; -+ } -+ -+ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN))); -+ sender.sendMessage(createMobcapsComponent( -+ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category), -+ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category)) -+ )); -+ } -+ -+ private static Component createMobcapsComponent(final ToIntFunction countGetter, final ToIntFunction limitGetter) { -+ return MOB_CATEGORY_COLORS.entrySet().stream() -+ .map(entry -> { -+ final MobCategory category = entry.getKey(); -+ final TextColor color = entry.getValue(); -+ -+ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)), -+ Component.text(category.getName(), color), -+ Component.text(':', NamedTextColor.GRAY), -+ Component.newline(), -+ Component.newline(), -+ Registry.ENTITY_TYPE.entrySet().stream() -+ .filter(it -> it.getValue().getCategory() == category) -+ .map(it -> Component.translatable(it.getValue().getDescriptionId())) -+ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY))) -+ ); -+ -+ final Component categoryComponent = Component.text() -+ .content(" " + category.getName()) -+ .color(color) -+ .hoverEvent(categoryHover) -+ .build(); -+ -+ final TextComponent.Builder builder = Component.text() -+ .append( -+ categoryComponent, -+ Component.text(": ", NamedTextColor.GRAY) -+ ); -+ final int limit = limitGetter.applyAsInt(category); -+ if (limit != -1) { -+ builder.append( -+ Component.text(countGetter.applyAsInt(category)), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text(limit) -+ ); -+ } else { -+ builder.append(Component.text() -+ .append( -+ Component.text('n'), -+ Component.text("/", NamedTextColor.GRAY), -+ Component.text('a') -+ ) -+ .hoverEvent(Component.text("This category does not naturally spawn."))); -+ } -+ return builder; -+ }) -+ .map(ComponentLike::asComponent) -+ .collect(Component.toComponent(Component.newline())); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index fa23e9c476d4edc6176d8b8a6cb13c52d2f66a87..4150e8cd7197eac53042d56f0a53a4951f8824ce 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -191,6 +191,16 @@ public final class NaturalSpawner { - world.getProfiler().pop(); - } - -+ // Paper start -+ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) { -+ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category)); -+ if (categoryLimit < 1) { -+ return categoryLimit; -+ } -+ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER; -+ } -+ // Paper end -+ - public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { - // Paper start - add parameters and int ret type - spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index e7863390c8394a6afcfa25099b4ce49c5e010f13..4adacf6f849fe41918690fb8f195727a9c880b53 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2156,6 +2156,11 @@ public final class CraftServer implements Server { - - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { -+ // Paper start -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { -+ // Paper end - return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 38a74d683aec58a419af84111844a205596524b2..5138182d8004aec69848d10b8cc63453c1e8808f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1711,9 +1711,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); - Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " are not supported."); - -+ // Paper start -+ return this.getSpawnLimitUnsafe(spawnCategory); -+ } -+ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { - int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1); - if (limit < 0) { -- limit = this.server.getSpawnLimit(spawnCategory); -+ limit = this.server.getSpawnLimitUnsafe(spawnCategory); -+ // Paper end - } - return limit; - } -diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f1dd3bca7fa0df8b6ed177bb435877229af1c0c5 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java -@@ -0,0 +1,20 @@ -+package io.papermc.paper.command.subcommands; -+ -+import java.util.HashSet; -+import java.util.Set; -+import net.minecraft.world.entity.MobCategory; -+import org.junit.Assert; -+import org.junit.Test; -+ -+public class MobcapsCommandTest { -+ @Test -+ public void testMobCategoryColors() { -+ final Set missing = new HashSet<>(); -+ for (final MobCategory value : MobCategory.values()) { -+ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) { -+ missing.add(value.getName()); -+ } -+ } -+ Assert.assertTrue("MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"), missing.isEmpty()); -+ } -+} diff --git a/patches/server/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch b/patches/server/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch new file mode 100644 index 0000000000..516b5b8e0c --- /dev/null +++ b/patches/server/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 18 Jun 2020 18:23:20 -0700 +Subject: [PATCH] Prevent unload() calls removing tickets for sync loads + + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index b2df5e18ce5260a9781052db7afb0b9786fb887c..537d34a0325a985948c744929b90144a66a35ee3 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -545,7 +545,7 @@ public abstract class DistanceManager { + } + + public void removeTicketsOnClosing() { +- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD); // Paper - add additional tickets to preserve ++ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + + while (objectiterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 29ba8971ceffbac68290f6063a69c98065e9bcba..2390fcbc1b21653b1753a58da33f936cec43d0cb 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -537,6 +537,8 @@ public class ServerChunkCache extends ChunkSource { + return completablefuture; + } + ++ private long syncLoadCounter; // Paper - prevent plugin unloads from removing our ticket ++ + private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { + // Paper start - add isUrgent - old sig left in place for dirty nms plugins + return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); +@@ -555,9 +557,12 @@ public class ServerChunkCache extends ChunkSource { + ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel()); + currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)); + } ++ final Long identifier; // Paper - prevent plugin unloads from removing our ticket + if (create && !currentlyUnloading) { + // CraftBukkit end + this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); ++ identifier = Long.valueOf(this.syncLoadCounter++); // Paper - prevent plugin unloads from removing our ticket ++ this.distanceManager.addTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper - prevent plugin unloads from removing our ticket + if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority + if (this.chunkAbsent(playerchunk, l)) { + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); +@@ -568,13 +573,21 @@ public class ServerChunkCache extends ChunkSource { + playerchunk = this.getVisibleChunkIfPresent(k); + gameprofilerfiller.pop(); + if (this.chunkAbsent(playerchunk, l)) { ++ this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper + throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); + } + } +- } + ++ } else { identifier = null; } // Paper - prevent plugin unloads from removing our ticket + // Paper start - Chunk priority + CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); ++ // Paper start - prevent plugin unloads from removing our ticket ++ if (create && !currentlyUnloading) { ++ future.thenAcceptAsync((either) -> { ++ ServerChunkCache.this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); ++ }, ServerChunkCache.this.mainThreadProcessor); ++ } ++ // Paper end - prevent plugin unloads from removing our ticket + if (isUrgent) { + future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 3c1698ba0d3bc412ab957777d9b5211dbc555208..41ddcf6775f99c56cf4b13b284420061e5dd6bdc 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -31,6 +31,7 @@ public class TicketType { + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper ++ public static final TicketType REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail + + public static TicketType create(String name, Comparator argumentComparator) { + return new TicketType<>(name, argumentComparator, 0L); diff --git a/patches/server/0730-Prevent-unload-calls-removing-tickets-for-sync-loads.patch b/patches/server/0730-Prevent-unload-calls-removing-tickets-for-sync-loads.patch deleted file mode 100644 index 516b5b8e0c..0000000000 --- a/patches/server/0730-Prevent-unload-calls-removing-tickets-for-sync-loads.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 18 Jun 2020 18:23:20 -0700 -Subject: [PATCH] Prevent unload() calls removing tickets for sync loads - - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index b2df5e18ce5260a9781052db7afb0b9786fb887c..537d34a0325a985948c744929b90144a66a35ee3 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -545,7 +545,7 @@ public abstract class DistanceManager { - } - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD); // Paper - add additional tickets to preserve -+ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 29ba8971ceffbac68290f6063a69c98065e9bcba..2390fcbc1b21653b1753a58da33f936cec43d0cb 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -537,6 +537,8 @@ public class ServerChunkCache extends ChunkSource { - return completablefuture; - } - -+ private long syncLoadCounter; // Paper - prevent plugin unloads from removing our ticket -+ - private CompletableFuture> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { - // Paper start - add isUrgent - old sig left in place for dirty nms plugins - return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false); -@@ -555,9 +557,12 @@ public class ServerChunkCache extends ChunkSource { - ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel()); - currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)); - } -+ final Long identifier; // Paper - prevent plugin unloads from removing our ticket - if (create && !currentlyUnloading) { - // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -+ identifier = Long.valueOf(this.syncLoadCounter++); // Paper - prevent plugin unloads from removing our ticket -+ this.distanceManager.addTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper - prevent plugin unloads from removing our ticket - if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority - if (this.chunkAbsent(playerchunk, l)) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); -@@ -568,13 +573,21 @@ public class ServerChunkCache extends ChunkSource { - playerchunk = this.getVisibleChunkIfPresent(k); - gameprofilerfiller.pop(); - if (this.chunkAbsent(playerchunk, l)) { -+ this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper - throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added")); - } - } -- } - -+ } else { identifier = null; } // Paper - prevent plugin unloads from removing our ticket - // Paper start - Chunk priority - CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap); -+ // Paper start - prevent plugin unloads from removing our ticket -+ if (create && !currentlyUnloading) { -+ future.thenAcceptAsync((either) -> { -+ ServerChunkCache.this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); -+ }, ServerChunkCache.this.mainThreadProcessor); -+ } -+ // Paper end - prevent plugin unloads from removing our ticket - if (isUrgent) { - future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); - } -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 3c1698ba0d3bc412ab957777d9b5211dbc555208..41ddcf6775f99c56cf4b13b284420061e5dd6bdc 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -31,6 +31,7 @@ public class TicketType { - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper -+ public static final TicketType REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); diff --git a/patches/server/0730-Sanitize-ResourceLocation-error-logging.patch b/patches/server/0730-Sanitize-ResourceLocation-error-logging.patch new file mode 100644 index 0000000000..c2ff3a02e4 --- /dev/null +++ b/patches/server/0730-Sanitize-ResourceLocation-error-logging.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 26 Aug 2021 12:09:47 +0200 +Subject: [PATCH] Sanitize ResourceLocation error logging + + +diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java +index 0e04275f62c5c2d8afede431f78f38f06e8009e6..7017dd42f832d928f1008a05f01701667d951644 100644 +--- a/src/main/java/net/minecraft/resources/ResourceLocation.java ++++ b/src/main/java/net/minecraft/resources/ResourceLocation.java +@@ -32,9 +32,9 @@ public class ResourceLocation implements Comparable { + this.namespace = StringUtils.isEmpty(id[0]) ? "minecraft" : id[0]; + this.path = id[1]; + if (!isValidNamespace(this.namespace)) { +- throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + this.namespace + ":" + this.path); ++ throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + org.apache.commons.lang3.StringUtils.normalizeSpace(this.namespace) + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(this.path)); // Paper + } else if (!isValidPath(this.path)) { +- throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + this.namespace + ":" + this.path); ++ throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + this.namespace + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(this.path)); // Paper + } + } + diff --git a/patches/server/0731-Allow-controlled-flushing-for-network-manager.patch b/patches/server/0731-Allow-controlled-flushing-for-network-manager.patch new file mode 100644 index 0000000000..cbd07781ef --- /dev/null +++ b/patches/server/0731-Allow-controlled-flushing-for-network-manager.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 4 Apr 2020 15:27:44 -0700 +Subject: [PATCH] Allow controlled flushing for network manager + +Only make one flush call when emptying the packet queue too + +This patch will be used to optimise out flush calls in later +patches. + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index b5f884d6671823085a2ab0e8da2d30afd2928f32..057a0be81b12bd8a4ac71106dc8ada91bd4c9bfd 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -99,6 +99,39 @@ public class Connection extends SimpleChannelInboundHandler> { + public ConnectionProtocol protocol; + // Paper end + ++ // Paper start - allow controlled flushing ++ volatile boolean canFlush = true; ++ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); ++ private int flushPacketsStart; ++ private final Object flushLock = new Object(); ++ ++ public void disableAutomaticFlush() { ++ synchronized (this.flushLock) { ++ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false ++ this.canFlush = false; ++ } ++ } ++ ++ public void enableAutomaticFlush() { ++ synchronized (this.flushLock) { ++ this.canFlush = true; ++ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true ++ this.flush(); // only make the flush call if we need to ++ } ++ } ++ } ++ ++ private final void flush() { ++ if (this.channel.eventLoop().inEventLoop()) { ++ this.channel.flush(); ++ } else { ++ this.channel.eventLoop().execute(() -> { ++ this.channel.flush(); ++ }); ++ } ++ } ++ // Paper end - allow controlled flushing ++ + public Connection(PacketFlow side) { + this.receiving = side; + } +@@ -264,7 +297,7 @@ public class Connection extends SimpleChannelInboundHandler> { + net.minecraft.server.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() && + (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) + ))) { +- this.sendPacket(packet, callbacks); ++ this.sendPacket(packet, callbacks, null); // Paper + return; + } + // write the packets to the queue, then flush - antixray hooks there already +@@ -288,6 +321,14 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks) { ++ // Paper start - add flush parameter ++ this.sendPacket(packet, callbacks, Boolean.TRUE); ++ } ++ private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks, Boolean flushConditional) { ++ this.packetWrites.getAndIncrement(); // must be befeore using canFlush ++ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); ++ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets ++ // Paper end - add flush parameter + ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); + ConnectionProtocol enumprotocol1 = this.getCurrentProtocol(); + +@@ -298,16 +339,21 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + if (this.channel.eventLoop().inEventLoop()) { +- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); ++ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper + } else { + this.channel.eventLoop().execute(() -> { +- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); ++ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper + }); + } + + } + + private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState) { ++ // Paper start - add flush parameter ++ this.doSendPacket(packet, callbacks, packetState, currentState, true); ++ } ++ private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState, boolean flush) { ++ // Paper end - add flush parameter + if (packetState != currentState) { + this.setProtocol(packetState); + } +@@ -321,7 +367,7 @@ public class Connection extends SimpleChannelInboundHandler> { + + try { + // Paper end +- ChannelFuture channelfuture = this.channel.writeAndFlush(packet); ++ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter + + if (callbacks != null) { + channelfuture.addListener((future) -> { +@@ -376,6 +422,10 @@ public class Connection extends SimpleChannelInboundHandler> { + } + private boolean processQueue() { + if (this.queue.isEmpty()) return true; ++ // Paper start - make only one flush call per sendPacketQueue() call ++ final boolean needsFlush = this.canFlush; ++ boolean hasWrotePacket = false; ++ // Paper end - make only one flush call per sendPacketQueue() call + // 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.queue.iterator(); +@@ -383,16 +433,22 @@ public class Connection extends SimpleChannelInboundHandler> { + PacketHolder queued = iterator.next(); // poll -> peek + + // Fix NPE (Spigot bug caused by handleDisconnection()) +- if (queued == null) { ++ if (false && queued == null) { // Paper - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here + return true; + } + + Packet packet = queued.packet; + if (!packet.isReady()) { ++ // Paper start - make only one flush call per sendPacketQueue() call ++ if (hasWrotePacket && (needsFlush || this.canFlush)) { ++ this.flush(); ++ } ++ // Paper end - make only one flush call per sendPacketQueue() call + return false; + } else { + iterator.remove(); +- this.sendPacket(packet, queued.listener); ++ this.sendPacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call ++ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call + } + } + return true; diff --git a/patches/server/0731-Sanitize-ResourceLocation-error-logging.patch b/patches/server/0731-Sanitize-ResourceLocation-error-logging.patch deleted file mode 100644 index c2ff3a02e4..0000000000 --- a/patches/server/0731-Sanitize-ResourceLocation-error-logging.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 26 Aug 2021 12:09:47 +0200 -Subject: [PATCH] Sanitize ResourceLocation error logging - - -diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java -index 0e04275f62c5c2d8afede431f78f38f06e8009e6..7017dd42f832d928f1008a05f01701667d951644 100644 ---- a/src/main/java/net/minecraft/resources/ResourceLocation.java -+++ b/src/main/java/net/minecraft/resources/ResourceLocation.java -@@ -32,9 +32,9 @@ public class ResourceLocation implements Comparable { - this.namespace = StringUtils.isEmpty(id[0]) ? "minecraft" : id[0]; - this.path = id[1]; - if (!isValidNamespace(this.namespace)) { -- throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + this.namespace + ":" + this.path); -+ throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + org.apache.commons.lang3.StringUtils.normalizeSpace(this.namespace) + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(this.path)); // Paper - } else if (!isValidPath(this.path)) { -- throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + this.namespace + ":" + this.path); -+ throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + this.namespace + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(this.path)); // Paper - } - } - diff --git a/patches/server/0732-Allow-controlled-flushing-for-network-manager.patch b/patches/server/0732-Allow-controlled-flushing-for-network-manager.patch deleted file mode 100644 index cbd07781ef..0000000000 --- a/patches/server/0732-Allow-controlled-flushing-for-network-manager.patch +++ /dev/null @@ -1,147 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 4 Apr 2020 15:27:44 -0700 -Subject: [PATCH] Allow controlled flushing for network manager - -Only make one flush call when emptying the packet queue too - -This patch will be used to optimise out flush calls in later -patches. - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index b5f884d6671823085a2ab0e8da2d30afd2928f32..057a0be81b12bd8a4ac71106dc8ada91bd4c9bfd 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -99,6 +99,39 @@ public class Connection extends SimpleChannelInboundHandler> { - public ConnectionProtocol protocol; - // Paper end - -+ // Paper start - allow controlled flushing -+ volatile boolean canFlush = true; -+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); -+ private int flushPacketsStart; -+ private final Object flushLock = new Object(); -+ -+ public void disableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false -+ this.canFlush = false; -+ } -+ } -+ -+ public void enableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.canFlush = true; -+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true -+ this.flush(); // only make the flush call if we need to -+ } -+ } -+ } -+ -+ private final void flush() { -+ if (this.channel.eventLoop().inEventLoop()) { -+ this.channel.flush(); -+ } else { -+ this.channel.eventLoop().execute(() -> { -+ this.channel.flush(); -+ }); -+ } -+ } -+ // Paper end - allow controlled flushing -+ - public Connection(PacketFlow side) { - this.receiving = side; - } -@@ -264,7 +297,7 @@ public class Connection extends SimpleChannelInboundHandler> { - net.minecraft.server.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() && - (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) - ))) { -- this.sendPacket(packet, callbacks); -+ this.sendPacket(packet, callbacks, null); // Paper - return; - } - // write the packets to the queue, then flush - antixray hooks there already -@@ -288,6 +321,14 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks) { -+ // Paper start - add flush parameter -+ this.sendPacket(packet, callbacks, Boolean.TRUE); -+ } -+ private void sendPacket(Packet packet, @Nullable PacketSendListener callbacks, Boolean flushConditional) { -+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush -+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); -+ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets -+ // Paper end - add flush parameter - ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); - ConnectionProtocol enumprotocol1 = this.getCurrentProtocol(); - -@@ -298,16 +339,21 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - if (this.channel.eventLoop().inEventLoop()) { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - } else { - this.channel.eventLoop().execute(() -> { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1); -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - }); - } - - } - - private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState) { -+ // Paper start - add flush parameter -+ this.doSendPacket(packet, callbacks, packetState, currentState, true); -+ } -+ private void doSendPacket(Packet packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState, boolean flush) { -+ // Paper end - add flush parameter - if (packetState != currentState) { - this.setProtocol(packetState); - } -@@ -321,7 +367,7 @@ public class Connection extends SimpleChannelInboundHandler> { - - try { - // Paper end -- ChannelFuture channelfuture = this.channel.writeAndFlush(packet); -+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter - - if (callbacks != null) { - channelfuture.addListener((future) -> { -@@ -376,6 +422,10 @@ public class Connection extends SimpleChannelInboundHandler> { - } - private boolean processQueue() { - if (this.queue.isEmpty()) return true; -+ // Paper start - make only one flush call per sendPacketQueue() call -+ final boolean needsFlush = this.canFlush; -+ boolean hasWrotePacket = false; -+ // Paper end - make only one flush call per sendPacketQueue() call - // 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.queue.iterator(); -@@ -383,16 +433,22 @@ public class Connection extends SimpleChannelInboundHandler> { - PacketHolder queued = iterator.next(); // poll -> peek - - // Fix NPE (Spigot bug caused by handleDisconnection()) -- if (queued == null) { -+ if (false && queued == null) { // Paper - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here - return true; - } - - Packet packet = queued.packet; - if (!packet.isReady()) { -+ // Paper start - make only one flush call per sendPacketQueue() call -+ if (hasWrotePacket && (needsFlush || this.canFlush)) { -+ this.flush(); -+ } -+ // Paper end - make only one flush call per sendPacketQueue() call - return false; - } else { - iterator.remove(); -- this.sendPacket(packet, queued.listener); -+ this.sendPacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call -+ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call - } - } - return true; diff --git a/patches/server/0732-Optimise-general-POI-access.patch b/patches/server/0732-Optimise-general-POI-access.patch new file mode 100644 index 0000000000..b7014db094 --- /dev/null +++ b/patches/server/0732-Optimise-general-POI-access.patch @@ -0,0 +1,1059 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 31 Jan 2021 02:29:24 -0800 +Subject: [PATCH] Optimise general POI access + +There are a couple of problems with mojang's POI code. +Firstly, it's all streams. Unsurprisingly, stacking +streams on top of each other is horrible for performance +and ultimately took up half of a villager's tick! + +Secondly, sometime's the search radius is large and there are +a significant number of poi entries per chunk section. Even +removing streams at this point doesn't help much. The only solution +is to start at the search point and iterate outwards. This +type of approach shows massive gains for portals, simply because +we can avoid sync loading a large area of chunks. I also tested +a massive farm I found in JellySquid's discord, which showed +to benefit significantly simply because the farm had so many +portal blocks that searching through them all was very slow. + +Great care has been taken so that behavior remains identical to +vanilla, however I cannot account for oddball Stream API +implementations, if they even exist (streams can technically +be loose with iteration order in a sorted stream given its +source stream is not tagged with ordered, and mojang does not +tag the source stream as ordered). However in my testing on openjdk +there showed no difference, as expected. + +This patch also specifically optimises other areas of code to +use PoiAccess. For example, some villager AI and portaling code +had to be specifically modified. + +diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1e7be5da4907616ad9e1e01a2227d29b6dd54b32 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/PoiAccess.java +@@ -0,0 +1,801 @@ ++package io.papermc.paper.util; ++ ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.doubles.Double2ObjectMap; ++import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap; ++import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import java.util.function.BiPredicate; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Holder; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.ai.village.poi.PoiManager; ++import net.minecraft.world.entity.ai.village.poi.PoiRecord; ++import net.minecraft.world.entity.ai.village.poi.PoiSection; ++import net.minecraft.world.entity.ai.village.poi.PoiType; ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Map; ++import java.util.Optional; ++import java.util.Set; ++import java.util.function.Predicate; ++ ++/** ++ * Provides optimised access to POI data. All returned values will be identical to vanilla. ++ */ ++public final class PoiAccess { ++ ++ protected static double clamp(final double val, final double min, final double max) { ++ return (val < min ? min : (val > max ? max : val)); ++ } ++ ++ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ, ++ final double boxMaxX, final double boxMaxY, final double boxMaxZ, ++ ++ final double circleX, final double circleY, final double circleZ) { ++ // is the circle center inside the box? ++ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) { ++ return 0.0; ++ } ++ ++ final double boxWidthX = (boxMaxX - boxMinX) / 2.0; ++ final double boxWidthY = (boxMaxY - boxMinY) / 2.0; ++ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0; ++ ++ final double boxCenterX = (boxMinX + boxMaxX) / 2.0; ++ final double boxCenterY = (boxMinY + boxMaxY) / 2.0; ++ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0; ++ ++ double centerDiffX = circleX - boxCenterX; ++ double centerDiffY = circleY - boxCenterY; ++ double centerDiffZ = circleZ - boxCenterZ; ++ ++ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX); ++ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY); ++ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ); ++ ++ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ); ++ } ++ ++ ++ // key is: ++ // upper 32 bits: ++ // upper 16 bits: max y section ++ // lower 16 bits: min y section ++ // lower 32 bits: ++ // upper 16 bits: section ++ // lower 16 bits: radius ++ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) { ++ return ( ++ (maxSection & 0xFFFFL) << (64 - 16) ++ | (minSection & 0xFFFFL) << (64 - 32) ++ | (section & 0xFFFFL) << (64 - 48) ++ | (radius & 0xFFFFL) << (64 - 64) ++ ); ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. ++ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final PoiRecord ret = findClosestPoiDataRecord( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load ++ ); ++ ++ return ret == null ? null : ret.getPos(); ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. ++ public static Pair, BlockPos> findClosestPoiDataTypeAndPosition(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final PoiRecord ret = findClosestPoiDataRecord( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load ++ ); ++ ++ return ret == null ? null : Pair.of(ret.getPoiType(), ret.getPos()); ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. ++ public static void findClosestPoiDataPositions(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final Set ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List toConvert = new ArrayList<>(); ++ findClosestPoiDataRecords( ++ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert ++ ); ++ ++ for (final PoiRecord record : toConvert) { ++ ret.add(record.getPos()); ++ } ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. ++ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final List ret = new ArrayList<>(); ++ findClosestPoiDataRecords( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret ++ ); ++ return ret.isEmpty() ? null : ret.get(0); ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. ++ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final BiPredicate, BlockPos> predicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final List ret = new ArrayList<>(); ++ findClosestPoiDataRecords( ++ poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistance, occupancy, load, ret ++ ); ++ return ret.isEmpty() ? null : ret.get(0); ++ } ++ ++ // only includes x/z axis ++ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. ++ public static void findClosestPoiDataRecords(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final List ret) { ++ final BiPredicate, BlockPos> predicate = positionPredicate != null ? (type, pos) -> positionPredicate.test(pos) : null; ++ findClosestPoiDataRecords(poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistance, occupancy, load, ret); ++ } ++ ++ public static void findClosestPoiDataRecords(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final BiPredicate, BlockPos> predicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final List ret) { ++ final Predicate occupancyFilter = occupancy.getTest(); ++ ++ final List closestRecords = new ArrayList<>(); ++ double closestDistanceSquared = maxDistance * maxDistance; ++ ++ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; ++ final int lowerY = WorldUtil.getMinSection(poiStorage.world); ++ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; ++ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; ++ final int upperY = WorldUtil.getMaxSection(poiStorage.world); ++ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; ++ ++ final int centerX = sourcePosition.getX() >> 4; ++ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); ++ final int centerZ = sourcePosition.getZ() >> 4; ++ ++ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); ++ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); ++ final LongOpenHashSet seen = new LongOpenHashSet(); ++ ++ while (!queue.isEmpty()) { ++ final long key = queue.dequeueLong(); ++ final int sectionX = CoordinateUtils.getChunkSectionX(key); ++ final int sectionY = CoordinateUtils.getChunkSectionY(key); ++ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); ++ ++ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { ++ // out of bound chunk ++ continue; ++ } ++ ++ final double sectionDistanceSquared = getSmallestDistanceSquared( ++ (sectionX << 4) + 0.5, ++ (sectionY << 4) + 0.5, ++ (sectionZ << 4) + 0.5, ++ (sectionX << 4) + 15.5, ++ (sectionY << 4) + 15.5, ++ (sectionZ << 4) + 15.5, ++ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ() ++ ); ++ if (sectionDistanceSquared > closestDistanceSquared) { ++ continue; ++ } ++ ++ // queue all neighbours ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dy = -1; dy <= 1; ++dy) { ++ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many ++ // values are set. we only care about cardinal neighbours, so, we only care if one value is set ++ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { ++ continue; ++ } ++ ++ final int neighbourX = sectionX + dx; ++ final int neighbourY = sectionY + dy; ++ final int neighbourZ = sectionZ + dz; ++ ++ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); ++ if (seen.add(neighbourKey)) { ++ queue.enqueue(neighbourKey); ++ } ++ } ++ } ++ } ++ ++ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); ++ ++ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { ++ continue; ++ } ++ ++ final PoiSection poiSection = poiSectionOptional.orElse(null); ++ ++ final Map, Set> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry, Set> entry : sectionData.entrySet()) { ++ if (!villagePlaceType.test(entry.getKey())) { ++ // filter out by poi type ++ continue; ++ } ++ ++ // now we can look at the poi data ++ for (final PoiRecord poiData : entry.getValue()) { ++ if (!occupancyFilter.test(poiData)) { ++ // filter by occupancy ++ continue; ++ } ++ ++ final BlockPos poiPosition = poiData.getPos(); ++ ++ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range ++ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { ++ // out of range for square radius ++ continue; ++ } ++ ++ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! ++ final double dataRange = poiPosition.distSqr(sourcePosition); ++ ++ if (dataRange > closestDistanceSquared) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(poiData.getPoiType(), poiPosition)) { ++ // filter by position ++ continue; ++ } ++ ++ if (dataRange < closestDistanceSquared) { ++ closestRecords.clear(); ++ closestDistanceSquared = dataRange; ++ } ++ closestRecords.add(poiData); ++ } ++ } ++ } ++ ++ // uh oh! we might have multiple records that match the distance sorting! ++ // we need to re-order our results by the way vanilla would have iterated over them. ++ closestRecords.sort((record1, record2) -> { ++ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section ++ // is fine and should be preserved (this sort is stable so we're good there) ++ // but they iterate sections by x then by z (like the following) ++ // for (int x = -dx; x <= dx; ++x) ++ // for (int z = -dz; z <= dz; ++z) ++ // .... ++ // so we need to reorder such that records with lower chunk z, then lower chunk x come first ++ final BlockPos pos1 = record1.getPos(); ++ final BlockPos pos2 = record2.getPos(); ++ ++ final int cx1 = pos1.getX() >> 4; ++ final int cz1 = pos1.getZ() >> 4; ++ ++ final int cx2 = pos2.getX() >> 4; ++ final int cz2 = pos2.getZ() >> 4; ++ ++ if (cz2 != cz1) { ++ // want smaller z ++ return Integer.compare(cz1, cz2); ++ } ++ ++ if (cx2 != cx1) { ++ // want smaller x ++ return Integer.compare(cx1, cx2); ++ } ++ ++ // same chunk ++ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y ++ // so now we just compare section y, wanting smaller y ++ ++ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); ++ }); ++ ++ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). ++ ret.addAll(closestRecords); ++ } ++ ++ // finds the closest poi entry pos. ++ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final PoiRecord ret = findNearestPoiRecord( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load ++ ); ++ return ret == null ? null : ret.getPos(); ++ } ++ ++ // finds the closest `max` poi entry positions. ++ public static void findNearestPoiPositions(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final int max, ++ final List, BlockPos>> ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List toConvert = new ArrayList<>(); ++ findNearestPoiRecords( ++ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert ++ ); ++ ++ for (final PoiRecord record : toConvert) { ++ ret.add(Pair.of(record.getPoiType(), record.getPos())); ++ } ++ } ++ ++ // finds the closest poi entry. ++ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final List ret = new ArrayList<>(); ++ findNearestPoiRecords( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ++ 1, ret ++ ); ++ return ret.isEmpty() ? null : ret.get(0); ++ } ++ ++ // finds the closest `max` poi entries. ++ public static void findNearestPoiRecords(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ // position predicate must not modify chunk POI ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final double maxDistance, ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final int max, ++ final List ret) { ++ final Predicate occupancyFilter = occupancy.getTest(); ++ ++ final double maxDistanceSquared = maxDistance * maxDistance; ++ final Double2ObjectRBTreeMap> closestRecords = new Double2ObjectRBTreeMap<>(); ++ int totalRecords = 0; ++ double furthestDistanceSquared = maxDistanceSquared; ++ ++ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; ++ final int lowerY = WorldUtil.getMinSection(poiStorage.world); ++ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; ++ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; ++ final int upperY = WorldUtil.getMaxSection(poiStorage.world); ++ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; ++ ++ final int centerX = sourcePosition.getX() >> 4; ++ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); ++ final int centerZ = sourcePosition.getZ() >> 4; ++ ++ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); ++ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); ++ final LongOpenHashSet seen = new LongOpenHashSet(); ++ ++ while (!queue.isEmpty()) { ++ final long key = queue.dequeueLong(); ++ final int sectionX = CoordinateUtils.getChunkSectionX(key); ++ final int sectionY = CoordinateUtils.getChunkSectionY(key); ++ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); ++ ++ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { ++ // out of bound chunk ++ continue; ++ } ++ ++ final double sectionDistanceSquared = getSmallestDistanceSquared( ++ (sectionX << 4) + 0.5, ++ (sectionY << 4) + 0.5, ++ (sectionZ << 4) + 0.5, ++ (sectionX << 4) + 15.5, ++ (sectionY << 4) + 15.5, ++ (sectionZ << 4) + 15.5, ++ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ() ++ ); ++ ++ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) { ++ continue; ++ } ++ ++ // queue all neighbours ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dy = -1; dy <= 1; ++dy) { ++ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many ++ // values are set. we only care about cardinal neighbours, so, we only care if one value is set ++ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { ++ continue; ++ } ++ ++ final int neighbourX = sectionX + dx; ++ final int neighbourY = sectionY + dy; ++ final int neighbourZ = sectionZ + dz; ++ ++ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); ++ if (seen.add(neighbourKey)) { ++ queue.enqueue(neighbourKey); ++ } ++ } ++ } ++ } ++ ++ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); ++ ++ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { ++ continue; ++ } ++ ++ final PoiSection poiSection = poiSectionOptional.orElse(null); ++ ++ final Map, Set> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry, Set> entry : sectionData.entrySet()) { ++ if (!villagePlaceType.test(entry.getKey())) { ++ // filter out by poi type ++ continue; ++ } ++ ++ // now we can look at the poi data ++ for (final PoiRecord poiData : entry.getValue()) { ++ if (!occupancyFilter.test(poiData)) { ++ // filter by occupancy ++ continue; ++ } ++ ++ final BlockPos poiPosition = poiData.getPos(); ++ ++ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range ++ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { ++ // out of range for square radius ++ continue; ++ } ++ ++ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! ++ final double dataRange = poiPosition.distSqr(sourcePosition); ++ ++ if (dataRange > maxDistanceSquared) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (dataRange > furthestDistanceSquared && totalRecords >= max) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { ++ // filter by position ++ continue; ++ } ++ ++ if (dataRange > furthestDistanceSquared) { ++ // we know totalRecords < max, so this entry is now our furthest ++ furthestDistanceSquared = dataRange; ++ } ++ ++ closestRecords.computeIfAbsent(dataRange, (final double unused) -> { ++ return new ArrayList<>(); ++ }).add(poiData); ++ ++ if (++totalRecords >= max) { ++ if (closestRecords.size() >= 2) { ++ int entriesInClosest = 0; ++ final Iterator>> iterator = closestRecords.double2ObjectEntrySet().iterator(); ++ double nextFurthestDistanceSquared = 0.0; ++ ++ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) { ++ final Double2ObjectMap.Entry> recordEntry = iterator.next(); ++ entriesInClosest += recordEntry.getValue().size(); ++ nextFurthestDistanceSquared = recordEntry.getDoubleKey(); ++ } ++ ++ if (entriesInClosest >= max) { ++ // the last set of entries at range wont even be considered for sure... nuke em ++ final Double2ObjectMap.Entry> recordEntry = iterator.next(); ++ totalRecords -= recordEntry.getValue().size(); ++ iterator.remove(); ++ ++ furthestDistanceSquared = nextFurthestDistanceSquared; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ final List closestRecordsUnsorted = new ArrayList<>(); ++ ++ // we're done here, so now just flatten the map and sort it. ++ ++ for (final List records : closestRecords.values()) { ++ closestRecordsUnsorted.addAll(records); ++ } ++ ++ // uh oh! we might have multiple records that match the distance sorting! ++ // we need to re-order our results by the way vanilla would have iterated over them. ++ closestRecordsUnsorted.sort((record1, record2) -> { ++ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section ++ // is fine and should be preserved (this sort is stable so we're good there) ++ // but they iterate sections by x then by z (like the following) ++ // for (int x = -dx; x <= dx; ++x) ++ // for (int z = -dz; z <= dz; ++z) ++ // .... ++ // so we need to reorder such that records with lower chunk z, then lower chunk x come first ++ final BlockPos pos1 = record1.getPos(); ++ final BlockPos pos2 = record2.getPos(); ++ ++ final int cx1 = pos1.getX() >> 4; ++ final int cz1 = pos1.getZ() >> 4; ++ ++ final int cx2 = pos2.getX() >> 4; ++ final int cz2 = pos2.getZ() >> 4; ++ ++ if (cz2 != cz1) { ++ // want smaller z ++ return Integer.compare(cz1, cz2); ++ } ++ ++ if (cx2 != cx1) { ++ // want smaller x ++ return Integer.compare(cx1, cx2); ++ } ++ ++ // same chunk ++ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y ++ // so now we just compare section y, wanting smaller section y ++ ++ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); ++ }); ++ ++ // trim out any entries exceeding our maximum ++ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) { ++ closestRecordsUnsorted.remove(i); ++ } ++ ++ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). ++ ret.addAll(closestRecordsUnsorted); ++ } ++ ++ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final PoiRecord ret = findAnyPoiRecord( ++ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load ++ ); ++ ++ return ret == null ? null : ret.getPos(); ++ } ++ ++ public static void findAnyPoiPositions(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final int max, ++ final List, BlockPos>> ret) { ++ final Set positions = new HashSet<>(); ++ // pos predicate is last thing that runs before adding to ret. ++ final Predicate newPredicate = (final BlockPos pos) -> { ++ if (positionPredicate != null && !positionPredicate.test(pos)) { ++ return false; ++ } ++ return positions.add(pos.immutable()); ++ }; ++ ++ final List toConvert = new ArrayList<>(); ++ findAnyPoiRecords( ++ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert ++ ); ++ ++ for (final PoiRecord record : toConvert) { ++ ret.add(Pair.of(record.getPoiType(), record.getPos())); ++ } ++ } ++ ++ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final PoiManager.Occupancy occupancy, ++ final boolean load) { ++ final List ret = new ArrayList<>(); ++ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret); ++ return ret.isEmpty() ? null : ret.get(0); ++ } ++ ++ public static void findAnyPoiRecords(final PoiManager poiStorage, ++ final Predicate> villagePlaceType, ++ final Predicate positionPredicate, ++ final BlockPos sourcePosition, ++ final int range, // distance on x y z axis ++ final PoiManager.Occupancy occupancy, ++ final boolean load, ++ final int max, ++ final List ret) { ++ // the biggest issue with the original mojang implementation is that they chain so many streams together ++ // the amount of streams chained just rolls performance, even if nothing is iterated over ++ final Predicate occupancyFilter = occupancy.getTest(); ++ final double rangeSquared = range * range; ++ ++ int added = 0; ++ ++ // First up, we need to iterate the chunks ++ // all the values here are in chunk sections ++ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; ++ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4); ++ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; ++ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; ++ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4); ++ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; ++ ++ // Vanilla iterates by x until max is reached then increases z ++ // vanilla also searches by increasing Y section value ++ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { ++ for (int currX = lowerX; currX <= upperX; ++currX) { ++ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need ++ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) : ++ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)); ++ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); ++ if (poiSection == null) { ++ continue; ++ } ++ ++ final Map, Set> sectionData = poiSection.getData(); ++ if (sectionData.isEmpty()) { ++ continue; ++ } ++ ++ // now we search the section data ++ for (final Map.Entry, Set> entry : sectionData.entrySet()) { ++ if (!villagePlaceType.test(entry.getKey())) { ++ // filter out by poi type ++ continue; ++ } ++ ++ // now we can look at the poi data ++ for (final PoiRecord poiData : entry.getValue()) { ++ if (!occupancyFilter.test(poiData)) { ++ // filter by occupancy ++ continue; ++ } ++ ++ final BlockPos poiPosition = poiData.getPos(); ++ ++ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range ++ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { ++ // out of range for square radius ++ continue; ++ } ++ ++ if (poiPosition.distSqr(sourcePosition) > rangeSquared) { ++ // out of range for distance check ++ continue; ++ } ++ ++ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { ++ // filter by position ++ continue; ++ } ++ ++ // found one! ++ ret.add(poiData); ++ if (++added >= max) { ++ return; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ private PoiAccess() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 4f75f2be7070d49c2c60866ad7146da19ab61652..43243537b765a2d270be6de3f053fea77ff67d18 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -87,7 +87,11 @@ public class AcquirePoi extends Behavior { + return true; + } + }; +- Set, BlockPos>> set = poiManager.findAllClosestFirstWithType(this.poiType, predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); ++ // Paper start - optimise POI access ++ java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); ++ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType, predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); ++ Set, BlockPos>> set = new java.util.HashSet<>(poiposes); ++ // Paper end - optimise POI access + Path path = findPathToPois(entity, set); + if (path != null && path.canReach()) { + BlockPos blockPos = path.getTarget(); +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +index 33fbf72b440e0d164ecd4fb0fdec72e2394d0a1e..8db20db72cd51046213625fac46c35854c59ec5d 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +@@ -53,10 +53,12 @@ public class NearestBedSensor extends Sensor { + return true; + } + }; +- Set, BlockPos>> set = poiManager.findAllWithType((holder) -> { +- return holder.is(PoiTypes.HOME); +- }, predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY).collect(Collectors.toSet()); +- Path path = AcquirePoi.findPathToPois(entity, set); ++ // Paper start - optimise POI access ++ java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); ++ // don't ask me why it's unbounded. ask mojang. ++ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); ++ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); ++ // Paper end - optimise POI access + if (path != null && path.canReach()) { + BlockPos blockPos = path.getTarget(); + Optional> optional = poiManager.getType(blockPos); +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index ab9bb440c8e91ecb49c1e14a427d35087a87ac80..497a81e49d54380713c18523ae8f09f94c453721 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -40,7 +40,7 @@ public class PoiManager extends SectionStorage { + public static final int VILLAGE_SECTION_SIZE = 1; + private final PoiManager.DistanceTracker distanceTracker; + private final LongSet loadedChunks = new LongOpenHashSet(); +- private final net.minecraft.server.level.ServerLevel world; // Paper ++ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public + + public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { + super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); +@@ -113,43 +113,62 @@ public class PoiManager extends SectionStorage { + } + + public Optional find(Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { +- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); ++ // Paper start - re-route to faster logic ++ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); ++ return Optional.ofNullable(ret); ++ // Paper end + } + + public Optional findClosest(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { +- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> { +- return blockPos2.distSqr(pos); +- })); ++ // Paper start - re-route to faster logic ++ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false); ++ return Optional.ofNullable(ret); ++ // Paper end - re-route to faster logic + } + + public Optional, BlockPos>> findClosestWithType(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { +- return this.getInRange(typePredicate, pos, radius, occupationStatus).min(Comparator.comparingDouble((poi) -> { +- return poi.getPos().distSqr(pos); +- })).map((poi) -> { +- return Pair.of(poi.getPoiType(), poi.getPos()); +- }); ++ // Paper start - re-route to faster logic ++ return Optional.ofNullable(io.papermc.paper.util.PoiAccess.findClosestPoiDataTypeAndPosition( ++ this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false ++ )); ++ // Paper end - re-route to faster logic + } + + public Optional findClosest(Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { +- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).filter(posPredicate).min(Comparator.comparingDouble((blockPos2) -> { +- return blockPos2.distSqr(pos); +- })); ++ // Paper start - re-route to faster logic ++ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false); ++ return Optional.ofNullable(ret); ++ // Paper end - re-route to faster logic + } + + public Optional take(Predicate> typePredicate, BiPredicate, BlockPos> biPredicate, BlockPos pos, int radius) { +- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> { +- return biPredicate.test(poi.getPoiType(), poi.getPos()); +- }).findFirst().map((poi) -> { ++ // Paper start - re-route to faster logic ++ final @javax.annotation.Nullable PoiRecord closest = io.papermc.paper.util.PoiAccess.findClosestPoiDataRecord( ++ this, typePredicate, biPredicate, pos, radius, radius * radius, Occupancy.HAS_SPACE, false ++ ); ++ return Optional.ofNullable(closest).map(poi -> { ++ // Paper end - re-route to faster logic + poi.acquireTicket(); + return poi.getPos(); + }); + } + + public Optional getRandom(Predicate> typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, RandomSource random) { +- List list = Util.toShuffledList(this.getInRange(typePredicate, pos, radius, occupationStatus), random); +- return list.stream().filter((poi) -> { +- return positionPredicate.test(poi.getPos()); +- }).findFirst().map(PoiRecord::getPos); ++ // Paper start - re-route to faster logic ++ List list = new java.util.ArrayList<>(); ++ io.papermc.paper.util.PoiAccess.findAnyPoiRecords( ++ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list ++ ); ++ ++ // the old method shuffled the list and then tried to find the first element in it that ++ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a ++ // shuffle entirely, and just pick a random element from list ++ if (list.isEmpty()) { ++ return Optional.empty(); ++ } ++ ++ return Optional.of(list.get(random.nextInt(list.size())).getPos()); ++ // Paper end - re-route to faster logic + } + + public boolean release(BlockPos pos) { +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +index bb2be6eea7a0cff4cc70bd43738b1ce213e43558..b71a4027a0eed467a3707c59315092ddecfd6bf3 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java +@@ -26,7 +26,7 @@ import org.slf4j.Logger; + public class PoiSection { + private static final Logger LOGGER = LogUtils.getLogger(); + private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); +- private final Map, Set> byType = Maps.newHashMap(); ++ private final Map, Set> byType = Maps.newHashMap(); public final Map, Set> getData() { return this.byType; } // Paper - public accessor + private final Runnable setDirty; + private boolean isValid; + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 678bd36581ead3a225e3a6e24b78e5db4e42657b..9b2cec7528936a5d53a926c91063cb6e9ed7da1b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -71,11 +71,11 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl + } + + @Nullable +- protected Optional get(long pos) { ++ public Optional get(long pos) { // Paper - public + return this.storage.get(pos); + } + +- protected Optional getOrLoad(long pos) { ++ public Optional getOrLoad(long pos) { // Paper - public + if (this.outsideStoredRange(pos)) { + return Optional.empty(); + } else { +diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +index 1311d69bb2fa7b3617936e6ad6eb5236fedc260d..386a73f32f2504af81107852307dcd393d4d8a11 100644 +--- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java ++++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +@@ -51,18 +51,39 @@ public class PortalForcer { + // int i = flag ? 16 : 128; + // CraftBukkit end + +- villageplace.ensureLoadedAndValid(this.level, blockposition, i); +- Optional optional = villageplace.getInSquare((holder) -> { +- return holder.is(PoiTypes.NETHER_PORTAL); +- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> { +- return worldborder.isWithinBounds(villageplacerecord.getPos()); +- }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error +- return villageplacerecord.getPos().distSqr(blockposition); +- }).thenComparingInt((villageplacerecord) -> { +- return villageplacerecord.getPos().getY(); +- })).filter((villageplacerecord) -> { +- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); +- }).findFirst(); ++ // Paper start - optimise portals ++ Optional optional; ++ java.util.List records = new java.util.ArrayList<>(); ++ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( ++ villageplace, ++ type -> type.is(PoiTypes.NETHER_PORTAL), ++ (BlockPos pos) -> { ++ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY); ++ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL) ++ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.HEIGHTMAPS))) { ++ // why would we generate the chunk? ++ return false; ++ } ++ if (!worldborder.isWithinBounds(pos)) { ++ return false; ++ } ++ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); ++ }, ++ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records ++ ); ++ ++ // this gets us most of the way there, but we bias towards lower y values. ++ PoiRecord lowestYRecord = null; ++ for (PoiRecord record : records) { ++ if (lowestYRecord == null) { ++ lowestYRecord = record; ++ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) { ++ lowestYRecord = record; ++ } ++ } ++ // now we're done ++ optional = Optional.ofNullable(lowestYRecord); ++ // Paper end - optimise portals + + return optional.map((villageplacerecord) -> { + BlockPos blockposition1 = villageplacerecord.getPos(); diff --git a/patches/server/0733-Add-more-async-catchers.patch b/patches/server/0733-Add-more-async-catchers.patch new file mode 100644 index 0000000000..c455647bca --- /dev/null +++ b/patches/server/0733-Add-more-async-catchers.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 15 Jul 2021 01:41:53 -0700 +Subject: [PATCH] Add more async catchers + + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +index 2830d32bba3dc85847e3a5d9b4d98f822e34b606..a176a886235494fdc722030a93658d361bf50f03 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +@@ -29,11 +29,13 @@ public class EntityTickList { + } + + public void add(Entity entity) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper + this.ensureActiveIsNotIterated(); + this.active.put(entity.getId(), entity); + } + + public void remove(Entity entity) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper + this.ensureActiveIsNotIterated(); + this.active.remove(entity.getId()); + } +@@ -43,6 +45,7 @@ public class EntityTickList { + } + + public void forEach(Consumer action) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper + if (this.iterated != null) { + throw new UnsupportedOperationException("Only one concurrent iteration supported"); + } else { +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70..a1a52669c19af22e3b5267d43584cb00d1646453 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -178,6 +178,7 @@ public class PersistentEntitySectionManager implements A + } + + public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) { ++ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper + Visibility visibility = Visibility.fromFullChunkStatus(levelType); + + this.updateChunkStatus(chunkPos, visibility); diff --git a/patches/server/0733-Optimise-general-POI-access.patch b/patches/server/0733-Optimise-general-POI-access.patch deleted file mode 100644 index b7014db094..0000000000 --- a/patches/server/0733-Optimise-general-POI-access.patch +++ /dev/null @@ -1,1059 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 31 Jan 2021 02:29:24 -0800 -Subject: [PATCH] Optimise general POI access - -There are a couple of problems with mojang's POI code. -Firstly, it's all streams. Unsurprisingly, stacking -streams on top of each other is horrible for performance -and ultimately took up half of a villager's tick! - -Secondly, sometime's the search radius is large and there are -a significant number of poi entries per chunk section. Even -removing streams at this point doesn't help much. The only solution -is to start at the search point and iterate outwards. This -type of approach shows massive gains for portals, simply because -we can avoid sync loading a large area of chunks. I also tested -a massive farm I found in JellySquid's discord, which showed -to benefit significantly simply because the farm had so many -portal blocks that searching through them all was very slow. - -Great care has been taken so that behavior remains identical to -vanilla, however I cannot account for oddball Stream API -implementations, if they even exist (streams can technically -be loose with iteration order in a sorted stream given its -source stream is not tagged with ordered, and mojang does not -tag the source stream as ordered). However in my testing on openjdk -there showed no difference, as expected. - -This patch also specifically optimises other areas of code to -use PoiAccess. For example, some villager AI and portaling code -had to be specifically modified. - -diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1e7be5da4907616ad9e1e01a2227d29b6dd54b32 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java -@@ -0,0 +1,801 @@ -+package io.papermc.paper.util; -+ -+import com.mojang.datafixers.util.Pair; -+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap; -+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import java.util.function.BiPredicate; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Holder; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.ai.village.poi.PoiManager; -+import net.minecraft.world.entity.ai.village.poi.PoiRecord; -+import net.minecraft.world.entity.ai.village.poi.PoiSection; -+import net.minecraft.world.entity.ai.village.poi.PoiType; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.Set; -+import java.util.function.Predicate; -+ -+/** -+ * Provides optimised access to POI data. All returned values will be identical to vanilla. -+ */ -+public final class PoiAccess { -+ -+ protected static double clamp(final double val, final double min, final double max) { -+ return (val < min ? min : (val > max ? max : val)); -+ } -+ -+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ, -+ final double boxMaxX, final double boxMaxY, final double boxMaxZ, -+ -+ final double circleX, final double circleY, final double circleZ) { -+ // is the circle center inside the box? -+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) { -+ return 0.0; -+ } -+ -+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0; -+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0; -+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0; -+ -+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0; -+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0; -+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0; -+ -+ double centerDiffX = circleX - boxCenterX; -+ double centerDiffY = circleY - boxCenterY; -+ double centerDiffZ = circleZ - boxCenterZ; -+ -+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX); -+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY); -+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ); -+ -+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ); -+ } -+ -+ -+ // key is: -+ // upper 32 bits: -+ // upper 16 bits: max y section -+ // lower 16 bits: min y section -+ // lower 32 bits: -+ // upper 16 bits: section -+ // lower 16 bits: radius -+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) { -+ return ( -+ (maxSection & 0xFFFFL) << (64 - 16) -+ | (minSection & 0xFFFFL) << (64 - 32) -+ | (section & 0xFFFFL) << (64 - 48) -+ | (radius & 0xFFFFL) << (64 - 64) -+ ); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findClosestPoiDataRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static Pair, BlockPos> findClosestPoiDataTypeAndPosition(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findClosestPoiDataRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ -+ return ret == null ? null : Pair.of(ret.getPoiType(), ret.getPos()); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataPositions(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final Set ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final BiPredicate, BlockPos> predicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistance, occupancy, load, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataRecords(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final List ret) { -+ final BiPredicate, BlockPos> predicate = positionPredicate != null ? (type, pos) -> positionPredicate.test(pos) : null; -+ findClosestPoiDataRecords(poiStorage, villagePlaceType, predicate, sourcePosition, range, maxDistance, occupancy, load, ret); -+ } -+ -+ public static void findClosestPoiDataRecords(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final BiPredicate, BlockPos> predicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final List closestRecords = new ArrayList<>(); -+ double closestDistanceSquared = maxDistance * maxDistance; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ() -+ ); -+ if (sectionDistanceSquared > closestDistanceSquared) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map, Set> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry, Set> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > closestDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(poiData.getPoiType(), poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange < closestDistanceSquared) { -+ closestRecords.clear(); -+ closestDistanceSquared = dataRange; -+ } -+ closestRecords.add(poiData); -+ } -+ } -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecords.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecords); -+ } -+ -+ // finds the closest poi entry pos. -+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findNearestPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // finds the closest `max` poi entry positions. -+ public static void findNearestPoiPositions(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List, BlockPos>> ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(Pair.of(record.getPoiType(), record.getPos())); -+ } -+ } -+ -+ // finds the closest poi entry. -+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, -+ 1, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // finds the closest `max` poi entries. -+ public static void findNearestPoiRecords(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final double maxDistanceSquared = maxDistance * maxDistance; -+ final Double2ObjectRBTreeMap> closestRecords = new Double2ObjectRBTreeMap<>(); -+ int totalRecords = 0; -+ double furthestDistanceSquared = maxDistanceSquared; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ() -+ ); -+ -+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map, Set> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry, Set> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > maxDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared && totalRecords >= max) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared) { -+ // we know totalRecords < max, so this entry is now our furthest -+ furthestDistanceSquared = dataRange; -+ } -+ -+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> { -+ return new ArrayList<>(); -+ }).add(poiData); -+ -+ if (++totalRecords >= max) { -+ if (closestRecords.size() >= 2) { -+ int entriesInClosest = 0; -+ final Iterator>> iterator = closestRecords.double2ObjectEntrySet().iterator(); -+ double nextFurthestDistanceSquared = 0.0; -+ -+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) { -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ entriesInClosest += recordEntry.getValue().size(); -+ nextFurthestDistanceSquared = recordEntry.getDoubleKey(); -+ } -+ -+ if (entriesInClosest >= max) { -+ // the last set of entries at range wont even be considered for sure... nuke em -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ totalRecords -= recordEntry.getValue().size(); -+ iterator.remove(); -+ -+ furthestDistanceSquared = nextFurthestDistanceSquared; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ final List closestRecordsUnsorted = new ArrayList<>(); -+ -+ // we're done here, so now just flatten the map and sort it. -+ -+ for (final List records : closestRecords.values()) { -+ closestRecordsUnsorted.addAll(records); -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecordsUnsorted.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller section y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // trim out any entries exceeding our maximum -+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) { -+ closestRecordsUnsorted.remove(i); -+ } -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecordsUnsorted); -+ } -+ -+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findAnyPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ public static void findAnyPoiPositions(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List, BlockPos>> ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findAnyPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(Pair.of(record.getPoiType(), record.getPos())); -+ } -+ } -+ -+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ public static void findAnyPoiRecords(final PoiManager poiStorage, -+ final Predicate> villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ // the biggest issue with the original mojang implementation is that they chain so many streams together -+ // the amount of streams chained just rolls performance, even if nothing is iterated over -+ final Predicate occupancyFilter = occupancy.getTest(); -+ final double rangeSquared = range * range; -+ -+ int added = 0; -+ -+ // First up, we need to iterate the chunks -+ // all the values here are in chunk sections -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ // Vanilla iterates by x until max is reached then increases z -+ // vanilla also searches by increasing Y section value -+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { -+ for (int currX = lowerX; currX <= upperX; ++currX) { -+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) : -+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)); -+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); -+ if (poiSection == null) { -+ continue; -+ } -+ -+ final Map, Set> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry, Set> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ // found one! -+ ret.add(poiData); -+ if (++added >= max) { -+ return; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ private PoiAccess() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -index 4f75f2be7070d49c2c60866ad7146da19ab61652..43243537b765a2d270be6de3f053fea77ff67d18 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -@@ -87,7 +87,11 @@ public class AcquirePoi extends Behavior { - return true; - } - }; -- Set, BlockPos>> set = poiManager.findAllClosestFirstWithType(this.poiType, predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); -+ // Paper start - optimise POI access -+ java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType, predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ Set, BlockPos>> set = new java.util.HashSet<>(poiposes); -+ // Paper end - optimise POI access - Path path = findPathToPois(entity, set); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -index 33fbf72b440e0d164ecd4fb0fdec72e2394d0a1e..8db20db72cd51046213625fac46c35854c59ec5d 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -@@ -53,10 +53,12 @@ public class NearestBedSensor extends Sensor { - return true; - } - }; -- Set, BlockPos>> set = poiManager.findAllWithType((holder) -> { -- return holder.is(PoiTypes.HOME); -- }, predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY).collect(Collectors.toSet()); -- Path path = AcquirePoi.findPathToPois(entity, set); -+ // Paper start - optimise POI access -+ java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); -+ // don't ask me why it's unbounded. ask mojang. -+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); -+ Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); -+ // Paper end - optimise POI access - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); - Optional> optional = poiManager.getType(blockPos); -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index ab9bb440c8e91ecb49c1e14a427d35087a87ac80..497a81e49d54380713c18523ae8f09f94c453721 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -40,7 +40,7 @@ public class PoiManager extends SectionStorage { - public static final int VILLAGE_SECTION_SIZE = 1; - private final PoiManager.DistanceTracker distanceTracker; - private final LongSet loadedChunks = new LongOpenHashSet(); -- private final net.minecraft.server.level.ServerLevel world; // Paper -+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public - - public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { - super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); -@@ -113,43 +113,62 @@ public class PoiManager extends SectionStorage { - } - - public Optional find(Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - } - - public Optional findClosest(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(pos); -- })); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - re-route to faster logic - } - - public Optional, BlockPos>> findClosestWithType(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).min(Comparator.comparingDouble((poi) -> { -- return poi.getPos().distSqr(pos); -- })).map((poi) -> { -- return Pair.of(poi.getPoiType(), poi.getPos()); -- }); -+ // Paper start - re-route to faster logic -+ return Optional.ofNullable(io.papermc.paper.util.PoiAccess.findClosestPoiDataTypeAndPosition( -+ this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false -+ )); -+ // Paper end - re-route to faster logic - } - - public Optional findClosest(Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).filter(posPredicate).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(pos); -- })); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - re-route to faster logic - } - - public Optional take(Predicate> typePredicate, BiPredicate, BlockPos> biPredicate, BlockPos pos, int radius) { -- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> { -- return biPredicate.test(poi.getPoiType(), poi.getPos()); -- }).findFirst().map((poi) -> { -+ // Paper start - re-route to faster logic -+ final @javax.annotation.Nullable PoiRecord closest = io.papermc.paper.util.PoiAccess.findClosestPoiDataRecord( -+ this, typePredicate, biPredicate, pos, radius, radius * radius, Occupancy.HAS_SPACE, false -+ ); -+ return Optional.ofNullable(closest).map(poi -> { -+ // Paper end - re-route to faster logic - poi.acquireTicket(); - return poi.getPos(); - }); - } - - public Optional getRandom(Predicate> typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, RandomSource random) { -- List list = Util.toShuffledList(this.getInRange(typePredicate, pos, radius, occupationStatus), random); -- return list.stream().filter((poi) -> { -- return positionPredicate.test(poi.getPos()); -- }).findFirst().map(PoiRecord::getPos); -+ // Paper start - re-route to faster logic -+ List list = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findAnyPoiRecords( -+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list -+ ); -+ -+ // the old method shuffled the list and then tried to find the first element in it that -+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a -+ // shuffle entirely, and just pick a random element from list -+ if (list.isEmpty()) { -+ return Optional.empty(); -+ } -+ -+ return Optional.of(list.get(random.nextInt(list.size())).getPos()); -+ // Paper end - re-route to faster logic - } - - public boolean release(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index bb2be6eea7a0cff4cc70bd43738b1ce213e43558..b71a4027a0eed467a3707c59315092ddecfd6bf3 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -26,7 +26,7 @@ import org.slf4j.Logger; - public class PoiSection { - private static final Logger LOGGER = LogUtils.getLogger(); - private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); -- private final Map, Set> byType = Maps.newHashMap(); -+ private final Map, Set> byType = Maps.newHashMap(); public final Map, Set> getData() { return this.byType; } // Paper - public accessor - private final Runnable setDirty; - private boolean isValid; - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 678bd36581ead3a225e3a6e24b78e5db4e42657b..9b2cec7528936a5d53a926c91063cb6e9ed7da1b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -71,11 +71,11 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - } - - @Nullable -- protected Optional get(long pos) { -+ public Optional get(long pos) { // Paper - public - return this.storage.get(pos); - } - -- protected Optional getOrLoad(long pos) { -+ public Optional getOrLoad(long pos) { // Paper - public - if (this.outsideStoredRange(pos)) { - return Optional.empty(); - } else { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 1311d69bb2fa7b3617936e6ad6eb5236fedc260d..386a73f32f2504af81107852307dcd393d4d8a11 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -51,18 +51,39 @@ public class PortalForcer { - // int i = flag ? 16 : 128; - // CraftBukkit end - -- villageplace.ensureLoadedAndValid(this.level, blockposition, i); -- Optional optional = villageplace.getInSquare((holder) -> { -- return holder.is(PoiTypes.NETHER_PORTAL); -- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> { -- return worldborder.isWithinBounds(villageplacerecord.getPos()); -- }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error -- return villageplacerecord.getPos().distSqr(blockposition); -- }).thenComparingInt((villageplacerecord) -> { -- return villageplacerecord.getPos().getY(); -- })).filter((villageplacerecord) -> { -- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -- }).findFirst(); -+ // Paper start - optimise portals -+ Optional optional; -+ java.util.List records = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( -+ villageplace, -+ type -> type.is(PoiTypes.NETHER_PORTAL), -+ (BlockPos pos) -> { -+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY); -+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL) -+ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.HEIGHTMAPS))) { -+ // why would we generate the chunk? -+ return false; -+ } -+ if (!worldborder.isWithinBounds(pos)) { -+ return false; -+ } -+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -+ }, -+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records -+ ); -+ -+ // this gets us most of the way there, but we bias towards lower y values. -+ PoiRecord lowestYRecord = null; -+ for (PoiRecord record : records) { -+ if (lowestYRecord == null) { -+ lowestYRecord = record; -+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) { -+ lowestYRecord = record; -+ } -+ } -+ // now we're done -+ optional = Optional.ofNullable(lowestYRecord); -+ // Paper end - optimise portals - - return optional.map((villageplacerecord) -> { - BlockPos blockposition1 = villageplacerecord.getPos(); diff --git a/patches/server/0734-Add-more-async-catchers.patch b/patches/server/0734-Add-more-async-catchers.patch deleted file mode 100644 index c455647bca..0000000000 --- a/patches/server/0734-Add-more-async-catchers.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 15 Jul 2021 01:41:53 -0700 -Subject: [PATCH] Add more async catchers - - -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index 2830d32bba3dc85847e3a5d9b4d98f822e34b606..a176a886235494fdc722030a93658d361bf50f03 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -29,11 +29,13 @@ public class EntityTickList { - } - - public void add(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper - this.ensureActiveIsNotIterated(); - this.active.put(entity.getId(), entity); - } - - public void remove(Entity entity) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper - this.ensureActiveIsNotIterated(); - this.active.remove(entity.getId()); - } -@@ -43,6 +45,7 @@ public class EntityTickList { - } - - public void forEach(Consumer action) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper - if (this.iterated != null) { - throw new UnsupportedOperationException("Only one concurrent iteration supported"); - } else { -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70..a1a52669c19af22e3b5267d43584cb00d1646453 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -178,6 +178,7 @@ public class PersistentEntitySectionManager implements A - } - - public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper - Visibility visibility = Visibility.fromFullChunkStatus(levelType); - - this.updateChunkStatus(chunkPos, visibility); diff --git a/patches/server/0734-Rewrite-entity-bounding-box-lookup-calls.patch b/patches/server/0734-Rewrite-entity-bounding-box-lookup-calls.patch new file mode 100644 index 0000000000..5179560e78 --- /dev/null +++ b/patches/server/0734-Rewrite-entity-bounding-box-lookup-calls.patch @@ -0,0 +1,1300 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 17 Jun 2021 19:55:02 -0700 +Subject: [PATCH] Rewrite entity bounding box lookup calls + +For whatever reason, Mojang thought it was OK to make this system +scale logn relative to the number of entity sections loaded. +On top of that, they do a hashtable lookup per section - before +this was just a basic array access. + +This patch brings back entity slices for lookup only. + +diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +new file mode 100644 +index 0000000000000000000000000000000000000000..47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +@@ -0,0 +1,500 @@ ++package io.papermc.paper.world; ++ ++import com.destroystokyo.paper.util.maplist.EntityList; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.entity.boss.EnderDragonPart; ++import net.minecraft.world.entity.boss.enderdragon.EnderDragon; ++import net.minecraft.world.phys.AABB; ++import java.util.Arrays; ++import java.util.Iterator; ++import java.util.List; ++import java.util.function.Predicate; ++ ++public final class ChunkEntitySlices { ++ ++ protected final int minSection; ++ protected final int maxSection; ++ protected final int chunkX; ++ protected final int chunkZ; ++ protected final ServerLevel world; ++ ++ protected final EntityCollectionBySection allEntities; ++ protected final EntityCollectionBySection hardCollidingEntities; ++ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; ++ protected final EntityList entities = new EntityList(); ++ ++ public ChunkHolder.FullChunkStatus status; ++ ++ // TODO implement container search optimisations ++ ++ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus status, ++ final int minSection, final int maxSection) { // inclusive, inclusive ++ this.minSection = minSection; ++ this.maxSection = maxSection; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.world = world; ++ ++ this.allEntities = new EntityCollectionBySection(this); ++ this.hardCollidingEntities = new EntityCollectionBySection(this); ++ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); ++ ++ this.status = status; ++ } ++ ++ // Paper start - optimise CraftChunk#getEntities ++ public org.bukkit.entity.Entity[] getChunkEntities() { ++ List ret = new java.util.ArrayList<>(); ++ final Entity[] entities = this.entities.getRawData(); ++ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { ++ final Entity entity = entities[i]; ++ if (entity == null) { ++ continue; ++ } ++ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); ++ if (bukkit != null && bukkit.isValid()) { ++ ret.add(bukkit); ++ } ++ } ++ ++ return ret.toArray(new org.bukkit.entity.Entity[0]); ++ } ++ // Paper end - optimise CraftChunk#getEntities ++ ++ public boolean isEmpty() { ++ return this.entities.size() == 0; ++ } ++ ++ private void updateTicketLevels() { ++ final Entity[] entities = this.entities.getRawData(); ++ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { ++ final Entity entity = entities[i]; ++ entity.chunkStatus = this.status; ++ } ++ } ++ ++ public synchronized void updateStatus(final ChunkHolder.FullChunkStatus status) { ++ this.status = status; ++ this.updateTicketLevels(); ++ } ++ ++ public synchronized void addEntity(final Entity entity, final int chunkSection) { ++ if (!this.entities.add(entity)) { ++ return; ++ } ++ entity.chunkStatus = this.status; ++ final int sectionIndex = chunkSection - this.minSection; ++ ++ this.allEntities.addEntity(entity, sectionIndex); ++ ++ if (entity.hardCollides()) { ++ this.hardCollidingEntities.addEntity(entity, sectionIndex); ++ } ++ ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().addEntity(entity, sectionIndex); ++ } ++ } ++ } ++ ++ public synchronized void removeEntity(final Entity entity, final int chunkSection) { ++ if (!this.entities.remove(entity)) { ++ return; ++ } ++ entity.chunkStatus = ChunkHolder.FullChunkStatus.INACCESSIBLE; ++ final int sectionIndex = chunkSection - this.minSection; ++ ++ this.allEntities.removeEntity(entity, sectionIndex); ++ ++ if (entity.hardCollides()) { ++ this.hardCollidingEntities.removeEntity(entity, sectionIndex); ++ } ++ ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().removeEntity(entity, sectionIndex); ++ } ++ } ++ } ++ ++ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.hardCollidingEntities.getEntities(except, box, into, predicate); ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); ++ } ++ ++ protected EntityCollectionBySection initClass(final Class clazz) { ++ final EntityCollectionBySection ret = new EntityCollectionBySection(this); ++ ++ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { ++ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; ++ if (sectionEntities == null) { ++ continue; ++ } ++ ++ final Entity[] storage = sectionEntities.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (clazz.isInstance(entity)) { ++ ret.addEntity(entity, sectionIndex); ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); ++ if (collection != null) { ++ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); ++ } else { ++ synchronized (this) { ++ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); ++ } ++ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); ++ } ++ } ++ ++ public synchronized void updateEntity(final Entity entity) { ++ /*// TODO ++ if (prev aabb != entity.getBoundingBox()) { ++ this.entityMap.delete(entity, prev aabb); ++ this.entityMap.insert(entity, prev aabb = entity.getBoundingBox()); ++ }*/ ++ } ++ ++ protected static final class BasicEntityList { ++ ++ protected static final Entity[] EMPTY = new Entity[0]; ++ protected static final int DEFAULT_CAPACITY = 4; ++ ++ protected E[] storage; ++ protected int size; ++ ++ public BasicEntityList() { ++ this(0); ++ } ++ ++ public BasicEntityList(final int cap) { ++ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); ++ } ++ ++ public boolean isEmpty() { ++ return this.size == 0; ++ } ++ ++ public int size() { ++ return this.size; ++ } ++ ++ private void resize() { ++ if (this.storage == EMPTY) { ++ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; ++ } else { ++ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); ++ } ++ } ++ ++ public void add(final E entity) { ++ final int idx = this.size++; ++ if (idx >= this.storage.length) { ++ this.resize(); ++ this.storage[idx] = entity; ++ } else { ++ this.storage[idx] = entity; ++ } ++ } ++ ++ public int indexOf(final E entity) { ++ final E[] storage = this.storage; ++ ++ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { ++ if (storage[i] == entity) { ++ return i; ++ } ++ } ++ ++ return -1; ++ } ++ ++ public boolean remove(final E entity) { ++ final int idx = this.indexOf(entity); ++ if (idx == -1) { ++ return false; ++ } ++ ++ final int size = --this.size; ++ final E[] storage = this.storage; ++ if (idx != size) { ++ System.arraycopy(storage, idx + 1, storage, idx, size - idx); ++ } ++ ++ storage[size] = null; ++ ++ return true; ++ } ++ ++ public boolean has(final E entity) { ++ return this.indexOf(entity) != -1; ++ } ++ } ++ ++ protected static final class EntityCollectionBySection { ++ ++ protected final ChunkEntitySlices manager; ++ protected final long[] nonEmptyBitset; ++ protected final BasicEntityList[] entitiesBySection; ++ protected int count; ++ ++ public EntityCollectionBySection(final ChunkEntitySlices manager) { ++ this.manager = manager; ++ ++ final int sectionCount = manager.maxSection - manager.minSection + 1; ++ ++ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE ++ this.entitiesBySection = new BasicEntityList[sectionCount]; ++ } ++ ++ public void addEntity(final Entity entity, final int sectionIndex) { ++ BasicEntityList list = this.entitiesBySection[sectionIndex]; ++ ++ if (list != null && list.has(entity)) { ++ return; ++ } ++ ++ if (list == null) { ++ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); ++ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); ++ } ++ ++ list.add(entity); ++ ++this.count; ++ } ++ ++ public void removeEntity(final Entity entity, final int sectionIndex) { ++ final BasicEntityList list = this.entitiesBySection[sectionIndex]; ++ ++ if (list == null || !list.remove(entity)) { ++ return; ++ } ++ ++ --this.count; ++ ++ if (list.isEmpty()) { ++ this.entitiesBySection[sectionIndex] = null; ++ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); ++ } ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(entity)) { ++ continue; ++ } ++ ++ into.add(entity); ++ } ++ } ++ } ++ ++ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate == null || predicate.test(entity)) { ++ into.add(entity); ++ } // else: continue to test the ender dragon parts ++ ++ if (entity instanceof EnderDragon) { ++ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { ++ if (part == except || !part.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(part)) { ++ continue; ++ } ++ ++ into.add(part); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate == null || predicate.test(entity)) { ++ into.add(entity); ++ } // else: continue to test the ender dragon parts ++ ++ if (entity instanceof EnderDragon) { ++ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { ++ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test(part)) { ++ continue; ++ } ++ ++ into.add(part); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ if (this.count == 0) { ++ return; ++ } ++ ++ final int minSection = this.manager.minSection; ++ final int maxSection = this.manager.maxSection; ++ ++ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); ++ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); ++ ++ // TODO use the bitset ++ ++ final BasicEntityList[] entitiesBySection = this.entitiesBySection; ++ ++ for (int section = min; section <= max; ++section) { ++ final BasicEntityList list = entitiesBySection[section - minSection]; ++ ++ if (list == null) { ++ continue; ++ } ++ ++ final Entity[] storage = list.storage; ++ ++ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ final Entity entity = storage[i]; ++ ++ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { ++ continue; ++ } ++ ++ if (predicate != null && !predicate.test((T)entity)) { ++ continue; ++ } ++ ++ into.add((T)entity); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/world/EntitySliceManager.java b/src/main/java/io/papermc/paper/world/EntitySliceManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3ba094e640d7fe7803e2bbdab8ff3beb6f50e8a0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/world/EntitySliceManager.java +@@ -0,0 +1,391 @@ ++package io.papermc.paper.world; ++ ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ChunkHolder; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.phys.AABB; ++import java.util.List; ++import java.util.concurrent.locks.StampedLock; ++import java.util.function.Predicate; ++ ++public final class EntitySliceManager { ++ ++ protected static final int REGION_SHIFT = 5; ++ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; ++ protected static final int REGION_SIZE = 1 << REGION_SHIFT; ++ ++ public final ServerLevel world; ++ ++ private final StampedLock stateLock = new StampedLock(); ++ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(64, 0.7f); ++ ++ private final int minSection; // inclusive ++ private final int maxSection; // inclusive ++ ++ protected final Long2ObjectOpenHashMap statusMap = new Long2ObjectOpenHashMap<>(); ++ { ++ this.statusMap.defaultReturnValue(ChunkHolder.FullChunkStatus.INACCESSIBLE); ++ } ++ ++ public EntitySliceManager(final ServerLevel world) { ++ this.world = world; ++ this.minSection = WorldUtil.getMinSection(world); ++ this.maxSection = WorldUtil.getMaxSection(world); ++ } ++ ++ public void chunkStatusChange(final int x, final int z, final ChunkHolder.FullChunkStatus newStatus) { ++ if (newStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) { ++ this.statusMap.remove(CoordinateUtils.getChunkKey(x, z)); ++ } else { ++ this.statusMap.put(CoordinateUtils.getChunkKey(x, z), newStatus); ++ final ChunkEntitySlices slices = this.getChunk(x, z); ++ if (slices != null) { ++ slices.updateStatus(newStatus); ++ } ++ } ++ } ++ ++ public synchronized void addEntity(final Entity entity) { ++ final BlockPos pos = entity.blockPosition(); ++ final int sectionX = pos.getX() >> 4; ++ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); ++ final int sectionZ = pos.getZ() >> 4; ++ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); ++ slices.addEntity(entity, sectionY); ++ ++ entity.sectionX = sectionX; ++ entity.sectionY = sectionY; ++ entity.sectionZ = sectionZ; ++ } ++ ++ public synchronized void removeEntity(final Entity entity) { ++ final ChunkEntitySlices slices = this.getChunk(entity.sectionX, entity.sectionZ); ++ slices.removeEntity(entity, entity.sectionY); ++ if (slices.isEmpty()) { ++ this.removeChunk(entity.sectionX, entity.sectionZ); ++ } ++ } ++ ++ public void moveEntity(final Entity entity) { ++ final BlockPos newPos = entity.blockPosition(); ++ final int newSectionX = newPos.getX() >> 4; ++ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); ++ final int newSectionZ = newPos.getZ() >> 4; ++ ++ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { ++ return; ++ } ++ ++ synchronized (this) { ++ // are we changing chunks? ++ if (newSectionX != entity.sectionX || newSectionZ != entity.sectionZ) { ++ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); ++ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); ++ synchronized (old) { ++ old.removeEntity(entity, entity.sectionY); ++ if (old.isEmpty()) { ++ this.removeChunk(entity.sectionX, entity.sectionZ); ++ } ++ } ++ ++ synchronized (slices) { ++ slices.addEntity(entity, newSectionY); ++ ++ entity.sectionX = newSectionX; ++ entity.sectionY = newSectionY; ++ entity.sectionZ = newSectionZ; ++ } ++ } else { ++ final ChunkEntitySlices slices = this.getChunk(newSectionX, newSectionZ); ++ // same chunk ++ synchronized (slices) { ++ slices.removeEntity(entity, entity.sectionY); ++ slices.addEntity(entity, newSectionY); ++ } ++ entity.sectionY = newSectionY; ++ } ++ } ++ ++ } ++ ++ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { ++ continue; ++ } ++ ++ chunk.getEntities(except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { ++ continue; ++ } ++ ++ chunk.getHardCollidingEntities(except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final EntityType type, final AABB box, final List into, ++ final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { ++ continue; ++ } ++ ++ chunk.getEntities(type, box, (List)into, (Predicate)predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, ++ final Predicate predicate) { ++ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; ++ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; ++ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; ++ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; ++ ++ final int minRegionX = minChunkX >> REGION_SHIFT; ++ final int minRegionZ = minChunkZ >> REGION_SHIFT; ++ final int maxRegionX = maxChunkX >> REGION_SHIFT; ++ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; ++ ++ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { ++ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; ++ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; ++ ++ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { ++ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); ++ ++ if (region == null) { ++ continue; ++ } ++ ++ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; ++ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; ++ ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); ++ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { ++ continue; ++ } ++ ++ chunk.getEntities(clazz, except, box, into, predicate); ++ } ++ } ++ } ++ } ++ } ++ ++ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { ++ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ if (region == null) { ++ return null; ++ } ++ ++ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); ++ } ++ ++ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { ++ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ ChunkEntitySlices ret; ++ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { ++ ret = new ChunkEntitySlices(this.world, chunkX, chunkZ, this.statusMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)), ++ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); ++ ++ this.addChunk(chunkX, chunkZ, ret); ++ ++ return ret; ++ } ++ ++ return ret; ++ } ++ ++ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { ++ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); ++ final long attempt = this.stateLock.tryOptimisticRead(); ++ if (attempt != 0L) { ++ try { ++ final ChunkSlicesRegion ret = this.regions.get(key); ++ ++ if (this.stateLock.validate(attempt)) { ++ return ret; ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable thr) { ++ // ignore ++ } ++ } ++ ++ this.stateLock.readLock(); ++ try { ++ return this.regions.get(key); ++ } finally { ++ this.stateLock.tryUnlockRead(); ++ } ++ } ++ ++ public synchronized void removeChunk(final int chunkX, final int chunkZ) { ++ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); ++ ++ final ChunkSlicesRegion region = this.regions.get(key); ++ final int remaining = region.remove(relIndex); ++ ++ if (remaining == 0) { ++ this.stateLock.writeLock(); ++ try { ++ this.regions.remove(key); ++ } finally { ++ this.stateLock.tryUnlockWrite(); ++ } ++ } ++ } ++ ++ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { ++ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); ++ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); ++ ++ ChunkSlicesRegion region = this.regions.get(key); ++ if (region != null) { ++ region.add(relIndex, slices); ++ } else { ++ region = new ChunkSlicesRegion(); ++ region.add(relIndex, slices); ++ this.stateLock.writeLock(); ++ try { ++ this.regions.put(key, region); ++ } finally { ++ this.stateLock.tryUnlockWrite(); ++ } ++ } ++ } ++ ++ public static final class ChunkSlicesRegion { ++ ++ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; ++ protected int sliceCount; ++ ++ public ChunkEntitySlices get(final int index) { ++ return this.slices[index]; ++ } ++ ++ public int remove(final int index) { ++ final ChunkEntitySlices slices = this.slices[index]; ++ if (slices == null) { ++ throw new IllegalStateException(); ++ } ++ ++ this.slices[index] = null; ++ ++ return --this.sliceCount; ++ } ++ ++ public void add(final int index, final ChunkEntitySlices slices) { ++ final ChunkEntitySlices curr = this.slices[index]; ++ if (curr != null) { ++ throw new IllegalStateException(); ++ } ++ ++ this.slices[index] = slices; ++ ++ ++this.sliceCount; ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 39d96b4e3dce6d67b568b7b00456de164f6a7241..29b80d074600fa7e20b05d1fe70ac12969b954a4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -454,7 +454,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + DataFixer datafixer = minecraftserver.getFixerUpper(); + EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); + +- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); ++ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper + StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); + int j = this.spigotConfig.viewDistance; // Spigot + int k = this.spigotConfig.simulationDistance; // Spigot +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 32d6e4b194c3c4eca7009059f8d185896b5ae556..51d3150e732f95be13f5f54d994dab1fa89ed3f2 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -498,4 +498,21 @@ public class WorldGenRegion implements WorldGenLevel { + public long nextSubTickCount() { + return this.subTickCount.getAndIncrement(); + } ++ ++ // Paper start ++ // No-op, this class doesn't provide entity access ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 5936fb0d27266e0e91eae5cf49e15f67aeb208bf..f8458173dd717ca5fd9d265dcdaee0f0ef1a833c 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -490,6 +490,56 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + // Paper end - make end portalling safe + ++ // Paper start ++ /** ++ * Overriding this field will cause memory leaks. ++ */ ++ private final boolean hardCollides; ++ ++ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); ++ { ++ /* // Goodbye, broken on reobf... ++ Boolean hardCollides = cachedOverrides.get(this.getClass()); ++ if (hardCollides == null) { ++ try { ++ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); ++ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); ++ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) ++ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { ++ hardCollides = Boolean.TRUE; ++ } else { ++ hardCollides = Boolean.FALSE; ++ } ++ cachedOverrides.put(this.getClass(), hardCollides); ++ } ++ catch (ThreadDeath thr) { throw thr; } ++ catch (Throwable thr) { ++ // shouldn't happen, just explode ++ throw new RuntimeException(thr); ++ } ++ } */ ++ this.hardCollides = this instanceof Boat ++ || this instanceof net.minecraft.world.entity.monster.Shulker ++ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart ++ || this.shouldHardCollide(); ++ } ++ ++ // plugins can override ++ protected boolean shouldHardCollide() { ++ return false; ++ } ++ ++ public final boolean hardCollides() { ++ return this.hardCollides; ++ } ++ ++ public net.minecraft.server.level.ChunkHolder.FullChunkStatus chunkStatus; ++ ++ public int sectionX = Integer.MIN_VALUE; ++ public int sectionY = Integer.MIN_VALUE; ++ public int sectionZ = Integer.MIN_VALUE; ++ // Paper end ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = ImmutableList.of(); +@@ -2372,11 +2422,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return InteractionResult.PASS; + } + +- public boolean canCollideWith(Entity other) { ++ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override + return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); + } + +- public boolean canBeCollidedWith() { ++ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override + return false; + } + +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index 1a3be6f0570c7c746eafa36544debe90d7629432..c0817ef8927f00e2fd3fbf3289f8041fcb494049 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + + public interface EntityGetter { ++ ++ // Paper start ++ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); ++ ++ void getEntities(Entity except, AABB box, Predicate predicate, List into); ++ ++ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); ++ ++ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, ++ Predicate predicate); ++ // Paper end ++ + List getEntities(@Nullable Entity except, AABB box, Predicate predicate); + + List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 4b7ed29a7a38cf7f8fb488c9c34471fafcdf2f25..a04517137ab9deff215b6f9b9ee405500af0e393 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -293,6 +293,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); + this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); + this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray ++ this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper + } + + // Paper start +@@ -968,26 +969,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { + this.getProfiler().incrementCounter("getEntities"); + List list = Lists.newArrayList(); +- +- this.getEntities().get(box, (entity1) -> { +- if (entity1 != except && predicate.test(entity1)) { +- list.add(entity1); +- } +- +- if (entity1 instanceof EnderDragon) { +- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); +- int i = aentitycomplexpart.length; +- +- for (int j = 0; j < i; ++j) { +- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; +- +- if (entity1 != except && predicate.test(entitycomplexpart)) { +- list.add(entitycomplexpart); +- } +- } +- } +- +- }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper ++ this.entitySliceManager.getEntities(except, box, list, predicate); // Paper - optimise this call + return list; + } + +@@ -996,27 +978,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.getProfiler().incrementCounter("getEntities"); + List list = Lists.newArrayList(); + +- this.getEntities().get(filter, box, (entity) -> { +- if (predicate.test(entity)) { +- list.add(entity); +- } +- +- if (entity instanceof EnderDragon) { +- EnderDragon entityenderdragon = (EnderDragon) entity; +- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities(); +- int i = aentitycomplexpart.length; +- +- for (int j = 0; j < i; ++j) { +- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; +- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error +- +- if (t0 != null && predicate.test(t0)) { +- list.add(t0); +- } +- } ++ // Paper start - optimise this call ++ if (filter instanceof net.minecraft.world.entity.EntityType) { ++ this.entitySliceManager.getEntities((net.minecraft.world.entity.EntityType)filter, box, list, predicate); ++ } else { ++ Predicate test = (obj) -> { ++ return filter.tryCast(obj) != null; ++ }; ++ predicate = predicate == null ? test : test.and((Predicate)predicate); ++ Class base; ++ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { ++ this.entitySliceManager.getEntities((Entity) null, box, (List)list, (Predicate)predicate); ++ } else { ++ this.entitySliceManager.getEntities(base, null, box, (List)list, (Predicate)predicate); // Paper - optimise this call + } +- +- }); ++ } ++ // Paper end - optimise this call + return list; + } + +@@ -1343,4 +1320,46 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public long nextSubTickCount() { + return (long) (this.subTickCount++); + } ++ ++ // Paper start ++ protected final io.papermc.paper.world.EntitySliceManager entitySliceManager; ++ ++ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { ++ io.papermc.paper.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ); ++ if (slices == null) { ++ return new org.bukkit.entity.Entity[0]; ++ } ++ return slices.getChunkEntities(); ++ } ++ ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ this.entitySliceManager.getEntities(except, box, ret, predicate); ++ return ret; ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { ++ this.entitySliceManager.getEntities(except, box, into, predicate); ++ } ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { ++ this.entitySliceManager.getHardCollidingEntities(except, box, into, predicate); ++ } ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, ++ Predicate predicate) { ++ this.entitySliceManager.getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); ++ } ++ ++ @Override ++ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ this.entitySliceManager.getEntities(entityClass, null, box, ret, predicate); ++ return ret; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index a1a52669c19af22e3b5267d43584cb00d1646453..f635b610e68d129aa0ae60c54b83da6943946436 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -49,8 +49,10 @@ public class PersistentEntitySectionManager implements A + private final Long2ObjectMap chunkLoadStatuses = new Long2ObjectOpenHashMap(); + private final LongSet chunksToUnload = new LongOpenHashSet(); + private final Queue> loadingInbox = Queues.newConcurrentLinkedQueue(); ++ public final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper + +- public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess) { ++ public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess, io.papermc.paper.world.EntitySliceManager entitySliceManager) { // Paper ++ this.entitySliceManager = entitySliceManager; // Paper + this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility); + this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN); + this.chunkLoadStatuses.defaultReturnValue(PersistentEntitySectionManager.ChunkLoadStatus.FRESH); +@@ -124,6 +126,7 @@ public class PersistentEntitySectionManager implements A + EntitySection entitysection = this.sectionStorage.getOrCreateSection(i); + + entitysection.add(entity); ++ this.entitySliceManager.addEntity((Entity)entity); // Paper + entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection)); + if (!existing) { + this.callbacks.onCreated(entity); +@@ -181,6 +184,7 @@ public class PersistentEntitySectionManager implements A + io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper + Visibility visibility = Visibility.fromFullChunkStatus(levelType); + ++ this.entitySliceManager.chunkStatusChange(chunkPos.x, chunkPos.z, levelType); // Paper + this.updateChunkStatus(chunkPos, visibility); + } + +@@ -467,6 +471,7 @@ public class PersistentEntitySectionManager implements A + long i = SectionPos.asLong(blockposition); + + if (i != this.currentSectionKey) { ++ PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper + Visibility visibility = this.currentSection.getStatus(); + + if (!this.currentSection.remove(this.entity)) { +@@ -524,6 +529,7 @@ public class PersistentEntitySectionManager implements A + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } ++ PersistentEntitySectionManager.this.entitySliceManager.removeEntity((Entity)this.entity); // Paper + + Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus()); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index d9c2e7e18e1ede37d92cecb8ddb32dae1472bd1c..2224b9cfbc3be59037ef49ce278989ea3a710bb5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -134,9 +134,7 @@ public class CraftChunk implements Chunk { + long pair = ChunkPos.asLong(x, z); + + if (entityManager.areEntitiesLoaded(pair)) { +- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() +- .map(net.minecraft.world.entity.Entity::getBukkitEntity) +- .filter(Objects::nonNull).toArray(Entity[]::new); ++ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this + } + + entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading +@@ -172,9 +170,7 @@ public class CraftChunk implements Chunk { + } + } + +- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() +- .map(net.minecraft.world.entity.Entity::getBukkitEntity) +- .filter(Objects::nonNull).toArray(Entity[]::new); ++ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index 3bedc22c253c3632b5624c05e78ed3671e5d30ce..fbd82b6be6604bf854e01ed5718e4e072f42b265 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -254,4 +254,20 @@ public class DummyGeneratorAccess implements WorldGenLevel { + public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) { + return false; // SPIGOT-6515 + } ++ ++ // Paper start ++ @Override ++ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { ++ return java.util.Collections.emptyList(); ++ } ++ ++ @Override ++ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} ++ ++ @Override ++ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} ++ // Paper end + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 0508f43ad396679d3372ae4caf029086a1524109..b1ed97618d08d7691d24f89e9e9b0ed0f2bddd09 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -211,7 +211,13 @@ public class ActivationRange + ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); + // Paper end + +- world.getEntities().get(maxBB, ActivationRange::activateEntity); ++ // Paper start ++ java.util.List entities = world.getEntities((Entity)null, maxBB, null); ++ for (int i = 0; i < entities.size(); i++) { ++ Entity entity = entities.get(i); ++ ActivationRange.activateEntity(entity); ++ } ++ // Paper end + } + MinecraftTimings.entityActivationCheckTimer.stopTiming(); + } diff --git a/patches/server/0735-Optimise-chunk-tick-iteration.patch b/patches/server/0735-Optimise-chunk-tick-iteration.patch new file mode 100644 index 0000000000..dff38c5fda --- /dev/null +++ b/patches/server/0735-Optimise-chunk-tick-iteration.patch @@ -0,0 +1,205 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 7 May 2020 05:48:54 -0700 +Subject: [PATCH] Optimise chunk tick iteration + +Use a dedicated list of entity ticking chunks to reduce the cost + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index a2b5f6457b08e4e02544dc71fbf383b5a67a2d69..538f21e6bc29c0307441fe4899dc7f600d2d1a04 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -84,6 +84,11 @@ public class ChunkHolder { + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + // Paper end - optimise anyPlayerCloseEnoughForSpawning ++ // Paper start - optimise chunk tick iteration ++ if (this.needsBroadcastChanges()) { ++ this.chunkMap.needsChangeBroadcasting.add(this); ++ } ++ // Paper end - optimise chunk tick iteration + } + + public void onChunkRemove() { +@@ -91,6 +96,11 @@ public class ChunkHolder { + this.playersInMobSpawnRange = null; + this.playersInChunkTickRange = null; + // Paper end - optimise anyPlayerCloseEnoughForSpawning ++ // Paper start - optimise chunk tick iteration ++ if (this.needsBroadcastChanges()) { ++ this.chunkMap.needsChangeBroadcasting.remove(this); ++ } ++ // Paper end - optimise chunk tick iteration + } + // Paper end + +@@ -258,7 +268,7 @@ public class ChunkHolder { + + if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 + if (this.changedBlocksPerSection[i] == null) { +- this.hasChangedSections = true; ++ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration + this.changedBlocksPerSection[i] = new ShortOpenHashSet(); + } + +@@ -281,6 +291,7 @@ public class ChunkHolder { + int k = this.lightEngine.getMaxLightSection(); + + if (y >= j && y <= k) { ++ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration + int l = y - j; + + if (lightType == LightLayer.SKY) { +@@ -295,8 +306,19 @@ public class ChunkHolder { + } + } + ++ // Paper start - optimise chunk tick iteration ++ public final boolean needsBroadcastChanges() { ++ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); ++ } ++ ++ private void addToBroadcastMap() { ++ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update"); ++ this.chunkMap.needsChangeBroadcasting.add(this); ++ } ++ // Paper end - optimise chunk tick iteration ++ + public void broadcastChanges(LevelChunk chunk) { +- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { ++ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call + Level world = chunk.getLevel(); + int i = 0; + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index b34c90497a5492c289839ba74df9f2f27e29370e..e811c7d617b8c9cc684bc0a58a98d5ecfe11db02 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -162,6 +162,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + private final Queue unloadQueue; + int viewDistance; + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper ++ public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); + + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 2390fcbc1b21653b1753a58da33f936cec43d0cb..7b1279256ed7963ba4e225b15592816087ab16b4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -47,6 +47,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.level.storage.LevelStorageSource; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper + + public class ServerChunkCache extends ChunkSource { + +@@ -810,34 +811,42 @@ public class ServerChunkCache extends ChunkSource { + + this.lastSpawnState = spawnercreature_d; + gameprofilerfiller.popPush("filteringLoadedChunks"); +- List list = Lists.newArrayListWithCapacity(l); +- Iterator iterator = this.chunkMap.getChunks().iterator(); ++ // Paper - moved down + this.level.timings.chunkTicks.startTiming(); // Paper + +- while (iterator.hasNext()) { +- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); +- LevelChunk chunk = playerchunk.getTickingChunk(); +- +- if (chunk != null) { +- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); +- } +- } ++ // Paper - moved down + + gameprofilerfiller.popPush("spawnAndTick"); + boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + +- Collections.shuffle(list); ++ // Paper - only shuffle if per-player mob spawning is disabled + // Paper - moved natural spawn event up +- Iterator iterator1 = list.iterator(); ++ // Paper start - optimise chunk tick iteration ++ Iterator iterator1; ++ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ iterator1 = this.entityTickingChunks.iterator(); ++ } else { ++ iterator1 = this.entityTickingChunks.unsafeIterator(); ++ List shuffled = Lists.newArrayListWithCapacity(this.entityTickingChunks.size()); ++ while (iterator1.hasNext()) { ++ shuffled.add(iterator1.next()); ++ } ++ Collections.shuffle(shuffled); ++ iterator1 = shuffled.iterator(); ++ } + ++ try { + while (iterator1.hasNext()) { +- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); +- LevelChunk chunk1 = chunkproviderserver_a.chunk; ++ LevelChunk chunk1 = iterator1.next(); ++ ChunkHolder holder = chunk1.playerChunk; ++ if (holder != null) { ++ // Paper - move down ++ // Paper end - optimise chunk tick iteration + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning ++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning + chunk1.incrementInhabitedTime(j); +- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning ++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +@@ -845,7 +854,16 @@ public class ServerChunkCache extends ChunkSource { + this.level.tickChunk(chunk1, k); + } + } ++ // Paper start - optimise chunk tick iteration ++ } ++ } ++ ++ } finally { ++ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { ++ safeIterator.finishedIterating(); ++ } + } ++ // Paper end - optimise chunk tick iteration + this.level.timings.chunkTicks.stopTiming(); // Paper + gameprofilerfiller.popPush("customSpawners"); + if (flag2) { +@@ -853,15 +871,24 @@ public class ServerChunkCache extends ChunkSource { + this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); + } // Paper - timings + } +- +- gameprofilerfiller.popPush("broadcast"); +- list.forEach((chunkproviderserver_a1) -> { +- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing +- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); +- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing +- }); + gameprofilerfiller.pop(); ++ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded ++ gameprofilerfiller.popPush("broadcast"); ++ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing ++ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { ++ ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); ++ this.chunkMap.needsChangeBroadcasting.clear(); ++ for (ChunkHolder holder : copy) { ++ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded ++ if (holder.needsBroadcastChanges()) { ++ // I DON'T want to KNOW what DUMB plugins might be doing. ++ this.chunkMap.needsChangeBroadcasting.add(holder); ++ } ++ } ++ } ++ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing + gameprofilerfiller.pop(); ++ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded + this.chunkMap.tick(); + } + } diff --git a/patches/server/0735-Rewrite-entity-bounding-box-lookup-calls.patch b/patches/server/0735-Rewrite-entity-bounding-box-lookup-calls.patch deleted file mode 100644 index 0318fbaff3..0000000000 --- a/patches/server/0735-Rewrite-entity-bounding-box-lookup-calls.patch +++ /dev/null @@ -1,1300 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 17 Jun 2021 19:55:02 -0700 -Subject: [PATCH] Rewrite entity bounding box lookup calls - -For whatever reason, Mojang thought it was OK to make this system -scale logn relative to the number of entity sections loaded. -On top of that, they do a hashtable lookup per section - before -this was just a basic array access. - -This patch brings back entity slices for lookup only. - -diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -new file mode 100644 -index 0000000000000000000000000000000000000000..47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java -@@ -0,0 +1,500 @@ -+package io.papermc.paper.world; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.phys.AABB; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Predicate; -+ -+public final class ChunkEntitySlices { -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int chunkX; -+ protected final int chunkZ; -+ protected final ServerLevel world; -+ -+ protected final EntityCollectionBySection allEntities; -+ protected final EntityCollectionBySection hardCollidingEntities; -+ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; -+ protected final EntityList entities = new EntityList(); -+ -+ public ChunkHolder.FullChunkStatus status; -+ -+ // TODO implement container search optimisations -+ -+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus status, -+ final int minSection, final int maxSection) { // inclusive, inclusive -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ -+ this.allEntities = new EntityCollectionBySection(this); -+ this.hardCollidingEntities = new EntityCollectionBySection(this); -+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); -+ -+ this.status = status; -+ } -+ -+ // Paper start - optimise CraftChunk#getEntities -+ public org.bukkit.entity.Entity[] getChunkEntities() { -+ List ret = new java.util.ArrayList<>(); -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ if (entity == null) { -+ continue; -+ } -+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); -+ } -+ // Paper end - optimise CraftChunk#getEntities -+ -+ public boolean isEmpty() { -+ return this.entities.size() == 0; -+ } -+ -+ private void updateTicketLevels() { -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ entity.chunkStatus = this.status; -+ } -+ } -+ -+ public synchronized void updateStatus(final ChunkHolder.FullChunkStatus status) { -+ this.status = status; -+ this.updateTicketLevels(); -+ } -+ -+ public synchronized void addEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.add(entity)) { -+ return; -+ } -+ entity.chunkStatus = this.status; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.addEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.addEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ public synchronized void removeEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.remove(entity)) { -+ return; -+ } -+ entity.chunkStatus = ChunkHolder.FullChunkStatus.INACCESSIBLE; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.removeEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.removeEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().removeEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.hardCollidingEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ -+ protected EntityCollectionBySection initClass(final Class clazz) { -+ final EntityCollectionBySection ret = new EntityCollectionBySection(this); -+ -+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { -+ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; -+ if (sectionEntities == null) { -+ continue; -+ } -+ -+ final Entity[] storage = sectionEntities.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (clazz.isInstance(entity)) { -+ ret.addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); -+ if (collection != null) { -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } else { -+ synchronized (this) { -+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); -+ } -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } -+ } -+ -+ public synchronized void updateEntity(final Entity entity) { -+ /*// TODO -+ if (prev aabb != entity.getBoundingBox()) { -+ this.entityMap.delete(entity, prev aabb); -+ this.entityMap.insert(entity, prev aabb = entity.getBoundingBox()); -+ }*/ -+ } -+ -+ protected static final class BasicEntityList { -+ -+ protected static final Entity[] EMPTY = new Entity[0]; -+ protected static final int DEFAULT_CAPACITY = 4; -+ -+ protected E[] storage; -+ protected int size; -+ -+ public BasicEntityList() { -+ this(0); -+ } -+ -+ public BasicEntityList(final int cap) { -+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); -+ } -+ -+ public boolean isEmpty() { -+ return this.size == 0; -+ } -+ -+ public int size() { -+ return this.size; -+ } -+ -+ private void resize() { -+ if (this.storage == EMPTY) { -+ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; -+ } else { -+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); -+ } -+ } -+ -+ public void add(final E entity) { -+ final int idx = this.size++; -+ if (idx >= this.storage.length) { -+ this.resize(); -+ this.storage[idx] = entity; -+ } else { -+ this.storage[idx] = entity; -+ } -+ } -+ -+ public int indexOf(final E entity) { -+ final E[] storage = this.storage; -+ -+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { -+ if (storage[i] == entity) { -+ return i; -+ } -+ } -+ -+ return -1; -+ } -+ -+ public boolean remove(final E entity) { -+ final int idx = this.indexOf(entity); -+ if (idx == -1) { -+ return false; -+ } -+ -+ final int size = --this.size; -+ final E[] storage = this.storage; -+ if (idx != size) { -+ System.arraycopy(storage, idx + 1, storage, idx, size - idx); -+ } -+ -+ storage[size] = null; -+ -+ return true; -+ } -+ -+ public boolean has(final E entity) { -+ return this.indexOf(entity) != -1; -+ } -+ } -+ -+ protected static final class EntityCollectionBySection { -+ -+ protected final ChunkEntitySlices manager; -+ protected final long[] nonEmptyBitset; -+ protected final BasicEntityList[] entitiesBySection; -+ protected int count; -+ -+ public EntityCollectionBySection(final ChunkEntitySlices manager) { -+ this.manager = manager; -+ -+ final int sectionCount = manager.maxSection - manager.minSection + 1; -+ -+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE -+ this.entitiesBySection = new BasicEntityList[sectionCount]; -+ } -+ -+ public void addEntity(final Entity entity, final int sectionIndex) { -+ BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list != null && list.has(entity)) { -+ return; -+ } -+ -+ if (list == null) { -+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); -+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ -+ list.add(entity); -+ ++this.count; -+ } -+ -+ public void removeEntity(final Entity entity, final int sectionIndex) { -+ final BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list == null || !list.remove(entity)) { -+ return; -+ } -+ -+ --this.count; -+ -+ if (list.isEmpty()) { -+ this.entitiesBySection[sectionIndex] = null; -+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(entity)) { -+ continue; -+ } -+ -+ into.add(entity); -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test((T)entity)) { -+ continue; -+ } -+ -+ into.add((T)entity); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/world/EntitySliceManager.java b/src/main/java/io/papermc/paper/world/EntitySliceManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3ba094e640d7fe7803e2bbdab8ff3beb6f50e8a0 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/world/EntitySliceManager.java -@@ -0,0 +1,391 @@ -+package io.papermc.paper.world; -+ -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.concurrent.locks.StampedLock; -+import java.util.function.Predicate; -+ -+public final class EntitySliceManager { -+ -+ protected static final int REGION_SHIFT = 5; -+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; -+ protected static final int REGION_SIZE = 1 << REGION_SHIFT; -+ -+ public final ServerLevel world; -+ -+ private final StampedLock stateLock = new StampedLock(); -+ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(64, 0.7f); -+ -+ private final int minSection; // inclusive -+ private final int maxSection; // inclusive -+ -+ protected final Long2ObjectOpenHashMap statusMap = new Long2ObjectOpenHashMap<>(); -+ { -+ this.statusMap.defaultReturnValue(ChunkHolder.FullChunkStatus.INACCESSIBLE); -+ } -+ -+ public EntitySliceManager(final ServerLevel world) { -+ this.world = world; -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ } -+ -+ public void chunkStatusChange(final int x, final int z, final ChunkHolder.FullChunkStatus newStatus) { -+ if (newStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) { -+ this.statusMap.remove(CoordinateUtils.getChunkKey(x, z)); -+ } else { -+ this.statusMap.put(CoordinateUtils.getChunkKey(x, z), newStatus); -+ final ChunkEntitySlices slices = this.getChunk(x, z); -+ if (slices != null) { -+ slices.updateStatus(newStatus); -+ } -+ } -+ } -+ -+ public synchronized void addEntity(final Entity entity) { -+ final BlockPos pos = entity.blockPosition(); -+ final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); -+ final int sectionZ = pos.getZ() >> 4; -+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); -+ slices.addEntity(entity, sectionY); -+ -+ entity.sectionX = sectionX; -+ entity.sectionY = sectionY; -+ entity.sectionZ = sectionZ; -+ } -+ -+ public synchronized void removeEntity(final Entity entity) { -+ final ChunkEntitySlices slices = this.getChunk(entity.sectionX, entity.sectionZ); -+ slices.removeEntity(entity, entity.sectionY); -+ if (slices.isEmpty()) { -+ this.removeChunk(entity.sectionX, entity.sectionZ); -+ } -+ } -+ -+ public void moveEntity(final Entity entity) { -+ final BlockPos newPos = entity.blockPosition(); -+ final int newSectionX = newPos.getX() >> 4; -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); -+ final int newSectionZ = newPos.getZ() >> 4; -+ -+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { -+ return; -+ } -+ -+ synchronized (this) { -+ // are we changing chunks? -+ if (newSectionX != entity.sectionX || newSectionZ != entity.sectionZ) { -+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); -+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); -+ synchronized (old) { -+ old.removeEntity(entity, entity.sectionY); -+ if (old.isEmpty()) { -+ this.removeChunk(entity.sectionX, entity.sectionZ); -+ } -+ } -+ -+ synchronized (slices) { -+ slices.addEntity(entity, newSectionY); -+ -+ entity.sectionX = newSectionX; -+ entity.sectionY = newSectionY; -+ entity.sectionZ = newSectionZ; -+ } -+ } else { -+ final ChunkEntitySlices slices = this.getChunk(newSectionX, newSectionZ); -+ // same chunk -+ synchronized (slices) { -+ slices.removeEntity(entity, entity.sectionY); -+ slices.addEntity(entity, newSectionY); -+ } -+ entity.sectionY = newSectionY; -+ } -+ } -+ -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getHardCollidingEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(clazz, except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ if (region == null) { -+ return null; -+ } -+ -+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); -+ } -+ -+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ ChunkEntitySlices ret; -+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { -+ ret = new ChunkEntitySlices(this.world, chunkX, chunkZ, this.statusMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)), -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); -+ -+ this.addChunk(chunkX, chunkZ, ret); -+ -+ return ret; -+ } -+ -+ return ret; -+ } -+ -+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { -+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); -+ final long attempt = this.stateLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final ChunkSlicesRegion ret = this.regions.get(key); -+ -+ if (this.stateLock.validate(attempt)) { -+ return ret; -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.stateLock.readLock(); -+ try { -+ return this.regions.get(key); -+ } finally { -+ this.stateLock.tryUnlockRead(); -+ } -+ } -+ -+ public synchronized void removeChunk(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ final ChunkSlicesRegion region = this.regions.get(key); -+ final int remaining = region.remove(relIndex); -+ -+ if (remaining == 0) { -+ this.stateLock.writeLock(); -+ try { -+ this.regions.remove(key); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ ChunkSlicesRegion region = this.regions.get(key); -+ if (region != null) { -+ region.add(relIndex, slices); -+ } else { -+ region = new ChunkSlicesRegion(); -+ region.add(relIndex, slices); -+ this.stateLock.writeLock(); -+ try { -+ this.regions.put(key, region); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public static final class ChunkSlicesRegion { -+ -+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; -+ protected int sliceCount; -+ -+ public ChunkEntitySlices get(final int index) { -+ return this.slices[index]; -+ } -+ -+ public int remove(final int index) { -+ final ChunkEntitySlices slices = this.slices[index]; -+ if (slices == null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = null; -+ -+ return --this.sliceCount; -+ } -+ -+ public void add(final int index, final ChunkEntitySlices slices) { -+ final ChunkEntitySlices curr = this.slices[index]; -+ if (curr != null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = slices; -+ -+ ++this.sliceCount; -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 39d96b4e3dce6d67b568b7b00456de164f6a7241..29b80d074600fa7e20b05d1fe70ac12969b954a4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -454,7 +454,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - DataFixer datafixer = minecraftserver.getFixerUpper(); - EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); - -- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); -+ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper - StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); - int j = this.spigotConfig.viewDistance; // Spigot - int k = this.spigotConfig.simulationDistance; // Spigot -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 32d6e4b194c3c4eca7009059f8d185896b5ae556..51d3150e732f95be13f5f54d994dab1fa89ed3f2 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -498,4 +498,21 @@ public class WorldGenRegion implements WorldGenLevel { - public long nextSubTickCount() { - return this.subTickCount.getAndIncrement(); - } -+ -+ // Paper start -+ // No-op, this class doesn't provide entity access -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 6f3147713b5bec3b2771e1ec52917fd4aee681bc..9567822f314cd3978ed63bb867e57b610d76228e 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -490,6 +490,56 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - // Paper end - make end portalling safe - -+ // Paper start -+ /** -+ * Overriding this field will cause memory leaks. -+ */ -+ private final boolean hardCollides; -+ -+ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); -+ { -+ /* // Goodbye, broken on reobf... -+ Boolean hardCollides = cachedOverrides.get(this.getClass()); -+ if (hardCollides == null) { -+ try { -+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); -+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); -+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) -+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { -+ hardCollides = Boolean.TRUE; -+ } else { -+ hardCollides = Boolean.FALSE; -+ } -+ cachedOverrides.put(this.getClass(), hardCollides); -+ } -+ catch (ThreadDeath thr) { throw thr; } -+ catch (Throwable thr) { -+ // shouldn't happen, just explode -+ throw new RuntimeException(thr); -+ } -+ } */ -+ this.hardCollides = this instanceof Boat -+ || this instanceof net.minecraft.world.entity.monster.Shulker -+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart -+ || this.shouldHardCollide(); -+ } -+ -+ // plugins can override -+ protected boolean shouldHardCollide() { -+ return false; -+ } -+ -+ public final boolean hardCollides() { -+ return this.hardCollides; -+ } -+ -+ public net.minecraft.server.level.ChunkHolder.FullChunkStatus chunkStatus; -+ -+ public int sectionX = Integer.MIN_VALUE; -+ public int sectionY = Integer.MIN_VALUE; -+ public int sectionZ = Integer.MIN_VALUE; -+ // Paper end -+ - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); -@@ -2358,11 +2408,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return InteractionResult.PASS; - } - -- public boolean canCollideWith(Entity other) { -+ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override - return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); - } - -- public boolean canBeCollidedWith() { -+ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override - return false; - } - -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 1a3be6f0570c7c746eafa36544debe90d7629432..c0817ef8927f00e2fd3fbf3289f8041fcb494049 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes; - import net.minecraft.world.phys.shapes.VoxelShape; - - public interface EntityGetter { -+ -+ // Paper start -+ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); -+ -+ void getEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate); -+ // Paper end -+ - List getEntities(@Nullable Entity except, AABB box, Predicate predicate); - - List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 4b7ed29a7a38cf7f8fb488c9c34471fafcdf2f25..a04517137ab9deff215b6f9b9ee405500af0e393 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -293,6 +293,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); - this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray -+ this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper - } - - // Paper start -@@ -968,26 +969,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); -- -- this.getEntities().get(box, (entity1) -> { -- if (entity1 != except && predicate.test(entity1)) { -- list.add(entity1); -- } -- -- if (entity1 instanceof EnderDragon) { -- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- -- if (entity1 != except && predicate.test(entitycomplexpart)) { -- list.add(entitycomplexpart); -- } -- } -- } -- -- }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper -+ this.entitySliceManager.getEntities(except, box, list, predicate); // Paper - optimise this call - return list; - } - -@@ -996,27 +978,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); - -- this.getEntities().get(filter, box, (entity) -> { -- if (predicate.test(entity)) { -- list.add(entity); -- } -- -- if (entity instanceof EnderDragon) { -- EnderDragon entityenderdragon = (EnderDragon) entity; -- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error -- -- if (t0 != null && predicate.test(t0)) { -- list.add(t0); -- } -- } -+ // Paper start - optimise this call -+ if (filter instanceof net.minecraft.world.entity.EntityType) { -+ this.entitySliceManager.getEntities((net.minecraft.world.entity.EntityType)filter, box, list, predicate); -+ } else { -+ Predicate test = (obj) -> { -+ return filter.tryCast(obj) != null; -+ }; -+ predicate = predicate == null ? test : test.and((Predicate)predicate); -+ Class base; -+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { -+ this.entitySliceManager.getEntities((Entity) null, box, (List)list, (Predicate)predicate); -+ } else { -+ this.entitySliceManager.getEntities(base, null, box, (List)list, (Predicate)predicate); // Paper - optimise this call - } -- -- }); -+ } -+ // Paper end - optimise this call - return list; - } - -@@ -1343,4 +1320,46 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public long nextSubTickCount() { - return (long) (this.subTickCount++); - } -+ -+ // Paper start -+ protected final io.papermc.paper.world.EntitySliceManager entitySliceManager; -+ -+ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { -+ io.papermc.paper.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ); -+ if (slices == null) { -+ return new org.bukkit.entity.Entity[0]; -+ } -+ return slices.getChunkEntities(); -+ } -+ -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ this.entitySliceManager.getEntities(except, box, ret, predicate); -+ return ret; -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { -+ this.entitySliceManager.getEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { -+ this.entitySliceManager.getHardCollidingEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate) { -+ this.entitySliceManager.getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); -+ } -+ -+ @Override -+ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ this.entitySliceManager.getEntities(entityClass, null, box, ret, predicate); -+ return ret; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index a1a52669c19af22e3b5267d43584cb00d1646453..f635b610e68d129aa0ae60c54b83da6943946436 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -49,8 +49,10 @@ public class PersistentEntitySectionManager implements A - private final Long2ObjectMap chunkLoadStatuses = new Long2ObjectOpenHashMap(); - private final LongSet chunksToUnload = new LongOpenHashSet(); - private final Queue> loadingInbox = Queues.newConcurrentLinkedQueue(); -+ public final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper - -- public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess) { -+ public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess, io.papermc.paper.world.EntitySliceManager entitySliceManager) { // Paper -+ this.entitySliceManager = entitySliceManager; // Paper - this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility); - this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN); - this.chunkLoadStatuses.defaultReturnValue(PersistentEntitySectionManager.ChunkLoadStatus.FRESH); -@@ -124,6 +126,7 @@ public class PersistentEntitySectionManager implements A - EntitySection entitysection = this.sectionStorage.getOrCreateSection(i); - - entitysection.add(entity); -+ this.entitySliceManager.addEntity((Entity)entity); // Paper - entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection)); - if (!existing) { - this.callbacks.onCreated(entity); -@@ -181,6 +184,7 @@ public class PersistentEntitySectionManager implements A - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper - Visibility visibility = Visibility.fromFullChunkStatus(levelType); - -+ this.entitySliceManager.chunkStatusChange(chunkPos.x, chunkPos.z, levelType); // Paper - this.updateChunkStatus(chunkPos, visibility); - } - -@@ -467,6 +471,7 @@ public class PersistentEntitySectionManager implements A - long i = SectionPos.asLong(blockposition); - - if (i != this.currentSectionKey) { -+ PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper - Visibility visibility = this.currentSection.getStatus(); - - if (!this.currentSection.remove(this.entity)) { -@@ -524,6 +529,7 @@ public class PersistentEntitySectionManager implements A - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); - } -+ PersistentEntitySectionManager.this.entitySliceManager.removeEntity((Entity)this.entity); // Paper - - Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus()); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 518dfbb7dbd4221937636cf46d27109de6f431a4..e86b3ee5c8225d9f789cf426cc1418fde0fa12f0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -134,9 +134,7 @@ public class CraftChunk implements Chunk { - long pair = ChunkPos.asLong(x, z); - - if (entityManager.areEntitiesLoaded(pair)) { -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -+ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this - } - - entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading -@@ -172,9 +170,7 @@ public class CraftChunk implements Chunk { - } - } - -- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream() -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(Objects::nonNull).toArray(Entity[]::new); -+ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index 3bedc22c253c3632b5624c05e78ed3671e5d30ce..fbd82b6be6604bf854e01ed5718e4e072f42b265 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -254,4 +254,20 @@ public class DummyGeneratorAccess implements WorldGenLevel { - public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) { - return false; // SPIGOT-6515 - } -+ -+ // Paper start -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return java.util.Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Paper end - } -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 0508f43ad396679d3372ae4caf029086a1524109..b1ed97618d08d7691d24f89e9e9b0ed0f2bddd09 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -211,7 +211,13 @@ public class ActivationRange - ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange ); - // Paper end - -- world.getEntities().get(maxBB, ActivationRange::activateEntity); -+ // Paper start -+ java.util.List entities = world.getEntities((Entity)null, maxBB, null); -+ for (int i = 0; i < entities.size(); i++) { -+ Entity entity = entities.get(i); -+ ActivationRange.activateEntity(entity); -+ } -+ // Paper end - } - MinecraftTimings.entityActivationCheckTimer.stopTiming(); - } diff --git a/patches/server/0736-Execute-chunk-tasks-mid-tick.patch b/patches/server/0736-Execute-chunk-tasks-mid-tick.patch new file mode 100644 index 0000000000..c2e8035538 --- /dev/null +++ b/patches/server/0736-Execute-chunk-tasks-mid-tick.patch @@ -0,0 +1,179 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 04:20:44 -0700 +Subject: [PATCH] Execute chunk tasks mid-tick + +This will help the server load chunks if tick times are high. + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 23e564b05ba438924180c91f9b19a60731eedd1b..5ec241d49ff5e3a161a39006f05823a5de847c5e 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -46,6 +46,8 @@ public final class MinecraftTimings { + public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + ++ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); ++ + private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); + + private MinecraftTimings() {} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 98fe4165d291b47a39ce741884353c87dd0a4789..99073ea2757cd1c15b098d7cfaf8681702f04a19 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1304,6 +1304,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= MAX_CHUNK_EXEC_TIME) { ++ if (!moreTasks) { ++ lastMidTickExecuteFailure = currTime; ++ } ++ ++ // note: negative values reduce the time ++ long overuse = diff - MAX_CHUNK_EXEC_TIME; ++ if (overuse >= (10L * 1000L * 1000L)) { // 10ms ++ // make sure something like a GC or dumb plugin doesn't screw us over... ++ overuse = 10L * 1000L * 1000L; // 10ms ++ } ++ ++ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; ++ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); ++ ++ lastMidTickExecute = currTime + extraSleep; ++ return; ++ } ++ } ++ } finally { ++ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); ++ } ++ } ++ // Paper end - execute chunk tasks mid tick + } +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 7b1279256ed7963ba4e225b15592816087ab16b4..1510e1fcfbbb2ccbaf57dd274bed05afc85eeeb0 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -835,6 +835,7 @@ public class ServerChunkCache extends ChunkSource { + iterator1 = shuffled.iterator(); + } + ++ int chunksTicked = 0; // Paper + try { + while (iterator1.hasNext()) { + LevelChunk chunk1 = iterator1.next(); +@@ -852,6 +853,7 @@ public class ServerChunkCache extends ChunkSource { + + if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { + this.level.tickChunk(chunk1, k); ++ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + } + } + // Paper start - optimise chunk tick iteration +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 29b80d074600fa7e20b05d1fe70ac12969b954a4..7324f2ad437a15c42f84ba2deeb58861c0552209 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -212,6 +212,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + private final StructureManager structureManager; + private final StructureCheck structureCheck; + private final boolean tickTime; ++ public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess convertable; +@@ -991,6 +992,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (fluid1.is(fluid)) { + fluid1.tick(this, pos); + } ++ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick + + } + +@@ -1000,6 +1002,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + if (iblockdata.is(block)) { + iblockdata.tick(this, pos, this.random); + } ++ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick + + } + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index a04517137ab9deff215b6f9b9ee405500af0e393..90b3ca30a5ab381acde66393eb1fed5be677e5c8 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -800,6 +800,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // Spigot end + } else if (this.shouldTickBlocksAt(tickingblockentity.getPos())) { + tickingblockentity.tick(); ++ // Paper start - execute chunk tasks during tick ++ if ((this.tileTickPosition & 7) == 0) { ++ MinecraftServer.getServer().executeMidTickTasks(); ++ } ++ // Paper end - execute chunk tasks during tick + } + } + this.blockEntityTickers.removeAll(toRemove); +@@ -814,6 +819,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public void guardEntityTick(Consumer tickConsumer, T entity) { + try { + tickConsumer.accept(entity); ++ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick + } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes diff --git a/patches/server/0736-Optimise-chunk-tick-iteration.patch b/patches/server/0736-Optimise-chunk-tick-iteration.patch deleted file mode 100644 index dff38c5fda..0000000000 --- a/patches/server/0736-Optimise-chunk-tick-iteration.patch +++ /dev/null @@ -1,205 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 7 May 2020 05:48:54 -0700 -Subject: [PATCH] Optimise chunk tick iteration - -Use a dedicated list of entity ticking chunks to reduce the cost - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index a2b5f6457b08e4e02544dc71fbf383b5a67a2d69..538f21e6bc29c0307441fe4899dc7f600d2d1a04 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -84,6 +84,11 @@ public class ChunkHolder { - this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); - this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); - // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - - public void onChunkRemove() { -@@ -91,6 +96,11 @@ public class ChunkHolder { - this.playersInMobSpawnRange = null; - this.playersInChunkTickRange = null; - // Paper end - optimise anyPlayerCloseEnoughForSpawning -+ // Paper start - optimise chunk tick iteration -+ if (this.needsBroadcastChanges()) { -+ this.chunkMap.needsChangeBroadcasting.remove(this); -+ } -+ // Paper end - optimise chunk tick iteration - } - // Paper end - -@@ -258,7 +268,7 @@ public class ChunkHolder { - - if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { -- this.hasChangedSections = true; -+ this.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - this.changedBlocksPerSection[i] = new ShortOpenHashSet(); - } - -@@ -281,6 +291,7 @@ public class ChunkHolder { - int k = this.lightEngine.getMaxLightSection(); - - if (y >= j && y <= k) { -+ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration - int l = y - j; - - if (lightType == LightLayer.SKY) { -@@ -295,8 +306,19 @@ public class ChunkHolder { - } - } - -+ // Paper start - optimise chunk tick iteration -+ public final boolean needsBroadcastChanges() { -+ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty(); -+ } -+ -+ private void addToBroadcastMap() { -+ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update"); -+ this.chunkMap.needsChangeBroadcasting.add(this); -+ } -+ // Paper end - optimise chunk tick iteration -+ - public void broadcastChanges(LevelChunk chunk) { -- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) { -+ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call - Level world = chunk.getLevel(); - int i = 0; - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index b34c90497a5492c289839ba74df9f2f27e29370e..e811c7d617b8c9cc684bc0a58a98d5ecfe11db02 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -162,6 +162,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private final Queue unloadQueue; - int viewDistance; - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper -+ public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); - - // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() - public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 2390fcbc1b21653b1753a58da33f936cec43d0cb..7b1279256ed7963ba4e225b15592816087ab16b4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -47,6 +47,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemp - import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelData; - import net.minecraft.world.level.storage.LevelStorageSource; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper - - public class ServerChunkCache extends ChunkSource { - -@@ -810,34 +811,42 @@ public class ServerChunkCache extends ChunkSource { - - this.lastSpawnState = spawnercreature_d; - gameprofilerfiller.popPush("filteringLoadedChunks"); -- List list = Lists.newArrayListWithCapacity(l); -- Iterator iterator = this.chunkMap.getChunks().iterator(); -+ // Paper - moved down - this.level.timings.chunkTicks.startTiming(); // Paper - -- while (iterator.hasNext()) { -- ChunkHolder playerchunk = (ChunkHolder) iterator.next(); -- LevelChunk chunk = playerchunk.getTickingChunk(); -- -- if (chunk != null) { -- list.add(new ServerChunkCache.ChunkAndHolder(chunk, playerchunk)); -- } -- } -+ // Paper - moved down - - gameprofilerfiller.popPush("spawnAndTick"); - boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - -- Collections.shuffle(list); -+ // Paper - only shuffle if per-player mob spawning is disabled - // Paper - moved natural spawn event up -- Iterator iterator1 = list.iterator(); -+ // Paper start - optimise chunk tick iteration -+ Iterator iterator1; -+ if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ iterator1 = this.entityTickingChunks.iterator(); -+ } else { -+ iterator1 = this.entityTickingChunks.unsafeIterator(); -+ List shuffled = Lists.newArrayListWithCapacity(this.entityTickingChunks.size()); -+ while (iterator1.hasNext()) { -+ shuffled.add(iterator1.next()); -+ } -+ Collections.shuffle(shuffled); -+ iterator1 = shuffled.iterator(); -+ } - -+ try { - while (iterator1.hasNext()) { -- ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next(); -- LevelChunk chunk1 = chunkproviderserver_a.chunk; -+ LevelChunk chunk1 = iterator1.next(); -+ ChunkHolder holder = chunk1.playerChunk; -+ if (holder != null) { -+ // Paper - move down -+ // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning - chunk1.incrementInhabitedTime(j); -- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -@@ -845,7 +854,16 @@ public class ServerChunkCache extends ChunkSource { - this.level.tickChunk(chunk1, k); - } - } -+ // Paper start - optimise chunk tick iteration -+ } -+ } -+ -+ } finally { -+ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) { -+ safeIterator.finishedIterating(); -+ } - } -+ // Paper end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - gameprofilerfiller.popPush("customSpawners"); - if (flag2) { -@@ -853,15 +871,24 @@ public class ServerChunkCache extends ChunkSource { - this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); - } // Paper - timings - } -- -- gameprofilerfiller.popPush("broadcast"); -- list.forEach((chunkproviderserver_a1) -> { -- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk); -- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing -- }); - gameprofilerfiller.pop(); -+ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded -+ gameprofilerfiller.popPush("broadcast"); -+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing -+ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -+ ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+ this.chunkMap.needsChangeBroadcasting.clear(); -+ for (ChunkHolder holder : copy) { -+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+ if (holder.needsBroadcastChanges()) { -+ // I DON'T want to KNOW what DUMB plugins might be doing. -+ this.chunkMap.needsChangeBroadcasting.add(holder); -+ } -+ } -+ } -+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - gameprofilerfiller.pop(); -+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded - this.chunkMap.tick(); - } - } diff --git a/patches/server/0737-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/patches/server/0737-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch new file mode 100644 index 0000000000..c448b0249c --- /dev/null +++ b/patches/server/0737-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch @@ -0,0 +1,766 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 2 Feb 2020 02:25:10 -0800 +Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt + +Instead of trying to relocate the chunk, which is seems to never +be the correct choice, so we end up duplicating or swapping chunks, +we instead drop the current regionfile header and recalculate - +hoping that at least then we don't swap chunks, and maybe recover +them all. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 12e3831324abdc1112cbe65cab0f0c75ce77a9ef..be9c15fe141ede1132dbe07ba4bfcf22036ab194 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -68,6 +68,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks; + import org.slf4j.Logger; + + public class ChunkSerializer { ++ // Paper start ++ // TODO: Check on update ++ public static long getLastWorldSaveTime(CompoundTag chunkData) { ++ final int dataVersion = ChunkStorage.getVersion(chunkData); ++ if (dataVersion < 2842) { // Level tag is removed after this version ++ final CompoundTag levelData = chunkData.getCompound("Level"); ++ return levelData.getLong("LastUpdate"); ++ } else { ++ return chunkData.getLong("LastUpdate"); ++ } ++ } ++ // Paper end + + public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -473,7 +485,7 @@ public class ChunkSerializer { + nbttagcompound.putInt("xPos", chunkcoordintpair.x); + nbttagcompound.putInt("yPos", chunk.getMinSection()); + nbttagcompound.putInt("zPos", chunkcoordintpair.z); +- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading ++ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change + nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); + nbttagcompound.putString("Status", chunk.getStatus().getName()); + BlendingData blendingdata = chunk.getBlendingData(); +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 315be30daf0be84efbb4d634dc01e1bf9e6e696e..3010e092bd1337e5a6b99636bbd312d90e470acf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -41,7 +41,7 @@ public class ChunkStorage implements AutoCloseable { + this.fixerUpper = dataFixer; + // Paper start - async chunk io + // remove IO worker +- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker ++ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper + // Paper end - async chunk io + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java +index c8298a597818227de33a4afce4698ec0666cf758..6baceb6ce9021c489be6e79d338a9704285afa26 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java +@@ -9,6 +9,27 @@ import java.util.BitSet; + public class RegionBitmap { + private final BitSet used = new BitSet(); + ++ // Paper start ++ public final void copyFrom(RegionBitmap other) { ++ BitSet thisBitset = this.used; ++ BitSet otherBitset = other.used; ++ ++ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { ++ thisBitset.set(i, otherBitset.get(i)); ++ } ++ } ++ ++ public final boolean tryAllocate(int from, int length) { ++ BitSet bitset = this.used; ++ int firstSet = bitset.nextSetBit(from); ++ if (firstSet > 0 && firstSet < (from + length)) { ++ return false; ++ } ++ bitset.set(from, from + length); ++ return true; ++ } ++ // Paper end ++ + public void force(int start, int size) { + this.used.set(start, start + size); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 861a25a15f1aab20e3245b6d5cdad5d23bdfd6d0..8ff8855c5267379b3a5f5d8baa4a275ffee2c4bf 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 +@@ -51,6 +51,355 @@ public class RegionFile implements AutoCloseable { + public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper + public final Path regionFile; // Paper + ++ // Paper start - try to recover from RegionFile header corruption ++ private static long roundToSectors(long bytes) { ++ long sectors = bytes >>> 12; // 4096 = 2^12 ++ long remainingBytes = bytes & 4095; ++ long sign = -remainingBytes; // sign is 1 if nonzero ++ return sectors + (sign >>> 63); ++ } ++ ++ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); ++ ++ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { ++ try { ++ if (chunkDataLength < 0) { ++ return null; ++ } ++ ++ long offset = sector * 4096L + 4L; // offset for chunk data ++ ++ if ((offset + chunkDataLength) > fileLength) { ++ return null; ++ } ++ ++ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); ++ if (chunkDataLength != this.file.read(chunkData, offset)) { ++ return null; ++ } ++ ++ ((java.nio.Buffer)chunkData).flip(); ++ ++ byte compressionType = chunkData.get(); ++ if (compressionType < 0) { // compressionType & 128 != 0 ++ // oversized chunk ++ return OVERSIZED_COMPOUND; ++ } ++ ++ RegionFileVersion compression = RegionFileVersion.fromId(compressionType); ++ if (compression == null) { ++ return null; ++ } ++ ++ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); ++ ++ return NbtIo.read(new DataInputStream(input)); ++ } catch (Exception ex) { ++ return null; ++ } ++ } ++ ++ private int getLength(long sector) throws IOException { ++ ByteBuffer length = ByteBuffer.allocate(4); ++ if (4 != this.file.read(length, sector * 4096L)) { ++ return -1; ++ } ++ ++ return length.getInt(0); ++ } ++ ++ private void backupRegionFile() { ++ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup"); ++ this.backupRegionFile(backup); ++ } ++ ++ private void backupRegionFile(Path to) { ++ try { ++ this.file.force(true); ++ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath()); ++ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); ++ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath()); ++ } catch (IOException ex) { ++ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex); ++ } ++ } ++ ++ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) { ++ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31); ++ } ++ ++ // note: only call for CHUNK regionfiles ++ boolean recalculateHeader() throws IOException { ++ if (!this.canRecalcHeader) { ++ return false; ++ } ++ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile); ++ if (ourLowerLeftPosition == null) { ++ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header"); ++ return false; ++ } ++ synchronized (this) { ++ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable()); ++ ++ // try to backup file so maybe it could be sent to us for further investigation ++ ++ this.backupRegionFile(); ++ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) ++ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes ++ int[] sectorOffsets = new int[32 * 32]; // in sectors ++ boolean[] hasAikarOversized = new boolean[32 * 32]; ++ ++ long fileLength = this.file.size(); ++ long totalSectors = roundToSectors(fileLength); ++ ++ // search the regionfile from start to finish for the most up-to-date chunk data ++ ++ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip ++ int chunkDataLength = this.getLength(i); ++ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); ++ if (compound == null || compound == OVERSIZED_COMPOUND) { ++ continue; ++ } ++ ++ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound); ++ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) { ++ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")"); ++ continue; ++ } ++ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); ++ ++ CompoundTag otherCompound = compounds[location]; ++ ++ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) { ++ continue; // don't overwrite newer data. ++ } ++ ++ // aikar oversized? ++ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); ++ boolean isAikarOversized = false; ++ if (Files.exists(aikarOversizedFile)) { ++ try { ++ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); ++ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) { ++ // best we got for an id. hope it's good enough ++ isAikarOversized = true; ++ } ++ } catch (Exception ex) { ++ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex); ++ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data ++ } ++ } ++ ++ hasAikarOversized[location] = isAikarOversized; ++ compounds[location] = compound; ++ rawLengths[location] = chunkDataLength + 4; ++ sectorOffsets[location] = (int)i; ++ ++ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); ++ i += chunkSectorLength; ++ --i; // gets incremented next iteration ++ } ++ ++ // forge style oversized data is already handled by the local search, and aikar data we just hope ++ // we get it right as aikar data has no identifiers we could use to try and find its corresponding ++ // local data compound ++ ++ java.nio.file.Path containingFolder = this.externalFileDir; ++ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new); ++ boolean[] oversized = new boolean[32 * 32]; ++ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32]; ++ ++ if (regionFiles != null) { ++ int lowerXBound = ourLowerLeftPosition.x; // inclusive ++ int lowerZBound = ourLowerLeftPosition.z; // inclusive ++ int upperXBound = lowerXBound + 32 - 1; // inclusive ++ int upperZBound = lowerZBound + 32 - 1; // inclusive ++ ++ // read mojang oversized data ++ for (Path regionFile : regionFiles) { ++ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile); ++ if (oversizedCoords == null) { ++ continue; ++ } ++ ++ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { ++ continue; // not in our regionfile ++ } ++ ++ // ensure oversized data is valid & is newer than data in the regionfile ++ ++ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); ++ ++ byte[] chunkData; ++ try { ++ chunkData = Files.readAllBytes(regionFile); ++ } catch (Exception ex) { ++ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex); ++ continue; ++ } ++ ++ CompoundTag compound = null; ++ ++ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them ++ RegionFileVersion compression = null; ++ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) { ++ try { ++ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java ++ compound = NbtIo.read((java.io.DataInput)in); ++ compression = compressionType; ++ break; // reaches here iff readNBT does not throw ++ } catch (Exception ex) { ++ continue; ++ } ++ } ++ ++ if (compound == null) { ++ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost"); ++ continue; ++ } ++ ++ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) { ++ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords); ++ continue; ++ } ++ ++ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) { ++ oversized[location] = true; ++ oversizedCompressionTypes[location] = compression; ++ } ++ } ++ } ++ ++ // now we need to calculate a new offset header ++ ++ int[] calculatedOffsets = new int[32 * 32]; ++ RegionBitmap newSectorAllocations = new RegionBitmap(); ++ newSectorAllocations.force(0, 2); // make space for header ++ ++ // allocate sectors for normal chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (oversized[location]) { ++ continue; ++ } ++ ++ int rawLength = rawLengths[location]; // bytes ++ int sectorOffset = sectorOffsets[location]; // sectors ++ int sectorLength = (int)roundToSectors(rawLength); ++ ++ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } else { ++ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated"); ++ } ++ } ++ } ++ ++ // allocate sectors for oversized chunks ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ if (!oversized[location]) { ++ continue; ++ } ++ ++ int sectorOffset = newSectorAllocations.allocate(1); ++ int sectorLength = 1; ++ ++ try { ++ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096); ++ // only allocate in the new offsets if the write succeeds ++ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized ++ } catch (IOException ex) { ++ newSectorAllocations.free(sectorOffset, sectorLength); ++ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated"); ++ } ++ } ++ } ++ ++ // rewrite aikar oversized data ++ ++ this.oversizedCount = 0; ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; ++ ++ this.oversizedCount += isAikarOversized; ++ this.oversized[location] = (byte)isAikarOversized; ++ } ++ } ++ ++ if (this.oversizedCount > 0) { ++ try { ++ this.writeOversizedMeta(); ++ } catch (Exception ex) { ++ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex); ++ Files.deleteIfExists(this.getOversizedMetaFile()); ++ } ++ } else { ++ Files.deleteIfExists(this.getOversizedMetaFile()); ++ } ++ ++ this.usedSectors.copyFrom(newSectorAllocations); ++ ++ // before we overwrite the old sectors, print a summary of the chunks that got changed. ++ ++ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath()); ++ ++ for (int chunkX = 0; chunkX < 32; ++chunkX) { ++ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { ++ int location = chunkX | (chunkZ << 5); ++ ++ int oldOffset = this.offsets.get(location); ++ int newOffset = calculatedOffsets[location]; ++ ++ if (oldOffset == newOffset) { ++ continue; ++ } ++ ++ this.offsets.put(location, newOffset); // overwrite incorrect offset ++ ++ if (oldOffset == 0) { ++ // found lost data ++ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath()); ++ } else if (newOffset == 0) { ++ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated"); ++ } else { ++ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath()); ++ } ++ } ++ } ++ ++ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath()); ++ ++ // simply destroy the timestamp header, it's not used ++ ++ for (int i = 0; i < 32 * 32; ++i) { ++ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this ++ } ++ ++ // write new header ++ try { ++ this.flush(); ++ this.file.force(true); // try to ensure it goes through... ++ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath()); ++ } catch (IOException ex) { ++ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex); ++ } ++ } ++ ++ return true; ++ } ++ ++ final boolean canRecalcHeader; // final forces compile fail on new constructor ++ // Paper end ++ + // Paper start - Cache chunk status + private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; + +@@ -78,8 +427,19 @@ public class RegionFile implements AutoCloseable { + public RegionFile(Path file, Path directory, boolean dsync) throws IOException { + this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync); + } ++ // Paper start - add can recalc flag ++ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException { ++ this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader); ++ } ++ // Paper end - add can recalc flag + + public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { ++ // Paper start - add can recalc flag ++ this(file, directory, outputChunkStreamVersion, dsync, false); ++ } ++ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException { ++ this.canRecalcHeader = canRecalcHeader; ++ // Paper end - add can recalc flag + this.header = ByteBuffer.allocateDirect(8192); + this.regionFile = file; // Paper + initOversizedState(); // Paper +@@ -108,14 +468,16 @@ public class RegionFile implements AutoCloseable { + RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i); + } + +- long j = Files.size(file); ++ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption + +- for (int k = 0; k < 1024; ++k) { +- int l = this.offsets.get(k); ++ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption ++ boolean hasBackedUp = false; // Paper - recalculate header on header corruption ++ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location ++ final int l = this.offsets.get(k); + + if (l != 0) { +- int i1 = RegionFile.getSectorNumber(l); +- int j1 = RegionFile.getNumSectors(l); ++ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors ++ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments + // Spigot start + if (j1 == 255) { + // We're maxed out, so we need to read the proper length from the section +@@ -124,32 +486,102 @@ public class RegionFile implements AutoCloseable { + j1 = (realLen.getInt(0) + 4) / 4096 + 1; + } + // Spigot end ++ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region + + if (i1 < 2) { + RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1}); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else if (j1 == 0) { + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else if ((long) i1 * 4096L > j) { + RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1}); +- this.offsets.put(k, 0); ++ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change + } else { +- this.usedSectors.force(i1, j1); ++ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate ++ } ++ // Paper start - recalculate header on header corruption ++ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { ++ if (canRecalcHeader) { ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header..."); ++ needsHeaderRecalc = true; ++ break; ++ } else { ++ // location = chunkX | (chunkZ << 5); ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.offsets.put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ } ++ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); ++ if (failedToAllocate) { ++ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath()); + } ++ if (failedToAllocate & !canRecalcHeader) { ++ // location = chunkX | (chunkZ << 5); ++ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + ++ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); ++ if (!hasBackedUp) { ++ hasBackedUp = true; ++ this.backupRegionFile(); ++ } ++ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too ++ this.offsets.put(headerLocation, 0); // delete the entry from header ++ continue; ++ } ++ needsHeaderRecalc |= failedToAllocate; ++ // Paper end - recalculate header on header corruption + } + } ++ // Paper start - recalculate header on header corruption ++ // we move the recalc here so comparison to old header is correct when logging to console ++ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues ++ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations"); ++ this.recalculateHeader(); ++ } ++ // Paper end + } + + } + } + + private Path getExternalChunkPath(ChunkPos chunkPos) { +- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; ++ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change + + return this.externalFileDir.resolve(s); + } + ++ // Paper start ++ private static ChunkPos getOversizedChunkPair(Path file) { ++ String fileName = file.getFileName().toString(); ++ ++ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { ++ return null; ++ } ++ ++ String[] split = fileName.split("\\."); ++ ++ if (split.length != 4) { ++ return null; ++ } ++ ++ try { ++ int x = Integer.parseInt(split[1]); ++ int z = Integer.parseInt(split[2]); ++ ++ return new ChunkPos(x, z); ++ } catch (NumberFormatException ex) { ++ return null; ++ } ++ } ++ // Paper end ++ + @Nullable + public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { + int i = this.getOffset(pos); +@@ -173,6 +605,11 @@ public class RegionFile implements AutoCloseable { + ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + if (bytebuffer.remaining() < 5) { + RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { + int i1 = bytebuffer.getInt(); +@@ -180,6 +617,11 @@ public class RegionFile implements AutoCloseable { + + if (i1 == 0) { + RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { + int j1 = i1 - 1; +@@ -187,17 +629,44 @@ public class RegionFile implements AutoCloseable { + if (RegionFile.isExternalStreamChunk(b0)) { + if (j1 != 0) { + RegionFile.LOGGER.warn("Chunk has both internal and external streams"); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + } + +- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); ++ // Paper start - recalculate header on regionfile corruption ++ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); ++ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ return ret; ++ // Paper end - recalculate header on regionfile corruption + } else if (j1 > bytebuffer.remaining()) { + RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()}); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else if (j1 < 0) { + RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); ++ // Paper start - recalculate header on regionfile corruption ++ if (this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ // Paper end - recalculate header on regionfile corruption + return null; + } else { +- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); ++ // Paper start - recalculate header on regionfile corruption ++ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); ++ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { ++ return this.getChunkDataInputStream(pos); ++ } ++ return ret; ++ // Paper end - recalculate header on regionfile corruption + } + } + } +@@ -372,10 +841,15 @@ public class RegionFile implements AutoCloseable { + } + + private ByteBuffer createExternalStub() { ++ // Paper start - add version param ++ return this.createExternalStub(this.version); ++ } ++ private ByteBuffer createExternalStub(RegionFileVersion version) { ++ // Paper end - add version param + ByteBuffer bytebuffer = ByteBuffer.allocate(5); + + bytebuffer.putInt(1); +- bytebuffer.put((byte) (this.version.getId() | 128)); ++ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param + ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error + return bytebuffer; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index 666286f94f09299f95538ef2f19b588eb74d9dda..f38ec8914e1953091ab65aa3aaefc886d3eede8a 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -26,7 +26,15 @@ public class RegionFileStorage implements AutoCloseable { + private final Path folder; + private final boolean sync; + ++ private final boolean isChunkData; // Paper ++ + RegionFileStorage(Path directory, boolean dsync) { ++ // Paper start - add isChunkData param ++ this(directory, dsync, false); ++ } ++ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) { ++ this.isChunkData = isChunkData; ++ // Paper end - add isChunkData param + this.folder = directory; + this.sync = dsync; + } +@@ -88,9 +96,9 @@ public class RegionFileStorage implements AutoCloseable { + Files.createDirectories(this.folder); + Path path = this.folder; + int j = chunkcoordintpair.getRegionX(); +- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); ++ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change + if (existingOnly && !Files.exists(path1)) return null; // CraftBukkit +- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); ++ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header + + this.regionCache.putAndMoveToFirst(i, regionfile1); + // Paper start +@@ -175,6 +183,13 @@ public class RegionFileStorage implements AutoCloseable { + if (regionfile == null) { + return null; + } ++ // Paper start - Add regionfile parameter ++ return this.read(pos, regionfile); ++ } ++ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException { ++ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile ++ // if we decide to re-read ++ // Paper end + // CraftBukkit end + try { // Paper + DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); +@@ -191,6 +206,20 @@ public class RegionFileStorage implements AutoCloseable { + try { + if (datainputstream != null) { + nbttagcompound = NbtIo.read((DataInput) datainputstream); ++ // Paper start - recover from corrupt regionfile header ++ if (this.isChunkData) { ++ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); ++ if (!chunkPos.equals(pos)) { ++ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath()); ++ if (regionfile.recalculateHeader()) { ++ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. ++ return this.read(pos, regionfile); ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath()); ++ return null; ++ } ++ } ++ // Paper end - recover from corrupt regionfile header + break label43; + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +index 95070af5e5bb7013ce7126ba9f725b43e3c4c749..97d4ae5619dcc0922e0381b1bb45a135f514e3af 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +@@ -14,7 +14,7 @@ import javax.annotation.Nullable; + import net.minecraft.util.FastBufferedInputStream; + + public class RegionFileVersion { +- private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); ++ public static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - public + public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (inputStream) -> { + return new FastBufferedInputStream(new GZIPInputStream(inputStream)); + }, (outputStream) -> { diff --git a/patches/server/0737-Execute-chunk-tasks-mid-tick.patch b/patches/server/0737-Execute-chunk-tasks-mid-tick.patch deleted file mode 100644 index c2e8035538..0000000000 --- a/patches/server/0737-Execute-chunk-tasks-mid-tick.patch +++ /dev/null @@ -1,179 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 6 Apr 2020 04:20:44 -0700 -Subject: [PATCH] Execute chunk tasks mid-tick - -This will help the server load chunks if tick times are high. - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index 23e564b05ba438924180c91f9b19a60731eedd1b..5ec241d49ff5e3a161a39006f05823a5de847c5e 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -46,6 +46,8 @@ public final class MinecraftTimings { - public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); - public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); - -+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); -+ - private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); - - private MinecraftTimings() {} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 98fe4165d291b47a39ce741884353c87dd0a4789..99073ea2757cd1c15b098d7cfaf8681702f04a19 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1304,6 +1304,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= MAX_CHUNK_EXEC_TIME) { -+ if (!moreTasks) { -+ lastMidTickExecuteFailure = currTime; -+ } -+ -+ // note: negative values reduce the time -+ long overuse = diff - MAX_CHUNK_EXEC_TIME; -+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms -+ // make sure something like a GC or dumb plugin doesn't screw us over... -+ overuse = 10L * 1000L * 1000L; // 10ms -+ } -+ -+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; -+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); -+ -+ lastMidTickExecute = currTime + extraSleep; -+ return; -+ } -+ } -+ } finally { -+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); -+ } -+ } -+ // Paper end - execute chunk tasks mid tick - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 7b1279256ed7963ba4e225b15592816087ab16b4..1510e1fcfbbb2ccbaf57dd274bed05afc85eeeb0 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -835,6 +835,7 @@ public class ServerChunkCache extends ChunkSource { - iterator1 = shuffled.iterator(); - } - -+ int chunksTicked = 0; // Paper - try { - while (iterator1.hasNext()) { - LevelChunk chunk1 = iterator1.next(); -@@ -852,6 +853,7 @@ public class ServerChunkCache extends ChunkSource { - - if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { - this.level.tickChunk(chunk1, k); -+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper - } - } - // Paper start - optimise chunk tick iteration -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 29b80d074600fa7e20b05d1fe70ac12969b954a4..7324f2ad437a15c42f84ba2deeb58861c0552209 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -212,6 +212,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - private final StructureManager structureManager; - private final StructureCheck structureCheck; - private final boolean tickTime; -+ public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick - - // CraftBukkit start - public final LevelStorageSource.LevelStorageAccess convertable; -@@ -991,6 +992,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (fluid1.is(fluid)) { - fluid1.tick(this, pos); - } -+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick - - } - -@@ -1000,6 +1002,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (iblockdata.is(block)) { - iblockdata.tick(this, pos, this.random); - } -+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick - - } - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index a04517137ab9deff215b6f9b9ee405500af0e393..90b3ca30a5ab381acde66393eb1fed5be677e5c8 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -800,6 +800,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // Spigot end - } else if (this.shouldTickBlocksAt(tickingblockentity.getPos())) { - tickingblockentity.tick(); -+ // Paper start - execute chunk tasks during tick -+ if ((this.tileTickPosition & 7) == 0) { -+ MinecraftServer.getServer().executeMidTickTasks(); -+ } -+ // Paper end - execute chunk tasks during tick - } - } - this.blockEntityTickers.removeAll(toRemove); -@@ -814,6 +819,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public void guardEntityTick(Consumer tickConsumer, T entity) { - try { - tickConsumer.accept(entity); -+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick - } catch (Throwable throwable) { - if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent tile entity and entity crashes diff --git a/patches/server/0738-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/patches/server/0738-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch deleted file mode 100644 index c448b0249c..0000000000 --- a/patches/server/0738-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch +++ /dev/null @@ -1,766 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 2 Feb 2020 02:25:10 -0800 -Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt - -Instead of trying to relocate the chunk, which is seems to never -be the correct choice, so we end up duplicating or swapping chunks, -we instead drop the current regionfile header and recalculate - -hoping that at least then we don't swap chunks, and maybe recover -them all. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 12e3831324abdc1112cbe65cab0f0c75ce77a9ef..be9c15fe141ede1132dbe07ba4bfcf22036ab194 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -68,6 +68,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks; - import org.slf4j.Logger; - - public class ChunkSerializer { -+ // Paper start -+ // TODO: Check on update -+ public static long getLastWorldSaveTime(CompoundTag chunkData) { -+ final int dataVersion = ChunkStorage.getVersion(chunkData); -+ if (dataVersion < 2842) { // Level tag is removed after this version -+ final CompoundTag levelData = chunkData.getCompound("Level"); -+ return levelData.getLong("LastUpdate"); -+ } else { -+ return chunkData.getLong("LastUpdate"); -+ } -+ } -+ // Paper end - - public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -473,7 +485,7 @@ public class ChunkSerializer { - nbttagcompound.putInt("xPos", chunkcoordintpair.x); - nbttagcompound.putInt("yPos", chunk.getMinSection()); - nbttagcompound.putInt("zPos", chunkcoordintpair.z); -- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading -+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change - nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime()); - nbttagcompound.putString("Status", chunk.getStatus().getName()); - BlendingData blendingdata = chunk.getBlendingData(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 315be30daf0be84efbb4d634dc01e1bf9e6e696e..3010e092bd1337e5a6b99636bbd312d90e470acf 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -41,7 +41,7 @@ public class ChunkStorage implements AutoCloseable { - this.fixerUpper = dataFixer; - // Paper start - async chunk io - // remove IO worker -- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker -+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper - // Paper end - async chunk io - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -index c8298a597818227de33a4afce4698ec0666cf758..6baceb6ce9021c489be6e79d338a9704285afa26 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -@@ -9,6 +9,27 @@ import java.util.BitSet; - public class RegionBitmap { - private final BitSet used = new BitSet(); - -+ // Paper start -+ public final void copyFrom(RegionBitmap other) { -+ BitSet thisBitset = this.used; -+ BitSet otherBitset = other.used; -+ -+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { -+ thisBitset.set(i, otherBitset.get(i)); -+ } -+ } -+ -+ public final boolean tryAllocate(int from, int length) { -+ BitSet bitset = this.used; -+ int firstSet = bitset.nextSetBit(from); -+ if (firstSet > 0 && firstSet < (from + length)) { -+ return false; -+ } -+ bitset.set(from, from + length); -+ return true; -+ } -+ // Paper end -+ - public void force(int start, int size) { - this.used.set(start, start + size); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index 861a25a15f1aab20e3245b6d5cdad5d23bdfd6d0..8ff8855c5267379b3a5f5d8baa4a275ffee2c4bf 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 -@@ -51,6 +51,355 @@ public class RegionFile implements AutoCloseable { - public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper - public final Path regionFile; // Paper - -+ // Paper start - try to recover from RegionFile header corruption -+ private static long roundToSectors(long bytes) { -+ long sectors = bytes >>> 12; // 4096 = 2^12 -+ long remainingBytes = bytes & 4095; -+ long sign = -remainingBytes; // sign is 1 if nonzero -+ return sectors + (sign >>> 63); -+ } -+ -+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); -+ -+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { -+ try { -+ if (chunkDataLength < 0) { -+ return null; -+ } -+ -+ long offset = sector * 4096L + 4L; // offset for chunk data -+ -+ if ((offset + chunkDataLength) > fileLength) { -+ return null; -+ } -+ -+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); -+ if (chunkDataLength != this.file.read(chunkData, offset)) { -+ return null; -+ } -+ -+ ((java.nio.Buffer)chunkData).flip(); -+ -+ byte compressionType = chunkData.get(); -+ if (compressionType < 0) { // compressionType & 128 != 0 -+ // oversized chunk -+ return OVERSIZED_COMPOUND; -+ } -+ -+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType); -+ if (compression == null) { -+ return null; -+ } -+ -+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); -+ -+ return NbtIo.read(new DataInputStream(input)); -+ } catch (Exception ex) { -+ return null; -+ } -+ } -+ -+ private int getLength(long sector) throws IOException { -+ ByteBuffer length = ByteBuffer.allocate(4); -+ if (4 != this.file.read(length, sector * 4096L)) { -+ return -1; -+ } -+ -+ return length.getInt(0); -+ } -+ -+ private void backupRegionFile() { -+ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup"); -+ this.backupRegionFile(backup); -+ } -+ -+ private void backupRegionFile(Path to) { -+ try { -+ this.file.force(true); -+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath()); -+ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES); -+ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex); -+ } -+ } -+ -+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) { -+ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31); -+ } -+ -+ // note: only call for CHUNK regionfiles -+ boolean recalculateHeader() throws IOException { -+ if (!this.canRecalcHeader) { -+ return false; -+ } -+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile); -+ if (ourLowerLeftPosition == null) { -+ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header"); -+ return false; -+ } -+ synchronized (this) { -+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable()); -+ -+ // try to backup file so maybe it could be sent to us for further investigation -+ -+ this.backupRegionFile(); -+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) -+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes -+ int[] sectorOffsets = new int[32 * 32]; // in sectors -+ boolean[] hasAikarOversized = new boolean[32 * 32]; -+ -+ long fileLength = this.file.size(); -+ long totalSectors = roundToSectors(fileLength); -+ -+ // search the regionfile from start to finish for the most up-to-date chunk data -+ -+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip -+ int chunkDataLength = this.getLength(i); -+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); -+ if (compound == null || compound == OVERSIZED_COMPOUND) { -+ continue; -+ } -+ -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound); -+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) { -+ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")"); -+ continue; -+ } -+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); -+ -+ CompoundTag otherCompound = compounds[location]; -+ -+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) { -+ continue; // don't overwrite newer data. -+ } -+ -+ // aikar oversized? -+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); -+ boolean isAikarOversized = false; -+ if (Files.exists(aikarOversizedFile)) { -+ try { -+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); -+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) { -+ // best we got for an id. hope it's good enough -+ isAikarOversized = true; -+ } -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex); -+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data -+ } -+ } -+ -+ hasAikarOversized[location] = isAikarOversized; -+ compounds[location] = compound; -+ rawLengths[location] = chunkDataLength + 4; -+ sectorOffsets[location] = (int)i; -+ -+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); -+ i += chunkSectorLength; -+ --i; // gets incremented next iteration -+ } -+ -+ // forge style oversized data is already handled by the local search, and aikar data we just hope -+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding -+ // local data compound -+ -+ java.nio.file.Path containingFolder = this.externalFileDir; -+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new); -+ boolean[] oversized = new boolean[32 * 32]; -+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32]; -+ -+ if (regionFiles != null) { -+ int lowerXBound = ourLowerLeftPosition.x; // inclusive -+ int lowerZBound = ourLowerLeftPosition.z; // inclusive -+ int upperXBound = lowerXBound + 32 - 1; // inclusive -+ int upperZBound = lowerZBound + 32 - 1; // inclusive -+ -+ // read mojang oversized data -+ for (Path regionFile : regionFiles) { -+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile); -+ if (oversizedCoords == null) { -+ continue; -+ } -+ -+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { -+ continue; // not in our regionfile -+ } -+ -+ // ensure oversized data is valid & is newer than data in the regionfile -+ -+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); -+ -+ byte[] chunkData; -+ try { -+ chunkData = Files.readAllBytes(regionFile); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex); -+ continue; -+ } -+ -+ CompoundTag compound = null; -+ -+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them -+ RegionFileVersion compression = null; -+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) { -+ try { -+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java -+ compound = NbtIo.read((java.io.DataInput)in); -+ compression = compressionType; -+ break; // reaches here iff readNBT does not throw -+ } catch (Exception ex) { -+ continue; -+ } -+ } -+ -+ if (compound == null) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost"); -+ continue; -+ } -+ -+ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) { -+ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords); -+ continue; -+ } -+ -+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) { -+ oversized[location] = true; -+ oversizedCompressionTypes[location] = compression; -+ } -+ } -+ } -+ -+ // now we need to calculate a new offset header -+ -+ int[] calculatedOffsets = new int[32 * 32]; -+ RegionBitmap newSectorAllocations = new RegionBitmap(); -+ newSectorAllocations.force(0, 2); // make space for header -+ -+ // allocate sectors for normal chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (oversized[location]) { -+ continue; -+ } -+ -+ int rawLength = rawLengths[location]; // bytes -+ int sectorOffset = sectorOffsets[location]; // sectors -+ int sectorLength = (int)roundToSectors(rawLength); -+ -+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } else { -+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated"); -+ } -+ } -+ } -+ -+ // allocate sectors for oversized chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (!oversized[location]) { -+ continue; -+ } -+ -+ int sectorOffset = newSectorAllocations.allocate(1); -+ int sectorLength = 1; -+ -+ try { -+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096); -+ // only allocate in the new offsets if the write succeeds -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } catch (IOException ex) { -+ newSectorAllocations.free(sectorOffset, sectorLength); -+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated"); -+ } -+ } -+ } -+ -+ // rewrite aikar oversized data -+ -+ this.oversizedCount = 0; -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; -+ -+ this.oversizedCount += isAikarOversized; -+ this.oversized[location] = (byte)isAikarOversized; -+ } -+ } -+ -+ if (this.oversizedCount > 0) { -+ try { -+ this.writeOversizedMeta(); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex); -+ Files.deleteIfExists(this.getOversizedMetaFile()); -+ } -+ } else { -+ Files.deleteIfExists(this.getOversizedMetaFile()); -+ } -+ -+ this.usedSectors.copyFrom(newSectorAllocations); -+ -+ // before we overwrite the old sectors, print a summary of the chunks that got changed. -+ -+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath()); -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ int oldOffset = this.offsets.get(location); -+ int newOffset = calculatedOffsets[location]; -+ -+ if (oldOffset == newOffset) { -+ continue; -+ } -+ -+ this.offsets.put(location, newOffset); // overwrite incorrect offset -+ -+ if (oldOffset == 0) { -+ // found lost data -+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath()); -+ } else if (newOffset == 0) { -+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated"); -+ } else { -+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath()); -+ } -+ } -+ } -+ -+ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath()); -+ -+ // simply destroy the timestamp header, it's not used -+ -+ for (int i = 0; i < 32 * 32; ++i) { -+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this -+ } -+ -+ // write new header -+ try { -+ this.flush(); -+ this.file.force(true); // try to ensure it goes through... -+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex); -+ } -+ } -+ -+ return true; -+ } -+ -+ final boolean canRecalcHeader; // final forces compile fail on new constructor -+ // Paper end -+ - // Paper start - Cache chunk status - private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32]; - -@@ -78,8 +427,19 @@ public class RegionFile implements AutoCloseable { - public RegionFile(Path file, Path directory, boolean dsync) throws IOException { - this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync); - } -+ // Paper start - add can recalc flag -+ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader); -+ } -+ // Paper end - add can recalc flag - - public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { -+ // Paper start - add can recalc flag -+ this(file, directory, outputChunkStreamVersion, dsync, false); -+ } -+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this.canRecalcHeader = canRecalcHeader; -+ // Paper end - add can recalc flag - this.header = ByteBuffer.allocateDirect(8192); - this.regionFile = file; // Paper - initOversizedState(); // Paper -@@ -108,14 +468,16 @@ public class RegionFile implements AutoCloseable { - RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i); - } - -- long j = Files.size(file); -+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption - -- for (int k = 0; k < 1024; ++k) { -- int l = this.offsets.get(k); -+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption -+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption -+ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location -+ final int l = this.offsets.get(k); - - if (l != 0) { -- int i1 = RegionFile.getSectorNumber(l); -- int j1 = RegionFile.getNumSectors(l); -+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors -+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments - // Spigot start - if (j1 == 255) { - // We're maxed out, so we need to read the proper length from the section -@@ -124,32 +486,102 @@ public class RegionFile implements AutoCloseable { - j1 = (realLen.getInt(0) + 4) / 4096 + 1; - } - // Spigot end -+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region - - if (i1 < 2) { - RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1}); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else if (j1 == 0) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else if ((long) i1 * 4096L > j) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1}); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change - } else { -- this.usedSectors.force(i1, j1); -+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate -+ } -+ // Paper start - recalculate header on header corruption -+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { -+ if (canRecalcHeader) { -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header..."); -+ needsHeaderRecalc = true; -+ break; -+ } else { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } -+ } -+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); -+ if (failedToAllocate) { -+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath()); - } -+ if (failedToAllocate & !canRecalcHeader) { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } -+ needsHeaderRecalc |= failedToAllocate; -+ // Paper end - recalculate header on header corruption - } - } -+ // Paper start - recalculate header on header corruption -+ // we move the recalc here so comparison to old header is correct when logging to console -+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues -+ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations"); -+ this.recalculateHeader(); -+ } -+ // Paper end - } - - } - } - - private Path getExternalChunkPath(ChunkPos chunkPos) { -- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; -+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change - - return this.externalFileDir.resolve(s); - } - -+ // Paper start -+ private static ChunkPos getOversizedChunkPair(Path file) { -+ String fileName = file.getFileName().toString(); -+ -+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { -+ return null; -+ } -+ -+ String[] split = fileName.split("\\."); -+ -+ if (split.length != 4) { -+ return null; -+ } -+ -+ try { -+ int x = Integer.parseInt(split[1]); -+ int z = Integer.parseInt(split[2]); -+ -+ return new ChunkPos(x, z); -+ } catch (NumberFormatException ex) { -+ return null; -+ } -+ } -+ // Paper end -+ - @Nullable - public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { - int i = this.getOffset(pos); -@@ -173,6 +605,11 @@ public class RegionFile implements AutoCloseable { - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - if (bytebuffer.remaining() < 5) { - RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()}); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { - int i1 = bytebuffer.getInt(); -@@ -180,6 +617,11 @@ public class RegionFile implements AutoCloseable { - - if (i1 == 0) { - RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { - int j1 = i1 - 1; -@@ -187,17 +629,44 @@ public class RegionFile implements AutoCloseable { - if (RegionFile.isExternalStreamChunk(b0)) { - if (j1 != 0) { - RegionFile.LOGGER.warn("Chunk has both internal and external streams"); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - } - -- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Paper end - recalculate header on regionfile corruption - } else if (j1 > bytebuffer.remaining()) { - RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()}); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else if (j1 < 0) { - RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); -+ // Paper start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ // Paper end - recalculate header on regionfile corruption - return null; - } else { -- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Paper end - recalculate header on regionfile corruption - } - } - } -@@ -372,10 +841,15 @@ public class RegionFile implements AutoCloseable { - } - - private ByteBuffer createExternalStub() { -+ // Paper start - add version param -+ return this.createExternalStub(this.version); -+ } -+ private ByteBuffer createExternalStub(RegionFileVersion version) { -+ // Paper end - add version param - ByteBuffer bytebuffer = ByteBuffer.allocate(5); - - bytebuffer.putInt(1); -- bytebuffer.put((byte) (this.version.getId() | 128)); -+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - return bytebuffer; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 666286f94f09299f95538ef2f19b588eb74d9dda..f38ec8914e1953091ab65aa3aaefc886d3eede8a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -26,7 +26,15 @@ public class RegionFileStorage implements AutoCloseable { - private final Path folder; - private final boolean sync; - -+ private final boolean isChunkData; // Paper -+ - RegionFileStorage(Path directory, boolean dsync) { -+ // Paper start - add isChunkData param -+ this(directory, dsync, false); -+ } -+ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) { -+ this.isChunkData = isChunkData; -+ // Paper end - add isChunkData param - this.folder = directory; - this.sync = dsync; - } -@@ -88,9 +96,9 @@ public class RegionFileStorage implements AutoCloseable { - Files.createDirectories(this.folder); - Path path = this.folder; - int j = chunkcoordintpair.getRegionX(); -- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change - if (existingOnly && !Files.exists(path1)) return null; // CraftBukkit -- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync); -+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header - - this.regionCache.putAndMoveToFirst(i, regionfile1); - // Paper start -@@ -175,6 +183,13 @@ public class RegionFileStorage implements AutoCloseable { - if (regionfile == null) { - return null; - } -+ // Paper start - Add regionfile parameter -+ return this.read(pos, regionfile); -+ } -+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException { -+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile -+ // if we decide to re-read -+ // Paper end - // CraftBukkit end - try { // Paper - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); -@@ -191,6 +206,20 @@ public class RegionFileStorage implements AutoCloseable { - try { - if (datainputstream != null) { - nbttagcompound = NbtIo.read((DataInput) datainputstream); -+ // Paper start - recover from corrupt regionfile header -+ if (this.isChunkData) { -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); -+ if (!chunkPos.equals(pos)) { -+ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath()); -+ if (regionfile.recalculateHeader()) { -+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. -+ return this.read(pos, regionfile); -+ } -+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath()); -+ return null; -+ } -+ } -+ // Paper end - recover from corrupt regionfile header - break label43; - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -index 95070af5e5bb7013ce7126ba9f725b43e3c4c749..97d4ae5619dcc0922e0381b1bb45a135f514e3af 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -@@ -14,7 +14,7 @@ import javax.annotation.Nullable; - import net.minecraft.util.FastBufferedInputStream; - - public class RegionFileVersion { -- private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); -+ public static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - public - public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (inputStream) -> { - return new FastBufferedInputStream(new GZIPInputStream(inputStream)); - }, (outputStream) -> { diff --git a/patches/server/0738-Custom-table-implementation-for-blockstate-state-loo.patch b/patches/server/0738-Custom-table-implementation-for-blockstate-state-loo.patch new file mode 100644 index 0000000000..6cf6b743b6 --- /dev/null +++ b/patches/server/0738-Custom-table-implementation-for-blockstate-state-loo.patch @@ -0,0 +1,343 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 11 Mar 2021 20:05:44 -0800 +Subject: [PATCH] Custom table implementation for blockstate state lookups + +Testing some redstone intensive machines showed to bring about a 10% +improvement. + +diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..57d0cd3ad6f972e986c72a57f1a6e36003f190c2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java +@@ -0,0 +1,160 @@ ++package io.papermc.paper.util.table; ++ ++import com.google.common.collect.Table; ++import net.minecraft.world.level.block.state.StateHolder; ++import net.minecraft.world.level.block.state.properties.Property; ++import java.util.Collection; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class ZeroCollidingReferenceStateTable { ++ ++ // upper 32 bits: starting index ++ // lower 32 bits: bitset for contained ids ++ protected final long[] this_index_table; ++ protected final Comparable[] this_table; ++ protected final StateHolder this_state; ++ ++ protected long[] index_table; ++ protected StateHolder[][] value_table; ++ ++ public ZeroCollidingReferenceStateTable(final StateHolder state, final Map, Comparable> this_map) { ++ this.this_state = state; ++ this.this_index_table = this.create_table(this_map.keySet()); ++ ++ int max_id = -1; ++ for (final Property property : this_map.keySet()) { ++ final int id = lookup_vindex(property, this.this_index_table); ++ if (id > max_id) { ++ max_id = id; ++ } ++ } ++ ++ this.this_table = new Comparable[max_id + 1]; ++ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { ++ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); ++ } ++ } ++ ++ public void loadInTable(final Table, Comparable, StateHolder> table, ++ final Map, Comparable> this_map) { ++ final Set> combined = new HashSet<>(table.rowKeySet()); ++ combined.addAll(this_map.keySet()); ++ ++ this.index_table = this.create_table(combined); ++ ++ int max_id = -1; ++ for (final Property property : combined) { ++ final int id = lookup_vindex(property, this.index_table); ++ if (id > max_id) { ++ max_id = id; ++ } ++ } ++ ++ this.value_table = new StateHolder[max_id + 1][]; ++ ++ final Map, Map, StateHolder>> map = table.rowMap(); ++ for (final Property property : map.keySet()) { ++ final Map, StateHolder> propertyMap = map.get(property); ++ ++ final int id = lookup_vindex(property, this.index_table); ++ final StateHolder[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()]; ++ ++ for (final Map.Entry, StateHolder> entry : propertyMap.entrySet()) { ++ if (entry.getValue() == null) { ++ // TODO what ++ continue; ++ } ++ ++ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue(); ++ } ++ } ++ ++ ++ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { ++ final Property property = entry.getKey(); ++ final int index = lookup_vindex(property, this.index_table); ++ ++ if (this.value_table[index] == null) { ++ this.value_table[index] = new StateHolder[property.getPossibleValues().size()]; ++ } ++ ++ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state; ++ } ++ } ++ ++ ++ protected long[] create_table(final Collection> collection) { ++ int max_id = -1; ++ for (final Property property : collection) { ++ final int id = property.getId(); ++ if (id > max_id) { ++ max_id = id; ++ } ++ } ++ ++ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32) ++ ++ for (final Property property : collection) { ++ final int id = property.getId(); ++ ++ ret[id >>> 5] |= (1L << (id & 31)); ++ } ++ ++ int total = 0; ++ for (int i = 1, len = ret.length; i < len; ++i) { ++ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32; ++ } ++ ++ return ret; ++ } ++ ++ public Comparable get(final Property state) { ++ final Comparable[] table = this.this_table; ++ final int index = lookup_vindex(state, this.this_index_table); ++ ++ if (index < 0 || index >= table.length) { ++ return null; ++ } ++ return table[index]; ++ } ++ ++ public StateHolder get(final Property property, final Comparable with) { ++ final int withId = ((Property)property).getIdFor(with); ++ if (withId < 0) { ++ return null; ++ } ++ ++ final int index = lookup_vindex(property, this.index_table); ++ final StateHolder[][] table = this.value_table; ++ if (index < 0 || index >= table.length) { ++ return null; ++ } ++ ++ final StateHolder[] values = table[index]; ++ ++ if (withId >= values.length) { ++ return null; ++ } ++ ++ return values[withId]; ++ } ++ ++ protected static int lookup_vindex(final Property property, final long[] index_table) { ++ final int id = property.getId(); ++ final long bitset_mask = (1L << (id & 31)); ++ final long lower_mask = bitset_mask - 1; ++ final int index = id >>> 5; ++ if (index >= index_table.length) { ++ return -1; ++ } ++ final long index_value = index_table[index]; ++ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain ++ ++ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id ++ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0, ++ // otherwise it comes out as -1. ++ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check); ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +index ab712fd29b316e1235645bacaa79aa0a64d0bc00..340d0648fcf9b9749c4daa1c25a226b947707c3d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java ++++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +@@ -39,11 +39,13 @@ public abstract class StateHolder { + private final ImmutableMap, Comparable> values; + private Table, Comparable, S> neighbours; + protected final MapCodec propertiesCodec; ++ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup + + protected StateHolder(O owner, ImmutableMap, Comparable> entries, MapCodec codec) { + this.owner = owner; + this.values = entries; + this.propertiesCodec = codec; ++ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup + } + + public > S cycle(Property property) { +@@ -84,11 +86,11 @@ public abstract class StateHolder { + } + + public > boolean hasProperty(Property property) { +- return this.values.containsKey(property); ++ return this.optimisedTable.get(property) != null; // Paper - optimise state lookup + } + + public > T getValue(Property property) { +- Comparable comparable = this.values.get(property); ++ Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup + if (comparable == null) { + throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); + } else { +@@ -97,24 +99,18 @@ public abstract class StateHolder { + } + + public > Optional getOptionalValue(Property property) { +- Comparable comparable = this.values.get(property); ++ Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup + return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable)); + } + + public , V extends T> S setValue(Property property, V value) { +- Comparable comparable = this.values.get(property); +- if (comparable == null) { +- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); +- } else if (comparable == value) { +- return (S)this; +- } else { +- S object = this.neighbours.get(property, value); +- if (object == null) { +- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); +- } else { +- return object; +- } ++ // Paper start - optimise state lookup ++ final S ret = (S)this.optimisedTable.get(property, value); ++ if (ret == null) { ++ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); + } ++ return ret; ++ // Paper end - optimise state lookup + } + + public void populateNeighbours(Map, Comparable>, S> states) { +@@ -133,7 +129,7 @@ public abstract class StateHolder { + } + } + +- this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); ++ this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup + } + } + +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +index ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..233215280f8494dbc33a2fd0b14e37e59f1cb643 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +@@ -7,6 +7,13 @@ import java.util.Optional; + public class BooleanProperty extends Property { + private final ImmutableSet values = ImmutableSet.of(true, false); + ++ // Paper start - optimise iblockdata state lookup ++ @Override ++ public final int getIdFor(final Boolean value) { ++ return value.booleanValue() ? 1 : 0; ++ } ++ // Paper end - optimise iblockdata state lookup ++ + protected BooleanProperty(String name) { + super(name, Boolean.class); + } +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +index 0bca0f971dac994bd8b6ecd87e8b33e26c0f18f9..edd3c745efb40ee79a1393199c7a27ddaa2f8026 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +@@ -15,6 +15,15 @@ public class EnumProperty & StringRepresentable> extends Prope + private final ImmutableSet values; + private final Map names = Maps.newHashMap(); + ++ // Paper start - optimise iblockdata state lookup ++ private int[] idLookupTable; ++ ++ @Override ++ public final int getIdFor(final T value) { ++ return this.idLookupTable[value.ordinal()]; ++ } ++ // Paper end - optimise iblockdata state lookup ++ + protected EnumProperty(String name, Class type, Collection values) { + super(name, type); + this.values = ImmutableSet.copyOf(values); +@@ -28,6 +37,14 @@ public class EnumProperty & StringRepresentable> extends Prope + this.names.put(string, enum_); + } + ++ // Paper start - optimise iblockdata state lookup ++ int id = 0; ++ this.idLookupTable = new int[type.getEnumConstants().length]; ++ java.util.Arrays.fill(this.idLookupTable, -1); ++ for (final T value : this.getPossibleValues()) { ++ this.idLookupTable[value.ordinal()] = id++; ++ } ++ // Paper end - optimise iblockdata state lookup + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +index bdbe0362e49e73c05237f9f3143230e0b03e494e..8eb20ea852a8e89c431fea55a7b60833a6c8104f 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +@@ -11,6 +11,16 @@ public class IntegerProperty extends Property { + public final int min; + public final int max; + ++ // Paper start - optimise iblockdata state lookup ++ @Override ++ public final int getIdFor(final Integer value) { ++ final int val = value.intValue(); ++ final int ret = val - this.min; ++ ++ return ret | ((this.max - ret) >> 31); ++ } ++ // Paper end - optimise iblockdata state lookup ++ + protected IntegerProperty(String name, int min, int max) { + super(name, Integer.class); + if (min < 0) { +diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +index a37424bbc6bee02354abaa793aa0865c556c6bbe..f923593bd336dd1a950ba61603d53edb3c9703eb 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java ++++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +@@ -22,6 +22,17 @@ public abstract class Property> { + }, this::getName); + private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); + ++ // Paper start - optimise iblockdata state lookup ++ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); ++ private final int id = ID_GENERATOR.getAndIncrement(); ++ ++ public final int getId() { ++ return this.id; ++ } ++ ++ public abstract int getIdFor(final T value); ++ // Paper end - optimise state lookup ++ + protected Property(String name, Class type) { + this.clazz = type; + this.name = name; diff --git a/patches/server/0739-Custom-table-implementation-for-blockstate-state-loo.patch b/patches/server/0739-Custom-table-implementation-for-blockstate-state-loo.patch deleted file mode 100644 index 6cf6b743b6..0000000000 --- a/patches/server/0739-Custom-table-implementation-for-blockstate-state-loo.patch +++ /dev/null @@ -1,343 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 11 Mar 2021 20:05:44 -0800 -Subject: [PATCH] Custom table implementation for blockstate state lookups - -Testing some redstone intensive machines showed to bring about a 10% -improvement. - -diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..57d0cd3ad6f972e986c72a57f1a6e36003f190c2 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java -@@ -0,0 +1,160 @@ -+package io.papermc.paper.util.table; -+ -+import com.google.common.collect.Table; -+import net.minecraft.world.level.block.state.StateHolder; -+import net.minecraft.world.level.block.state.properties.Property; -+import java.util.Collection; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ZeroCollidingReferenceStateTable { -+ -+ // upper 32 bits: starting index -+ // lower 32 bits: bitset for contained ids -+ protected final long[] this_index_table; -+ protected final Comparable[] this_table; -+ protected final StateHolder this_state; -+ -+ protected long[] index_table; -+ protected StateHolder[][] value_table; -+ -+ public ZeroCollidingReferenceStateTable(final StateHolder state, final Map, Comparable> this_map) { -+ this.this_state = state; -+ this.this_index_table = this.create_table(this_map.keySet()); -+ -+ int max_id = -1; -+ for (final Property property : this_map.keySet()) { -+ final int id = lookup_vindex(property, this.this_index_table); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ this.this_table = new Comparable[max_id + 1]; -+ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { -+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); -+ } -+ } -+ -+ public void loadInTable(final Table, Comparable, StateHolder> table, -+ final Map, Comparable> this_map) { -+ final Set> combined = new HashSet<>(table.rowKeySet()); -+ combined.addAll(this_map.keySet()); -+ -+ this.index_table = this.create_table(combined); -+ -+ int max_id = -1; -+ for (final Property property : combined) { -+ final int id = lookup_vindex(property, this.index_table); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ this.value_table = new StateHolder[max_id + 1][]; -+ -+ final Map, Map, StateHolder>> map = table.rowMap(); -+ for (final Property property : map.keySet()) { -+ final Map, StateHolder> propertyMap = map.get(property); -+ -+ final int id = lookup_vindex(property, this.index_table); -+ final StateHolder[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()]; -+ -+ for (final Map.Entry, StateHolder> entry : propertyMap.entrySet()) { -+ if (entry.getValue() == null) { -+ // TODO what -+ continue; -+ } -+ -+ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue(); -+ } -+ } -+ -+ -+ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { -+ final Property property = entry.getKey(); -+ final int index = lookup_vindex(property, this.index_table); -+ -+ if (this.value_table[index] == null) { -+ this.value_table[index] = new StateHolder[property.getPossibleValues().size()]; -+ } -+ -+ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state; -+ } -+ } -+ -+ -+ protected long[] create_table(final Collection> collection) { -+ int max_id = -1; -+ for (final Property property : collection) { -+ final int id = property.getId(); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32) -+ -+ for (final Property property : collection) { -+ final int id = property.getId(); -+ -+ ret[id >>> 5] |= (1L << (id & 31)); -+ } -+ -+ int total = 0; -+ for (int i = 1, len = ret.length; i < len; ++i) { -+ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32; -+ } -+ -+ return ret; -+ } -+ -+ public Comparable get(final Property state) { -+ final Comparable[] table = this.this_table; -+ final int index = lookup_vindex(state, this.this_index_table); -+ -+ if (index < 0 || index >= table.length) { -+ return null; -+ } -+ return table[index]; -+ } -+ -+ public StateHolder get(final Property property, final Comparable with) { -+ final int withId = ((Property)property).getIdFor(with); -+ if (withId < 0) { -+ return null; -+ } -+ -+ final int index = lookup_vindex(property, this.index_table); -+ final StateHolder[][] table = this.value_table; -+ if (index < 0 || index >= table.length) { -+ return null; -+ } -+ -+ final StateHolder[] values = table[index]; -+ -+ if (withId >= values.length) { -+ return null; -+ } -+ -+ return values[withId]; -+ } -+ -+ protected static int lookup_vindex(final Property property, final long[] index_table) { -+ final int id = property.getId(); -+ final long bitset_mask = (1L << (id & 31)); -+ final long lower_mask = bitset_mask - 1; -+ final int index = id >>> 5; -+ if (index >= index_table.length) { -+ return -1; -+ } -+ final long index_value = index_table[index]; -+ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain -+ -+ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id -+ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0, -+ // otherwise it comes out as -1. -+ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -index ab712fd29b316e1235645bacaa79aa0a64d0bc00..340d0648fcf9b9749c4daa1c25a226b947707c3d 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -@@ -39,11 +39,13 @@ public abstract class StateHolder { - private final ImmutableMap, Comparable> values; - private Table, Comparable, S> neighbours; - protected final MapCodec propertiesCodec; -+ protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup - - protected StateHolder(O owner, ImmutableMap, Comparable> entries, MapCodec codec) { - this.owner = owner; - this.values = entries; - this.propertiesCodec = codec; -+ this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup - } - - public > S cycle(Property property) { -@@ -84,11 +86,11 @@ public abstract class StateHolder { - } - - public > boolean hasProperty(Property property) { -- return this.values.containsKey(property); -+ return this.optimisedTable.get(property) != null; // Paper - optimise state lookup - } - - public > T getValue(Property property) { -- Comparable comparable = this.values.get(property); -+ Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup - if (comparable == null) { - throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); - } else { -@@ -97,24 +99,18 @@ public abstract class StateHolder { - } - - public > Optional getOptionalValue(Property property) { -- Comparable comparable = this.values.get(property); -+ Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup - return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable)); - } - - public , V extends T> S setValue(Property property, V value) { -- Comparable comparable = this.values.get(property); -- if (comparable == null) { -- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); -- } else if (comparable == value) { -- return (S)this; -- } else { -- S object = this.neighbours.get(property, value); -- if (object == null) { -- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); -- } else { -- return object; -- } -+ // Paper start - optimise state lookup -+ final S ret = (S)this.optimisedTable.get(property, value); -+ if (ret == null) { -+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); - } -+ return ret; -+ // Paper end - optimise state lookup - } - - public void populateNeighbours(Map, Comparable>, S> states) { -@@ -133,7 +129,7 @@ public abstract class StateHolder { - } - } - -- this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); -+ this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -index ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..233215280f8494dbc33a2fd0b14e37e59f1cb643 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -@@ -7,6 +7,13 @@ import java.util.Optional; - public class BooleanProperty extends Property { - private final ImmutableSet values = ImmutableSet.of(true, false); - -+ // Paper start - optimise iblockdata state lookup -+ @Override -+ public final int getIdFor(final Boolean value) { -+ return value.booleanValue() ? 1 : 0; -+ } -+ // Paper end - optimise iblockdata state lookup -+ - protected BooleanProperty(String name) { - super(name, Boolean.class); - } -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -index 0bca0f971dac994bd8b6ecd87e8b33e26c0f18f9..edd3c745efb40ee79a1393199c7a27ddaa2f8026 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -@@ -15,6 +15,15 @@ public class EnumProperty & StringRepresentable> extends Prope - private final ImmutableSet values; - private final Map names = Maps.newHashMap(); - -+ // Paper start - optimise iblockdata state lookup -+ private int[] idLookupTable; -+ -+ @Override -+ public final int getIdFor(final T value) { -+ return this.idLookupTable[value.ordinal()]; -+ } -+ // Paper end - optimise iblockdata state lookup -+ - protected EnumProperty(String name, Class type, Collection values) { - super(name, type); - this.values = ImmutableSet.copyOf(values); -@@ -28,6 +37,14 @@ public class EnumProperty & StringRepresentable> extends Prope - this.names.put(string, enum_); - } - -+ // Paper start - optimise iblockdata state lookup -+ int id = 0; -+ this.idLookupTable = new int[type.getEnumConstants().length]; -+ java.util.Arrays.fill(this.idLookupTable, -1); -+ for (final T value : this.getPossibleValues()) { -+ this.idLookupTable[value.ordinal()] = id++; -+ } -+ // Paper end - optimise iblockdata state lookup - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -index bdbe0362e49e73c05237f9f3143230e0b03e494e..8eb20ea852a8e89c431fea55a7b60833a6c8104f 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -@@ -11,6 +11,16 @@ public class IntegerProperty extends Property { - public final int min; - public final int max; - -+ // Paper start - optimise iblockdata state lookup -+ @Override -+ public final int getIdFor(final Integer value) { -+ final int val = value.intValue(); -+ final int ret = val - this.min; -+ -+ return ret | ((this.max - ret) >> 31); -+ } -+ // Paper end - optimise iblockdata state lookup -+ - protected IntegerProperty(String name, int min, int max) { - super(name, Integer.class); - if (min < 0) { -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -index a37424bbc6bee02354abaa793aa0865c556c6bbe..f923593bd336dd1a950ba61603d53edb3c9703eb 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -@@ -22,6 +22,17 @@ public abstract class Property> { - }, this::getName); - private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); - -+ // Paper start - optimise iblockdata state lookup -+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); -+ private final int id = ID_GENERATOR.getAndIncrement(); -+ -+ public final int getId() { -+ return this.id; -+ } -+ -+ public abstract int getIdFor(final T value); -+ // Paper end - optimise state lookup -+ - protected Property(String name, Class type) { - this.clazz = type; - this.name = name; diff --git a/patches/server/0739-Detail-more-information-in-watchdog-dumps.patch b/patches/server/0739-Detail-more-information-in-watchdog-dumps.patch new file mode 100644 index 0000000000..0fc0b9475e --- /dev/null +++ b/patches/server/0739-Detail-more-information-in-watchdog-dumps.patch @@ -0,0 +1,297 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 26 Mar 2020 21:59:32 -0700 +Subject: [PATCH] Detail more information in watchdog dumps + +- Dump position, world, velocity, and uuid for currently ticking entities +- Dump player name, player uuid, position, and world for packet handling + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 057a0be81b12bd8a4ac71106dc8ada91bd4c9bfd..98443474d1881bbeee8f249b89e44a0f84261be7 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -475,9 +475,15 @@ public class Connection extends SimpleChannelInboundHandler> { + PacketListener packetlistener = this.packetListener; + + if (packetlistener instanceof TickablePacketListener) { ++ // Paper start - detailed watchdog information ++ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); ++ try { // Paper end - detailed watchdog information + TickablePacketListener tickablepacketlistener = (TickablePacketListener) packetlistener; + + tickablepacketlistener.tick(); ++ } finally { // Paper start - detailed watchdog information ++ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); ++ } // Paper start - detailed watchdog information + } + + if (!this.isConnected() && !this.disconnectionHandled) { +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index acfa1907bfc9c29d261cfccc00d65bad9ad1a002..d6f3869f5725c7f081efb7f486f74dbb99d4d005 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -15,6 +15,24 @@ public class PacketUtils { + + private static final Logger LOGGER = LogUtils.getLogger(); + ++ // Paper start - detailed watchdog information ++ public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); ++ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); ++ ++ public static long getTotalProcessedPackets() { ++ return totalMainThreadPacketsProcessed.get(); ++ } ++ ++ public static java.util.List getCurrentPacketProcessors() { ++ java.util.List ret = new java.util.ArrayList<>(4); ++ for (PacketListener listener : packetProcessing) { ++ ret.add(listener); ++ } ++ ++ return ret; ++ } ++ // Paper end - detailed watchdog information ++ + public PacketUtils() {} + + public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { +@@ -24,6 +42,8 @@ public class PacketUtils { + public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { + if (!engine.isSameThread()) { + engine.executeIfPossible(() -> { ++ packetProcessing.push(listener); // Paper - detailed watchdog information ++ try { // Paper - detailed watchdog information + if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 + if (listener.getConnection().isConnected()) { + co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings +@@ -43,6 +63,12 @@ public class PacketUtils { + } else { + PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); + } ++ // Paper start - detailed watchdog information ++ } finally { ++ totalMainThreadPacketsProcessed.getAndIncrement(); ++ packetProcessing.pop(); ++ } ++ // Paper end - detailed watchdog information + + }); + throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 7324f2ad437a15c42f84ba2deeb58861c0552209..92aafd475d58d1ef2e2736d9363c4b1010724fd7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1006,7 +1006,26 @@ public class ServerLevel extends Level implements WorldGenLevel { + + } + ++ // Paper start - log detailed entity tick information ++ // TODO replace with varhandle ++ static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); ++ ++ public static List getCurrentlyTickingEntities() { ++ Entity ticking = currentlyTickingEntity.get(); ++ List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); ++ ++ return ret; ++ } ++ // Paper end - log detailed entity tick information ++ + public void tickNonPassenger(Entity entity) { ++ // Paper start - log detailed entity tick information ++ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); ++ try { ++ if (currentlyTickingEntity.get() == null) { ++ currentlyTickingEntity.lazySet(entity); ++ } ++ // Paper end - log detailed entity tick information + ++TimingHistory.entityTicks; // Paper - timings + // Spigot start + co.aikar.timings.Timing timer; // Paper +@@ -1046,7 +1065,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + this.tickPassenger(entity, entity1); + } + // } finally { timer.stopTiming(); } // Paper - timings - move up +- ++ // Paper start - log detailed entity tick information ++ } finally { ++ if (currentlyTickingEntity.get() == entity) { ++ currentlyTickingEntity.lazySet(null); ++ } ++ } ++ // Paper end - log detailed entity tick information + } + + private void tickPassenger(Entity vehicle, Entity passenger) { +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f8458173dd717ca5fd9d265dcdaee0f0ef1a833c..51239bc47d6d2cfd8d345fb67fe0d92fabe53209 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -971,7 +971,42 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return this.onGround; + } + ++ // Paper start - detailed watchdog information ++ public final Object posLock = new Object(); // Paper - log detailed entity tick information ++ ++ private Vec3 moveVector; ++ private double moveStartX; ++ private double moveStartY; ++ private double moveStartZ; ++ ++ public final Vec3 getMoveVector() { ++ return this.moveVector; ++ } ++ ++ public final double getMoveStartX() { ++ return this.moveStartX; ++ } ++ ++ public final double getMoveStartY() { ++ return this.moveStartY; ++ } ++ ++ public final double getMoveStartZ() { ++ return this.moveStartZ; ++ } ++ // Paper end - detailed watchdog information ++ + public void move(MoverType movementType, Vec3 movement) { ++ // Paper start - detailed watchdog information ++ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); ++ synchronized (this.posLock) { ++ this.moveStartX = this.getX(); ++ this.moveStartY = this.getY(); ++ this.moveStartZ = this.getZ(); ++ this.moveVector = movement; ++ } ++ try { ++ // Paper end - detailed watchdog information + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -1144,6 +1179,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.level.getProfiler().pop(); + } + } ++ // Paper start - detailed watchdog information ++ } finally { ++ synchronized (this.posLock) { // Paper ++ this.moveVector = null; ++ } // Paper ++ } ++ // Paper end - detailed watchdog information + } + + protected boolean isHorizontalCollisionMinor(Vec3 adjustedMovement) { +@@ -3962,7 +4004,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void setDeltaMovement(Vec3 velocity) { ++ synchronized (this.posLock) { // Paper + this.deltaMovement = velocity; ++ } // Paper + } + + public void setDeltaMovement(double x, double y, double z) { +@@ -4038,7 +4082,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + // Paper end - fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { ++ synchronized (this.posLock) { // Paper + this.position = new Vec3(x, y, z); ++ } // Paper + int i = Mth.floor(x); + int j = Mth.floor(y); + int k = Mth.floor(z); +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 7823e97add506d42161fd86f830e4d988b2113d8..d568fc92d03c313a782796cc720a1ebb1a5ad8be 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -22,6 +22,78 @@ public class WatchdogThread extends Thread + private volatile long lastTick; + private volatile boolean stopping; + ++ // Paper start - log detailed tick information ++ private void dumpEntity(net.minecraft.world.entity.Entity entity) { ++ Logger log = Bukkit.getServer().getLogger(); ++ double posX, posY, posZ; ++ net.minecraft.world.phys.Vec3 mot; ++ double moveStartX, moveStartY, moveStartZ; ++ net.minecraft.world.phys.Vec3 moveVec; ++ synchronized (entity.posLock) { ++ posX = entity.getX(); ++ posY = entity.getY(); ++ posZ = entity.getZ(); ++ mot = entity.getDeltaMovement(); ++ moveStartX = entity.getMoveStartX(); ++ moveStartY = entity.getMoveStartY(); ++ moveStartZ = entity.getMoveStartZ(); ++ moveVec = entity.getMoveVector(); ++ } ++ ++ String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); ++ java.util.UUID entityUUID = entity.getUUID(); ++ net.minecraft.world.level.Level world = entity.level; ++ ++ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); ++ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); ++ log.log(Level.SEVERE, "Entity UUID: " + entityUUID); ++ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); ++ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); ++ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); ++ if (moveVec != null) { ++ log.log(Level.SEVERE, "Move call information: "); ++ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); ++ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); ++ } ++ } ++ ++ private void dumpTickingInfo() { ++ Logger log = Bukkit.getServer().getLogger(); ++ ++ // ticking entities ++ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { ++ this.dumpEntity(entity); ++ net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); ++ if (vehicle != null) { ++ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); ++ this.dumpEntity(vehicle); ++ } ++ } ++ ++ // packet processors ++ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) { ++ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) { ++ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player; ++ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets(); ++ if (player == null) { ++ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } else { ++ this.dumpEntity(player); ++ net.minecraft.world.entity.Entity vehicle = player.getVehicle(); ++ if (vehicle != null) { ++ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); ++ this.dumpEntity(vehicle); ++ } ++ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); ++ } ++ } else { ++ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); ++ } ++ } ++ } ++ // Paper end - log detailed tick information ++ + private WatchdogThread(long timeoutTime, boolean restart) + { + super( "Paper Watchdog Thread" ); +@@ -120,6 +192,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 + com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper ++ this.dumpTickingInfo(); // Paper - log detailed tick information + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // diff --git a/patches/server/0740-Detail-more-information-in-watchdog-dumps.patch b/patches/server/0740-Detail-more-information-in-watchdog-dumps.patch deleted file mode 100644 index 7b40f83dd9..0000000000 --- a/patches/server/0740-Detail-more-information-in-watchdog-dumps.patch +++ /dev/null @@ -1,297 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 26 Mar 2020 21:59:32 -0700 -Subject: [PATCH] Detail more information in watchdog dumps - -- Dump position, world, velocity, and uuid for currently ticking entities -- Dump player name, player uuid, position, and world for packet handling - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 057a0be81b12bd8a4ac71106dc8ada91bd4c9bfd..98443474d1881bbeee8f249b89e44a0f84261be7 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -475,9 +475,15 @@ public class Connection extends SimpleChannelInboundHandler> { - PacketListener packetlistener = this.packetListener; - - if (packetlistener instanceof TickablePacketListener) { -+ // Paper start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); -+ try { // Paper end - detailed watchdog information - TickablePacketListener tickablepacketlistener = (TickablePacketListener) packetlistener; - - tickablepacketlistener.tick(); -+ } finally { // Paper start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); -+ } // Paper start - detailed watchdog information - } - - if (!this.isConnected() && !this.disconnectionHandled) { -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index acfa1907bfc9c29d261cfccc00d65bad9ad1a002..d6f3869f5725c7f081efb7f486f74dbb99d4d005 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -15,6 +15,24 @@ public class PacketUtils { - - private static final Logger LOGGER = LogUtils.getLogger(); - -+ // Paper start - detailed watchdog information -+ public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); -+ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); -+ -+ public static long getTotalProcessedPackets() { -+ return totalMainThreadPacketsProcessed.get(); -+ } -+ -+ public static java.util.List getCurrentPacketProcessors() { -+ java.util.List ret = new java.util.ArrayList<>(4); -+ for (PacketListener listener : packetProcessing) { -+ ret.add(listener); -+ } -+ -+ return ret; -+ } -+ // Paper end - detailed watchdog information -+ - public PacketUtils() {} - - public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { -@@ -24,6 +42,8 @@ public class PacketUtils { - public static void ensureRunningOnSameThread(Packet packet, T listener, BlockableEventLoop engine) throws RunningOnDifferentThreadException { - if (!engine.isSameThread()) { - engine.executeIfPossible(() -> { -+ packetProcessing.push(listener); // Paper - detailed watchdog information -+ try { // Paper - detailed watchdog information - if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 - if (listener.getConnection().isConnected()) { - co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings -@@ -43,6 +63,12 @@ public class PacketUtils { - } else { - PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); - } -+ // Paper start - detailed watchdog information -+ } finally { -+ totalMainThreadPacketsProcessed.getAndIncrement(); -+ packetProcessing.pop(); -+ } -+ // Paper end - detailed watchdog information - - }); - throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7324f2ad437a15c42f84ba2deeb58861c0552209..92aafd475d58d1ef2e2736d9363c4b1010724fd7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1006,7 +1006,26 @@ public class ServerLevel extends Level implements WorldGenLevel { - - } - -+ // Paper start - log detailed entity tick information -+ // TODO replace with varhandle -+ static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); -+ -+ public static List getCurrentlyTickingEntities() { -+ Entity ticking = currentlyTickingEntity.get(); -+ List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); -+ -+ return ret; -+ } -+ // Paper end - log detailed entity tick information -+ - public void tickNonPassenger(Entity entity) { -+ // Paper start - log detailed entity tick information -+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); -+ try { -+ if (currentlyTickingEntity.get() == null) { -+ currentlyTickingEntity.lazySet(entity); -+ } -+ // Paper end - log detailed entity tick information - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper -@@ -1046,7 +1065,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - this.tickPassenger(entity, entity1); - } - // } finally { timer.stopTiming(); } // Paper - timings - move up -- -+ // Paper start - log detailed entity tick information -+ } finally { -+ if (currentlyTickingEntity.get() == entity) { -+ currentlyTickingEntity.lazySet(null); -+ } -+ } -+ // Paper end - log detailed entity tick information - } - - private void tickPassenger(Entity vehicle, Entity passenger) { -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 9567822f314cd3978ed63bb867e57b610d76228e..37123198bdf0188f59f289a31570663938fdc3c1 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -971,7 +971,42 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return this.onGround; - } - -+ // Paper start - detailed watchdog information -+ public final Object posLock = new Object(); // Paper - log detailed entity tick information -+ -+ private Vec3 moveVector; -+ private double moveStartX; -+ private double moveStartY; -+ private double moveStartZ; -+ -+ public final Vec3 getMoveVector() { -+ return this.moveVector; -+ } -+ -+ public final double getMoveStartX() { -+ return this.moveStartX; -+ } -+ -+ public final double getMoveStartY() { -+ return this.moveStartY; -+ } -+ -+ public final double getMoveStartZ() { -+ return this.moveStartZ; -+ } -+ // Paper end - detailed watchdog information -+ - public void move(MoverType movementType, Vec3 movement) { -+ // Paper start - detailed watchdog information -+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); -+ synchronized (this.posLock) { -+ this.moveStartX = this.getX(); -+ this.moveStartY = this.getY(); -+ this.moveStartZ = this.getZ(); -+ this.moveVector = movement; -+ } -+ try { -+ // Paper end - detailed watchdog information - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -1144,6 +1179,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.level.getProfiler().pop(); - } - } -+ // Paper start - detailed watchdog information -+ } finally { -+ synchronized (this.posLock) { // Paper -+ this.moveVector = null; -+ } // Paper -+ } -+ // Paper end - detailed watchdog information - } - - protected boolean isHorizontalCollisionMinor(Vec3 adjustedMovement) { -@@ -3948,7 +3990,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public void setDeltaMovement(Vec3 velocity) { -+ synchronized (this.posLock) { // Paper - this.deltaMovement = velocity; -+ } // Paper - } - - public void setDeltaMovement(double x, double y, double z) { -@@ -4024,7 +4068,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - // Paper end - fix MC-4 - if (this.position.x != x || this.position.y != y || this.position.z != z) { -+ synchronized (this.posLock) { // Paper - this.position = new Vec3(x, y, z); -+ } // Paper - int i = Mth.floor(x); - int j = Mth.floor(y); - int k = Mth.floor(z); -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 7823e97add506d42161fd86f830e4d988b2113d8..d568fc92d03c313a782796cc720a1ebb1a5ad8be 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -22,6 +22,78 @@ public class WatchdogThread extends Thread - private volatile long lastTick; - private volatile boolean stopping; - -+ // Paper start - log detailed tick information -+ private void dumpEntity(net.minecraft.world.entity.Entity entity) { -+ Logger log = Bukkit.getServer().getLogger(); -+ double posX, posY, posZ; -+ net.minecraft.world.phys.Vec3 mot; -+ double moveStartX, moveStartY, moveStartZ; -+ net.minecraft.world.phys.Vec3 moveVec; -+ synchronized (entity.posLock) { -+ posX = entity.getX(); -+ posY = entity.getY(); -+ posZ = entity.getZ(); -+ mot = entity.getDeltaMovement(); -+ moveStartX = entity.getMoveStartX(); -+ moveStartY = entity.getMoveStartY(); -+ moveStartZ = entity.getMoveStartZ(); -+ moveVec = entity.getMoveVector(); -+ } -+ -+ String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); -+ java.util.UUID entityUUID = entity.getUUID(); -+ net.minecraft.world.level.Level world = entity.level; -+ -+ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); -+ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); -+ log.log(Level.SEVERE, "Entity UUID: " + entityUUID); -+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); -+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); -+ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); -+ if (moveVec != null) { -+ log.log(Level.SEVERE, "Move call information: "); -+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); -+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); -+ } -+ } -+ -+ private void dumpTickingInfo() { -+ Logger log = Bukkit.getServer().getLogger(); -+ -+ // ticking entities -+ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { -+ this.dumpEntity(entity); -+ net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ } -+ -+ // packet processors -+ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) { -+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) { -+ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player; -+ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets(); -+ if (player == null) { -+ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } else { -+ this.dumpEntity(player); -+ net.minecraft.world.entity.Entity vehicle = player.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } -+ } else { -+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); -+ } -+ } -+ } -+ // Paper end - log detailed tick information -+ - private WatchdogThread(long timeoutTime, boolean restart) - { - super( "Paper Watchdog Thread" ); -@@ -120,6 +192,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 - com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper -+ this.dumpTickingInfo(); // Paper - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/patches/server/0740-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0740-Manually-inline-methods-in-BlockPosition.patch new file mode 100644 index 0000000000..0cab0f9b56 --- /dev/null +++ b/patches/server/0740-Manually-inline-methods-in-BlockPosition.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Jul 2020 22:48:48 -0700 +Subject: [PATCH] Manually inline methods in BlockPosition + + +diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java +index e68cd32a7db88f29d3224b0908119232ab3cf71a..153451ecd5b3c8e8ecb2d5ec91ccd582d4300899 100644 +--- a/src/main/java/net/minecraft/core/BlockPos.java ++++ b/src/main/java/net/minecraft/core/BlockPos.java +@@ -484,9 +484,9 @@ public class BlockPos extends Vec3i { + } + + public BlockPos.MutableBlockPos set(int x, int y, int z) { +- this.setX(x); +- this.setY(y); +- this.setZ(z); ++ this.x = x; // Paper - force inline ++ this.y = y; // Paper - force inline ++ this.z = z; // Paper - force inline + return this; + } + +@@ -550,19 +550,19 @@ public class BlockPos extends Vec3i { + // Paper start - comment out useless overrides @Override - TODO figure out why this is suddenly important to keep + @Override + public BlockPos.MutableBlockPos setX(int i) { +- super.setX(i); ++ this.x = i; // Paper + return this; + } + + @Override + public BlockPos.MutableBlockPos setY(int i) { +- super.setY(i); ++ this.y = i; // Paper + return this; + } + + @Override + public BlockPos.MutableBlockPos setZ(int i) { +- super.setZ(i); ++ this.z = i; // Paper + return this; + } + // Paper end +diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java +index 73b3c5f5c037fbe3f588f8f4b0113bef283632a4..c06c7da7de84bc764ea7b9612b3dc2303b49b22e 100644 +--- a/src/main/java/net/minecraft/core/Vec3i.java ++++ b/src/main/java/net/minecraft/core/Vec3i.java +@@ -19,9 +19,9 @@ public class Vec3i implements Comparable { + return IntStream.of(vec3i.getX(), vec3i.getY(), vec3i.getZ()); + }); + public static final Vec3i ZERO = new Vec3i(0, 0, 0); +- private int x; +- private int y; +- private int z; ++ protected int x; // Paper - protected ++ protected int y; // Paper - protected ++ protected int z; // Paper - protected + + private static Function> checkOffsetAxes(int maxAbsValue) { + return (vec) -> { diff --git a/patches/server/0741-Distance-manager-tick-timings.patch b/patches/server/0741-Distance-manager-tick-timings.patch new file mode 100644 index 0000000000..dd5f5f23b7 --- /dev/null +++ b/patches/server/0741-Distance-manager-tick-timings.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 18 Jul 2020 16:03:57 -0700 +Subject: [PATCH] Distance manager tick timings + +Recently this has been taking up more time, so add a timings to +really figure out how much. + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 5ec241d49ff5e3a161a39006f05823a5de847c5e..435b3b6d05e00803386d123c66f961c97da83d40 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -45,6 +45,7 @@ public final class MinecraftTimings { + + public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); ++ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager + + public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 1510e1fcfbbb2ccbaf57dd274bed05afc85eeeb0..98e279e5330b45d0587d1afebddaf876e9802e33 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -647,6 +647,7 @@ public class ServerChunkCache extends ChunkSource { + public boolean runDistanceManagerUpdates() { + if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority + if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper ++ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager + boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); + boolean flag1 = this.chunkMap.promoteChunkMap(); + +@@ -656,6 +657,7 @@ public class ServerChunkCache extends ChunkSource { + this.clearCache(); + return true; + } ++ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager + } + + // Paper start diff --git a/patches/server/0741-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0741-Manually-inline-methods-in-BlockPosition.patch deleted file mode 100644 index 0cab0f9b56..0000000000 --- a/patches/server/0741-Manually-inline-methods-in-BlockPosition.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 6 Jul 2020 22:48:48 -0700 -Subject: [PATCH] Manually inline methods in BlockPosition - - -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index e68cd32a7db88f29d3224b0908119232ab3cf71a..153451ecd5b3c8e8ecb2d5ec91ccd582d4300899 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -484,9 +484,9 @@ public class BlockPos extends Vec3i { - } - - public BlockPos.MutableBlockPos set(int x, int y, int z) { -- this.setX(x); -- this.setY(y); -- this.setZ(z); -+ this.x = x; // Paper - force inline -+ this.y = y; // Paper - force inline -+ this.z = z; // Paper - force inline - return this; - } - -@@ -550,19 +550,19 @@ public class BlockPos extends Vec3i { - // Paper start - comment out useless overrides @Override - TODO figure out why this is suddenly important to keep - @Override - public BlockPos.MutableBlockPos setX(int i) { -- super.setX(i); -+ this.x = i; // Paper - return this; - } - - @Override - public BlockPos.MutableBlockPos setY(int i) { -- super.setY(i); -+ this.y = i; // Paper - return this; - } - - @Override - public BlockPos.MutableBlockPos setZ(int i) { -- super.setZ(i); -+ this.z = i; // Paper - return this; - } - // Paper end -diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java -index 73b3c5f5c037fbe3f588f8f4b0113bef283632a4..c06c7da7de84bc764ea7b9612b3dc2303b49b22e 100644 ---- a/src/main/java/net/minecraft/core/Vec3i.java -+++ b/src/main/java/net/minecraft/core/Vec3i.java -@@ -19,9 +19,9 @@ public class Vec3i implements Comparable { - return IntStream.of(vec3i.getX(), vec3i.getY(), vec3i.getZ()); - }); - public static final Vec3i ZERO = new Vec3i(0, 0, 0); -- private int x; -- private int y; -- private int z; -+ protected int x; // Paper - protected -+ protected int y; // Paper - protected -+ protected int z; // Paper - protected - - private static Function> checkOffsetAxes(int maxAbsValue) { - return (vec) -> { diff --git a/patches/server/0742-Distance-manager-tick-timings.patch b/patches/server/0742-Distance-manager-tick-timings.patch deleted file mode 100644 index dd5f5f23b7..0000000000 --- a/patches/server/0742-Distance-manager-tick-timings.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 18 Jul 2020 16:03:57 -0700 -Subject: [PATCH] Distance manager tick timings - -Recently this has been taking up more time, so add a timings to -really figure out how much. - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index 5ec241d49ff5e3a161a39006f05823a5de847c5e..435b3b6d05e00803386d123c66f961c97da83d40 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -45,6 +45,7 @@ public final class MinecraftTimings { - - public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); - public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); -+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager - - public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 1510e1fcfbbb2ccbaf57dd274bed05afc85eeeb0..98e279e5330b45d0587d1afebddaf876e9802e33 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -647,6 +647,7 @@ public class ServerChunkCache extends ChunkSource { - public boolean runDistanceManagerUpdates() { - if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority - if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper -+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager - boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); - boolean flag1 = this.chunkMap.promoteChunkMap(); - -@@ -656,6 +657,7 @@ public class ServerChunkCache extends ChunkSource { - this.clearCache(); - return true; - } -+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager - } - - // Paper start diff --git a/patches/server/0742-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/server/0742-Name-craft-scheduler-threads-according-to-the-plugin.patch new file mode 100644 index 0000000000..7d081ee195 --- /dev/null +++ b/patches/server/0742-Name-craft-scheduler-threads-according-to-the-plugin.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 19 Jul 2020 15:17:01 -0700 +Subject: [PATCH] Name craft scheduler threads according to the plugin using + them + +Provides quick access to culprits running far more threads than +they should be + +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +index 2f3e2a404f55f09ae4db8261e495275e31228034..6d66f83afbeb650b10669fd7eeb24a315951fa86 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +@@ -25,7 +25,10 @@ class CraftAsyncTask extends CraftTask { + @Override + public void run() { + final Thread thread = Thread.currentThread(); +- synchronized (this.workers) { ++ // Paper start - name threads according to running plugin ++ final String nameBefore = thread.getName(); ++ thread.setName(nameBefore + " - " + this.getOwner().getName()); ++ try { synchronized (this.workers) { // Paper end - name threads according to running plugin + if (getPeriod() == CraftTask.CANCEL) { + // Never continue running after cancelled. + // Checking this with the lock is important! +@@ -92,6 +95,7 @@ class CraftAsyncTask extends CraftTask { + } + } + } ++ } finally { thread.setName(nameBefore); } // Paper - name worker thread according + } + + LinkedList getWorkers() { diff --git a/patches/server/0743-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0743-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch new file mode 100644 index 0000000000..8f16f744ad --- /dev/null +++ b/patches/server/0743-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Sep 2020 16:10:49 -0700 +Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded + chunks + +Tux did some profiling some time ago and showed that the +previous getChunkAt method which had inlined logic for loaded +chunks did get inlined, but the standard CPS.getChunkAt +method was not inlined. + +Paper recently reverted this optimisation, so it's been reintroduced +here. + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 90b3ca30a5ab381acde66393eb1fed5be677e5c8..8eabdd921cc976d9a1b9d3e8c315eeb22de3a968 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -368,6 +368,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + @Override + public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline ++ // Paper start - make sure loaded chunks get the inlined variant of this function ++ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); ++ if (cps.mainThread == Thread.currentThread()) { ++ LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ); ++ if (ifLoaded != null) { ++ return ifLoaded; ++ } ++ } ++ // Paper end - make sure loaded chunks get the inlined variant of this function + return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump + } + diff --git a/patches/server/0743-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/server/0743-Name-craft-scheduler-threads-according-to-the-plugin.patch deleted file mode 100644 index 7d081ee195..0000000000 --- a/patches/server/0743-Name-craft-scheduler-threads-according-to-the-plugin.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 19 Jul 2020 15:17:01 -0700 -Subject: [PATCH] Name craft scheduler threads according to the plugin using - them - -Provides quick access to culprits running far more threads than -they should be - -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -index 2f3e2a404f55f09ae4db8261e495275e31228034..6d66f83afbeb650b10669fd7eeb24a315951fa86 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -@@ -25,7 +25,10 @@ class CraftAsyncTask extends CraftTask { - @Override - public void run() { - final Thread thread = Thread.currentThread(); -- synchronized (this.workers) { -+ // Paper start - name threads according to running plugin -+ final String nameBefore = thread.getName(); -+ thread.setName(nameBefore + " - " + this.getOwner().getName()); -+ try { synchronized (this.workers) { // Paper end - name threads according to running plugin - if (getPeriod() == CraftTask.CANCEL) { - // Never continue running after cancelled. - // Checking this with the lock is important! -@@ -92,6 +95,7 @@ class CraftAsyncTask extends CraftTask { - } - } - } -+ } finally { thread.setName(nameBefore); } // Paper - name worker thread according - } - - LinkedList getWorkers() { diff --git a/patches/server/0744-Add-packet-limiter-config.patch b/patches/server/0744-Add-packet-limiter-config.patch new file mode 100644 index 0000000000..8aa45e5f93 --- /dev/null +++ b/patches/server/0744-Add-packet-limiter-config.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 30 Oct 2020 22:37:16 -0700 +Subject: [PATCH] Add packet limiter config + +Example config: +packet-limiter: + kick-message: '&cSent too many packets' + limits: + all: + interval: 7.0 + max-packet-rate: 500.0 + ServerboundPlaceRecipePacket: + interval: 4.0 + max-packet-rate: 5.0 + action: DROP + +all section refers to all incoming packets, the action for all is +hard coded to KICK. + +For specific limits, the section name is the class's name, +and an action can be defined: DROP or KICK + +If interval or rate are less-than 0, the limit is ignored + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 98443474d1881bbeee8f249b89e44a0f84261be7..51217798bfd549483ce456b44d14089f35642c55 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -131,6 +131,22 @@ public class Connection extends SimpleChannelInboundHandler> { + } + } + // Paper end - allow controlled flushing ++ // Paper start - packet limiter ++ protected final Object PACKET_LIMIT_LOCK = new Object(); ++ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( ++ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9) ++ ) : null; ++ protected final java.util.Map>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); ++ ++ private boolean stopReadingPackets; ++ private void killForPacketSpam() { ++ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)); ++ })); ++ this.setReadOnly(); ++ this.stopReadingPackets = true; ++ } ++ // Paper end - packet limiter + + public Connection(PacketFlow side) { + this.receiving = side; +@@ -211,6 +227,45 @@ public class Connection extends SimpleChannelInboundHandler> { + + protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { + if (this.channel.isOpen()) { ++ // Paper start - packet limiter ++ if (this.stopReadingPackets) { ++ return; ++ } ++ if (this.allPacketCounts != null || ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) { ++ long time = System.nanoTime(); ++ synchronized (PACKET_LIMIT_LOCK) { ++ if (this.allPacketCounts != null) { ++ this.allPacketCounts.updateAndAdd(1, time); ++ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) { ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ ++ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { ++ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit = ++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check); ++ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) { ++ continue; ++ } ++ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { ++ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9)); ++ }); ++ counter.updateAndAdd(1, time); ++ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) { ++ switch (packetSpecificLimit.action()) { ++ case DROP: ++ return; ++ case KICK: ++ this.killForPacketSpam(); ++ return; ++ } ++ } ++ } ++ } ++ } ++ // Paper end - packet limiter + try { + Connection.genericsFtw(packet, this.packetListener); + } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { diff --git a/patches/server/0744-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0744-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch deleted file mode 100644 index 300dfa8887..0000000000 --- a/patches/server/0744-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Sep 2020 16:10:49 -0700 -Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded - chunks - -Tux did some profiling some time ago and showed that the -previous getChunkAt method which had inlined logic for loaded -chunks did get inlined, but the standard CPS.getChunkAt -method was not inlined. - -Paper recently reverted this optimisation, so it's been reintroduced -here. - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 7c4ec47829bb0818f489544f2b396852d42a35d3..e7487e0a21727666889cc2ec8841cca2b2c965d5 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -368,6 +368,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - @Override - public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline -+ // Paper start - make sure loaded chunks get the inlined variant of this function -+ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); -+ if (cps.mainThread == Thread.currentThread()) { -+ LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ } -+ // Paper end - make sure loaded chunks get the inlined variant of this function - return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump - } - diff --git a/patches/server/0745-Add-packet-limiter-config.patch b/patches/server/0745-Add-packet-limiter-config.patch deleted file mode 100644 index 8aa45e5f93..0000000000 --- a/patches/server/0745-Add-packet-limiter-config.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 30 Oct 2020 22:37:16 -0700 -Subject: [PATCH] Add packet limiter config - -Example config: -packet-limiter: - kick-message: '&cSent too many packets' - limits: - all: - interval: 7.0 - max-packet-rate: 500.0 - ServerboundPlaceRecipePacket: - interval: 4.0 - max-packet-rate: 5.0 - action: DROP - -all section refers to all incoming packets, the action for all is -hard coded to KICK. - -For specific limits, the section name is the class's name, -and an action can be defined: DROP or KICK - -If interval or rate are less-than 0, the limit is ignored - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 98443474d1881bbeee8f249b89e44a0f84261be7..51217798bfd549483ce456b44d14089f35642c55 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -131,6 +131,22 @@ public class Connection extends SimpleChannelInboundHandler> { - } - } - // Paper end - allow controlled flushing -+ // Paper start - packet limiter -+ protected final Object PACKET_LIMIT_LOCK = new Object(); -+ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter( -+ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9) -+ ) : null; -+ protected final java.util.Map>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); -+ -+ private boolean stopReadingPackets; -+ private void killForPacketSpam() { -+ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)); -+ })); -+ this.setReadOnly(); -+ this.stopReadingPackets = true; -+ } -+ // Paper end - packet limiter - - public Connection(PacketFlow side) { - this.receiving = side; -@@ -211,6 +227,45 @@ public class Connection extends SimpleChannelInboundHandler> { - - protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { - if (this.channel.isOpen()) { -+ // Paper start - packet limiter -+ if (this.stopReadingPackets) { -+ return; -+ } -+ if (this.allPacketCounts != null || -+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) { -+ long time = System.nanoTime(); -+ synchronized (PACKET_LIMIT_LOCK) { -+ if (this.allPacketCounts != null) { -+ this.allPacketCounts.updateAndAdd(1, time); -+ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) { -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ -+ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { -+ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit = -+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check); -+ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) { -+ continue; -+ } -+ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { -+ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9)); -+ }); -+ counter.updateAndAdd(1, time); -+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) { -+ switch (packetSpecificLimit.action()) { -+ case DROP: -+ return; -+ case KICK: -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ } -+ } -+ } -+ // Paper end - packet limiter - try { - Connection.genericsFtw(packet, this.packetListener); - } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { diff --git a/patches/server/0745-Use-correct-LevelStem-registry-when-loading-default-.patch b/patches/server/0745-Use-correct-LevelStem-registry-when-loading-default-.patch new file mode 100644 index 0000000000..acaf871233 --- /dev/null +++ b/patches/server/0745-Use-correct-LevelStem-registry-when-loading-default-.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 16 Oct 2021 17:38:35 -0700 +Subject: [PATCH] Use correct LevelStem registry when loading default + end/nether + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/resources/RegistryLoader.java b/src/main/java/net/minecraft/resources/RegistryLoader.java +index 8da1226a6c293abb038d10c7921a77ed71ad06cc..f958f0ae738a6fb26400e17e54c8d69e95268cdd 100644 +--- a/src/main/java/net/minecraft/resources/RegistryLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryLoader.java +@@ -46,6 +46,12 @@ public class RegistryLoader { + RegistryLoader.ReadCache readCache = this.readCache(registryRef); + DataResult> dataResult = readCache.values.get(entryKey); + if (dataResult != null) { ++ // Paper start - register in registry due to craftbukkit running this 3 times instead of once ++ if (registryRef == (ResourceKey) Registry.LEVEL_STEM_REGISTRY && dataResult.result().isPresent()) { ++ // OptionalInt.empty() because the LevelStem registry is only loaded from the resource manager, not the InMemory resource access ++ registry.registerOrOverride(java.util.OptionalInt.empty(), entryKey, dataResult.result().get().value(), dataResult.lifecycle()); ++ } ++ // Paper end + return dataResult; + } else { + Holder holder = registry.getOrCreateHolderOrThrow(entryKey); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 99073ea2757cd1c15b098d7cfaf8681702f04a19..857af8ed37c10723d6cd81d7f3c2ba6169021050 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -553,7 +553,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); +- LevelStem worlddimension = (LevelStem) iregistry.get(dimensionKey); ++ // Paper start - Use correct LevelStem registry ++ final LevelStem worlddimension; ++ if (dimensionKey == LevelStem.END || dimensionKey == LevelStem.NETHER) { ++ worlddimension = generatorsettings.dimensions().get(dimensionKey); ++ } else { ++ worlddimension = iregistry.get(dimensionKey); ++ } ++ // Paper end + + org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.typeHolder().value()); + if (biomeProvider == null && gen != null) { diff --git a/patches/server/0746-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0746-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch new file mode 100644 index 0000000000..7f656dff77 --- /dev/null +++ b/patches/server/0746-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 11 Apr 2021 02:58:48 -0700 +Subject: [PATCH] Don't read neighbour chunk data off disk when converting + chunks + +Lighting is purged on update anyways, so let's not add more +into the conversion process + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index 3010e092bd1337e5a6b99636bbd312d90e470acf..fee9a8e74bfcc94942991b56799debf67b551f43 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -51,6 +51,7 @@ public class ChunkStorage implements AutoCloseable { + + // CraftBukkit start + private boolean check(ServerChunkCache cps, int x, int z) { ++ if (true) return true; // Paper - this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" + ChunkPos pos = new ChunkPos(x, z); + if (cps != null) { + //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe diff --git a/patches/server/0746-Use-correct-LevelStem-registry-when-loading-default-.patch b/patches/server/0746-Use-correct-LevelStem-registry-when-loading-default-.patch deleted file mode 100644 index acaf871233..0000000000 --- a/patches/server/0746-Use-correct-LevelStem-registry-when-loading-default-.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 16 Oct 2021 17:38:35 -0700 -Subject: [PATCH] Use correct LevelStem registry when loading default - end/nether - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/resources/RegistryLoader.java b/src/main/java/net/minecraft/resources/RegistryLoader.java -index 8da1226a6c293abb038d10c7921a77ed71ad06cc..f958f0ae738a6fb26400e17e54c8d69e95268cdd 100644 ---- a/src/main/java/net/minecraft/resources/RegistryLoader.java -+++ b/src/main/java/net/minecraft/resources/RegistryLoader.java -@@ -46,6 +46,12 @@ public class RegistryLoader { - RegistryLoader.ReadCache readCache = this.readCache(registryRef); - DataResult> dataResult = readCache.values.get(entryKey); - if (dataResult != null) { -+ // Paper start - register in registry due to craftbukkit running this 3 times instead of once -+ if (registryRef == (ResourceKey) Registry.LEVEL_STEM_REGISTRY && dataResult.result().isPresent()) { -+ // OptionalInt.empty() because the LevelStem registry is only loaded from the resource manager, not the InMemory resource access -+ registry.registerOrOverride(java.util.OptionalInt.empty(), entryKey, dataResult.result().get().value(), dataResult.lifecycle()); -+ } -+ // Paper end - return dataResult; - } else { - Holder holder = registry.getOrCreateHolderOrThrow(entryKey); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 99073ea2757cd1c15b098d7cfaf8681702f04a19..857af8ed37c10723d6cd81d7f3c2ba6169021050 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -553,7 +553,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver)); -- LevelStem worlddimension = (LevelStem) iregistry.get(dimensionKey); -+ // Paper start - Use correct LevelStem registry -+ final LevelStem worlddimension; -+ if (dimensionKey == LevelStem.END || dimensionKey == LevelStem.NETHER) { -+ worlddimension = generatorsettings.dimensions().get(dimensionKey); -+ } else { -+ worlddimension = iregistry.get(dimensionKey); -+ } -+ // Paper end - - org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.typeHolder().value()); - if (biomeProvider == null && gen != null) { diff --git a/patches/server/0747-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/server/0747-Consolidate-flush-calls-for-entity-tracker-packets.patch new file mode 100644 index 0000000000..5b23522f42 --- /dev/null +++ b/patches/server/0747-Consolidate-flush-calls-for-entity-tracker-packets.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 4 Apr 2020 17:00:20 -0700 +Subject: [PATCH] Consolidate flush calls for entity tracker packets + +Most server packets seem to be sent from here, so try to avoid +expensive flush calls from them. + +This change was motivated due to local testing: + +- My server spawn has 130 cows in it (for testing a prev. patch) +- Try to let 200 players join spawn + +Without this change, I could only get 20 players on before they +all started timing out due to the load put on the Netty I/O threads. + +With this change I could get all 200 on at 0ms ping. + +(one of the primary issues is that my CPU is kinda trash, and having +4 extra threads at 100% is just too much for it). + +So in general this patch should reduce Netty I/O thread load. + +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 98e279e5330b45d0587d1afebddaf876e9802e33..4c82f17313e18c9dfd9b28653715b8a3242b826c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -893,7 +893,24 @@ public class ServerChunkCache extends ChunkSource { + this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing + gameprofilerfiller.pop(); + // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded ++ // Paper start - controlled flush for entity tracker packets ++ List disabledFlushes = new java.util.ArrayList<>(this.level.players.size()); ++ for (ServerPlayer player : this.level.players) { ++ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; ++ if (connection != null) { ++ connection.connection.disableAutomaticFlush(); ++ disabledFlushes.add(connection.connection); ++ } ++ } ++ try { // Paper end - controlled flush for entity tracker packets + this.chunkMap.tick(); ++ // Paper start - controlled flush for entity tracker packets ++ } finally { ++ for (net.minecraft.network.Connection networkManager : disabledFlushes) { ++ networkManager.enableAutomaticFlush(); ++ } ++ } ++ // Paper end - controlled flush for entity tracker packets + } + } + diff --git a/patches/server/0747-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0747-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch deleted file mode 100644 index 7f656dff77..0000000000 --- a/patches/server/0747-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 11 Apr 2021 02:58:48 -0700 -Subject: [PATCH] Don't read neighbour chunk data off disk when converting - chunks - -Lighting is purged on update anyways, so let's not add more -into the conversion process - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 3010e092bd1337e5a6b99636bbd312d90e470acf..fee9a8e74bfcc94942991b56799debf67b551f43 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -51,6 +51,7 @@ public class ChunkStorage implements AutoCloseable { - - // CraftBukkit start - private boolean check(ServerChunkCache cps, int x, int z) { -+ if (true) return true; // Paper - this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { - //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe diff --git a/patches/server/0748-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/server/0748-Consolidate-flush-calls-for-entity-tracker-packets.patch deleted file mode 100644 index 5b23522f42..0000000000 --- a/patches/server/0748-Consolidate-flush-calls-for-entity-tracker-packets.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 4 Apr 2020 17:00:20 -0700 -Subject: [PATCH] Consolidate flush calls for entity tracker packets - -Most server packets seem to be sent from here, so try to avoid -expensive flush calls from them. - -This change was motivated due to local testing: - -- My server spawn has 130 cows in it (for testing a prev. patch) -- Try to let 200 players join spawn - -Without this change, I could only get 20 players on before they -all started timing out due to the load put on the Netty I/O threads. - -With this change I could get all 200 on at 0ms ping. - -(one of the primary issues is that my CPU is kinda trash, and having -4 extra threads at 100% is just too much for it). - -So in general this patch should reduce Netty I/O thread load. - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 98e279e5330b45d0587d1afebddaf876e9802e33..4c82f17313e18c9dfd9b28653715b8a3242b826c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -893,7 +893,24 @@ public class ServerChunkCache extends ChunkSource { - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - gameprofilerfiller.pop(); - // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded -+ // Paper start - controlled flush for entity tracker packets -+ List disabledFlushes = new java.util.ArrayList<>(this.level.players.size()); -+ for (ServerPlayer player : this.level.players) { -+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; -+ if (connection != null) { -+ connection.connection.disableAutomaticFlush(); -+ disabledFlushes.add(connection.connection); -+ } -+ } -+ try { // Paper end - controlled flush for entity tracker packets - this.chunkMap.tick(); -+ // Paper start - controlled flush for entity tracker packets -+ } finally { -+ for (net.minecraft.network.Connection networkManager : disabledFlushes) { -+ networkManager.enableAutomaticFlush(); -+ } -+ } -+ // Paper end - controlled flush for entity tracker packets - } - } - diff --git a/patches/server/0748-Don-t-lookup-fluid-state-when-raytracing.patch b/patches/server/0748-Don-t-lookup-fluid-state-when-raytracing.patch new file mode 100644 index 0000000000..eef7de0e32 --- /dev/null +++ b/patches/server/0748-Don-t-lookup-fluid-state-when-raytracing.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 28 Aug 2020 12:33:47 -0700 +Subject: [PATCH] Don't lookup fluid state when raytracing + +Just use the iblockdata already retrieved, removes a getType call. + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index bca0838e40bc91d78e9b93df5318642d1c9f341e..9cf2f046d50a8a0e08189c9b4b5d2f323d1f790d 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -84,7 +84,7 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); + } + // Paper end +- FluidState fluid = this.getFluidState(blockposition); ++ FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); + VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); diff --git a/patches/server/0749-Don-t-lookup-fluid-state-when-raytracing.patch b/patches/server/0749-Don-t-lookup-fluid-state-when-raytracing.patch deleted file mode 100644 index eef7de0e32..0000000000 --- a/patches/server/0749-Don-t-lookup-fluid-state-when-raytracing.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 28 Aug 2020 12:33:47 -0700 -Subject: [PATCH] Don't lookup fluid state when raytracing - -Just use the iblockdata already retrieved, removes a getType call. - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index bca0838e40bc91d78e9b93df5318642d1c9f341e..9cf2f046d50a8a0e08189c9b4b5d2f323d1f790d 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -84,7 +84,7 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); - } - // Paper end -- FluidState fluid = this.getFluidState(blockposition); -+ FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); - VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); diff --git a/patches/server/0749-Time-scoreboard-search.patch b/patches/server/0749-Time-scoreboard-search.patch new file mode 100644 index 0000000000..d1b1fd45ef --- /dev/null +++ b/patches/server/0749-Time-scoreboard-search.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 21 Apr 2020 01:53:22 -0700 +Subject: [PATCH] Time scoreboard search + +Plugins leaking scoreboards will make this very expensive, +let server owners debug it easily + +diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java +index 435b3b6d05e00803386d123c66f961c97da83d40..9da5a6086323ff4c4fd62a035fa8f7efc3d92e38 100644 +--- a/src/main/java/co/aikar/timings/MinecraftTimings.java ++++ b/src/main/java/co/aikar/timings/MinecraftTimings.java +@@ -46,6 +46,7 @@ public final class MinecraftTimings { + public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); + public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); + public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager ++ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search + + public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +index 39e19bea16419b9cbe53016084b8bbf014dcb056..138407c2d4b0bc55ddb9aac5d2aa3edadda090fb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +@@ -113,9 +113,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { + + // CraftBukkit method + public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer consumer) { ++ // Paper start - add timings for scoreboard search ++ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily ++ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); ++ try { ++ // Paper end - add timings for scoreboard search + for (CraftScoreboard scoreboard : this.scoreboards) { + Scoreboard board = scoreboard.board; + board.forAllObjectives(criteria, name, (score) -> consumer.accept(score)); + } ++ } finally { // Paper start - add timings for scoreboard search ++ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); ++ } ++ // Paper end - add timings for scoreboard search + } + } diff --git a/patches/server/0750-Send-full-pos-packets-for-hard-colliding-entities.patch b/patches/server/0750-Send-full-pos-packets-for-hard-colliding-entities.patch new file mode 100644 index 0000000000..b63f55bd81 --- /dev/null +++ b/patches/server/0750-Send-full-pos-packets-for-hard-colliding-entities.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 16 Feb 2021 00:16:56 -0800 +Subject: [PATCH] Send full pos packets for hard colliding entities + +Prevent collision problems due to desync (i.e boats) + +Configurable under +`send-full-pos-for-hard-colliding-entities` + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index c84ec3b93f2783de7a2815f23a9f1de89c1ab109..cc418554b655ea4111631e4a1abf69776e150e7c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -164,7 +164,7 @@ public class ServerEntity { + long i1 = this.positionCodec.encodeZ(vec3d); + boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; + +- if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) { ++ if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync + if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) { + if (flag2) { + packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround()); diff --git a/patches/server/0750-Time-scoreboard-search.patch b/patches/server/0750-Time-scoreboard-search.patch deleted file mode 100644 index d1b1fd45ef..0000000000 --- a/patches/server/0750-Time-scoreboard-search.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 21 Apr 2020 01:53:22 -0700 -Subject: [PATCH] Time scoreboard search - -Plugins leaking scoreboards will make this very expensive, -let server owners debug it easily - -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index 435b3b6d05e00803386d123c66f961c97da83d40..9da5a6086323ff4c4fd62a035fa8f7efc3d92e38 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -46,6 +46,7 @@ public final class MinecraftTimings { - public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); - public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); - public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager -+ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search - - public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks"); - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -index 39e19bea16419b9cbe53016084b8bbf014dcb056..138407c2d4b0bc55ddb9aac5d2aa3edadda090fb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -@@ -113,9 +113,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { - - // CraftBukkit method - public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer consumer) { -+ // Paper start - add timings for scoreboard search -+ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); -+ try { -+ // Paper end - add timings for scoreboard search - for (CraftScoreboard scoreboard : this.scoreboards) { - Scoreboard board = scoreboard.board; - board.forAllObjectives(criteria, name, (score) -> consumer.accept(score)); - } -+ } finally { // Paper start - add timings for scoreboard search -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); -+ } -+ // Paper end - add timings for scoreboard search - } - } diff --git a/patches/server/0751-Do-not-run-raytrace-logic-for-AIR.patch b/patches/server/0751-Do-not-run-raytrace-logic-for-AIR.patch new file mode 100644 index 0000000000..baedf55a9d --- /dev/null +++ b/patches/server/0751-Do-not-run-raytrace-logic-for-AIR.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 7 Mar 2021 13:15:04 -0800 +Subject: [PATCH] Do not run raytrace logic for AIR + +Saves approx. 5% for the raytrace call, as most (expensive) +raytracing tends to go through air and returning early is an +easy win. The remaining problems with this function +are mostly with the block getting itself. + +diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java +index 9cf2f046d50a8a0e08189c9b4b5d2f323d1f790d..d1eefa6ef3e9abfe7af4d8310aa64465fa2d5463 100644 +--- a/src/main/java/net/minecraft/world/level/BlockGetter.java ++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java +@@ -84,6 +84,7 @@ public interface BlockGetter extends LevelHeightAccessor { + return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); + } + // Paper end ++ if (iblockdata.isAir()) return null; // Paper - optimise air cases + FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again + Vec3 vec3d = raytrace1.getFrom(); + Vec3 vec3d1 = raytrace1.getTo(); diff --git a/patches/server/0751-Send-full-pos-packets-for-hard-colliding-entities.patch b/patches/server/0751-Send-full-pos-packets-for-hard-colliding-entities.patch deleted file mode 100644 index b63f55bd81..0000000000 --- a/patches/server/0751-Send-full-pos-packets-for-hard-colliding-entities.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 16 Feb 2021 00:16:56 -0800 -Subject: [PATCH] Send full pos packets for hard colliding entities - -Prevent collision problems due to desync (i.e boats) - -Configurable under -`send-full-pos-for-hard-colliding-entities` - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index c84ec3b93f2783de7a2815f23a9f1de89c1ab109..cc418554b655ea4111631e4a1abf69776e150e7c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -164,7 +164,7 @@ public class ServerEntity { - long i1 = this.positionCodec.encodeZ(vec3d); - boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; - -- if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) { -+ if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync - if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) { - if (flag2) { - packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround()); diff --git a/patches/server/0752-Do-not-run-raytrace-logic-for-AIR.patch b/patches/server/0752-Do-not-run-raytrace-logic-for-AIR.patch deleted file mode 100644 index baedf55a9d..0000000000 --- a/patches/server/0752-Do-not-run-raytrace-logic-for-AIR.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 7 Mar 2021 13:15:04 -0800 -Subject: [PATCH] Do not run raytrace logic for AIR - -Saves approx. 5% for the raytrace call, as most (expensive) -raytracing tends to go through air and returning early is an -easy win. The remaining problems with this function -are mostly with the block getting itself. - -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index 9cf2f046d50a8a0e08189c9b4b5d2f323d1f790d..d1eefa6ef3e9abfe7af4d8310aa64465fa2d5463 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -84,6 +84,7 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); - } - // Paper end -+ if (iblockdata.isAir()) return null; // Paper - optimise air cases - FluidState fluid = iblockdata.getFluidState(); // Paper - don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); diff --git a/patches/server/0752-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0752-Oprimise-map-impl-for-tracked-players.patch new file mode 100644 index 0000000000..e1ee820045 --- /dev/null +++ b/patches/server/0752-Oprimise-map-impl-for-tracked-players.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 19 Feb 2021 22:51:52 -0800 +Subject: [PATCH] Oprimise map impl for tracked players + +Reference2BooleanOpenHashMap is going to have +better lookups than HashMap. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index e811c7d617b8c9cc684bc0a58a98d5ecfe11db02..f47e2d4a7343f3e3ee68f36688720cedc58861dc 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -110,6 +110,7 @@ import org.slf4j.Logger; + import org.bukkit.craftbukkit.generator.CustomChunkGenerator; + import org.bukkit.entity.Player; + // CraftBukkit end ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper + + public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { + +@@ -2212,7 +2213,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = Sets.newIdentityHashSet(); ++ public final Set seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl + + public TrackedEntity(Entity entity, int i, int j, boolean flag) { + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0753-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0753-Oprimise-map-impl-for-tracked-players.patch deleted file mode 100644 index e1ee820045..0000000000 --- a/patches/server/0753-Oprimise-map-impl-for-tracked-players.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 19 Feb 2021 22:51:52 -0800 -Subject: [PATCH] Oprimise map impl for tracked players - -Reference2BooleanOpenHashMap is going to have -better lookups than HashMap. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index e811c7d617b8c9cc684bc0a58a98d5ecfe11db02..f47e2d4a7343f3e3ee68f36688720cedc58861dc 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -110,6 +110,7 @@ import org.slf4j.Logger; - import org.bukkit.craftbukkit.generator.CustomChunkGenerator; - import org.bukkit.entity.Player; - // CraftBukkit end -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper - - public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { - -@@ -2212,7 +2213,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final Entity entity; - private final int range; - SectionPos lastSectionPos; -- public final Set seenBy = Sets.newIdentityHashSet(); -+ public final Set seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl - - public TrackedEntity(Entity entity, int i, int j, boolean flag) { - this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit diff --git a/patches/server/0753-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0753-Optimise-BlockSoil-nearby-water-lookup.patch new file mode 100644 index 0000000000..bb523f58c5 --- /dev/null +++ b/patches/server/0753-Optimise-BlockSoil-nearby-water-lookup.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 10 Jun 2021 14:36:00 -0700 +Subject: [PATCH] Optimise BlockSoil nearby water lookup + +Apparently the abstract block iteration was taking about +75% of the method call. + +diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java +index 94ea6b8986c8fe3aee220ef0c95b65c5cef21c72..d089887030ac7c7a79abca97134ba9291e244059 100644 +--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java +@@ -138,19 +138,27 @@ public class FarmBlock extends Block { + } + + private static boolean isNearWater(LevelReader world, BlockPos pos) { +- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); +- +- BlockPos blockposition1; +- +- do { +- if (!iterator.hasNext()) { +- return false; ++ // Paper start - remove abstract block iteration ++ int xOff = pos.getX(); ++ int yOff = pos.getY(); ++ int zOff = pos.getZ(); ++ ++ for (int dz = -4; dz <= 4; ++dz) { ++ int z = dz + zOff; ++ for (int dx = -4; dx <= 4; ++dx) { ++ int x = xOff + dx; ++ for (int dy = 0; dy <= 1; ++dy) { ++ int y = dy + yOff; ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); ++ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState(); ++ if (fluid.is(FluidTags.WATER)) { ++ return true; ++ } ++ } + } ++ } + +- blockposition1 = (BlockPos) iterator.next(); +- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER)); +- +- return true; ++ return false; + } + + @Override diff --git a/patches/server/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch b/patches/server/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch new file mode 100644 index 0000000000..bf79d6270e --- /dev/null +++ b/patches/server/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Jun 2021 22:47:17 -0700 +Subject: [PATCH] Allow removal/addition of entities to entity ticklist during + tick + +It really doesn't make any sense that we would iterate over removed +entities during tick. Sure - tick entity checks removed, but +does it check if the entity is in an entity ticking chunk? +No it doesn't. So, allowing removal while iteration +ENSURES only entities MARKED TO TICK are ticked. + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +index a176a886235494fdc722030a93658d361bf50f03..4cdfc433df67afcd455422e9baf56f167dd712ae 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +@@ -8,57 +8,42 @@ import javax.annotation.Nullable; + import net.minecraft.world.entity.Entity; + + public class EntityTickList { +- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); +- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); +- @Nullable +- private Int2ObjectMap iterated; ++ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? + + private void ensureActiveIsNotIterated() { +- if (this.iterated == this.active) { +- this.passive.clear(); +- +- for(Int2ObjectMap.Entry entry : Int2ObjectMaps.fastIterable(this.active)) { +- this.passive.put(entry.getIntKey(), entry.getValue()); +- } +- +- Int2ObjectMap int2ObjectMap = this.active; +- this.active = this.passive; +- this.passive = int2ObjectMap; +- } ++ // Paper - replace with better logic, do not delay removals + + } + + public void add(Entity entity) { + io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper + this.ensureActiveIsNotIterated(); +- this.active.put(entity.getId(), entity); ++ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public void remove(Entity entity) { + io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper + this.ensureActiveIsNotIterated(); +- this.active.remove(entity.getId()); ++ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public boolean contains(Entity entity) { +- return this.active.containsKey(entity.getId()); ++ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions + } + + public void forEach(Consumer action) { + io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper +- if (this.iterated != null) { +- throw new UnsupportedOperationException("Only one concurrent iteration supported"); +- } else { +- this.iterated = this.active; +- +- try { +- for(Entity entity : this.active.values()) { +- action.accept(entity); +- } +- } finally { +- this.iterated = null; ++ // Paper start - replace with better logic, do not delay removals/additions ++ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... ++ // (by dfl iterator() is configured to not iterate over new entries) ++ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); ++ try { ++ while (iterator.hasNext()) { ++ action.accept(iterator.next()); + } +- ++ } finally { ++ iterator.finishedIterating(); + } ++ // Paper end - replace with better logic, do not delay removals/additions + } + } diff --git a/patches/server/0754-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0754-Optimise-BlockSoil-nearby-water-lookup.patch deleted file mode 100644 index bb523f58c5..0000000000 --- a/patches/server/0754-Optimise-BlockSoil-nearby-water-lookup.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 10 Jun 2021 14:36:00 -0700 -Subject: [PATCH] Optimise BlockSoil nearby water lookup - -Apparently the abstract block iteration was taking about -75% of the method call. - -diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 94ea6b8986c8fe3aee220ef0c95b65c5cef21c72..d089887030ac7c7a79abca97134ba9291e244059 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -138,19 +138,27 @@ public class FarmBlock extends Block { - } - - private static boolean isNearWater(LevelReader world, BlockPos pos) { -- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); -- -- BlockPos blockposition1; -- -- do { -- if (!iterator.hasNext()) { -- return false; -+ // Paper start - remove abstract block iteration -+ int xOff = pos.getX(); -+ int yOff = pos.getY(); -+ int zOff = pos.getZ(); -+ -+ for (int dz = -4; dz <= 4; ++dz) { -+ int z = dz + zOff; -+ for (int dx = -4; dx <= 4; ++dx) { -+ int x = xOff + dx; -+ for (int dy = 0; dy <= 1; ++dy) { -+ int y = dy + yOff; -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); -+ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState(); -+ if (fluid.is(FluidTags.WATER)) { -+ return true; -+ } -+ } - } -+ } - -- blockposition1 = (BlockPos) iterator.next(); -- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER)); -- -- return true; -+ return false; - } - - @Override diff --git a/patches/server/0755-Allow-removal-addition-of-entities-to-entity-ticklis.patch b/patches/server/0755-Allow-removal-addition-of-entities-to-entity-ticklis.patch deleted file mode 100644 index bf79d6270e..0000000000 --- a/patches/server/0755-Allow-removal-addition-of-entities-to-entity-ticklis.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 19 Jun 2021 22:47:17 -0700 -Subject: [PATCH] Allow removal/addition of entities to entity ticklist during - tick - -It really doesn't make any sense that we would iterate over removed -entities during tick. Sure - tick entity checks removed, but -does it check if the entity is in an entity ticking chunk? -No it doesn't. So, allowing removal while iteration -ENSURES only entities MARKED TO TICK are ticked. - -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index a176a886235494fdc722030a93658d361bf50f03..4cdfc433df67afcd455422e9baf56f167dd712ae 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -8,57 +8,42 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); -- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); -- @Nullable -- private Int2ObjectMap iterated; -+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? - - private void ensureActiveIsNotIterated() { -- if (this.iterated == this.active) { -- this.passive.clear(); -- -- for(Int2ObjectMap.Entry entry : Int2ObjectMaps.fastIterable(this.active)) { -- this.passive.put(entry.getIntKey(), entry.getValue()); -- } -- -- Int2ObjectMap int2ObjectMap = this.active; -- this.active = this.passive; -- this.passive = int2ObjectMap; -- } -+ // Paper - replace with better logic, do not delay removals - - } - - public void add(Entity entity) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.put(entity.getId(), entity); -+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void remove(Entity entity) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper - this.ensureActiveIsNotIterated(); -- this.active.remove(entity.getId()); -+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public boolean contains(Entity entity) { -- return this.active.containsKey(entity.getId()); -+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions - } - - public void forEach(Consumer action) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper -- if (this.iterated != null) { -- throw new UnsupportedOperationException("Only one concurrent iteration supported"); -- } else { -- this.iterated = this.active; -- -- try { -- for(Entity entity : this.active.values()) { -- action.accept(entity); -- } -- } finally { -- this.iterated = null; -+ // Paper start - replace with better logic, do not delay removals/additions -+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... -+ // (by dfl iterator() is configured to not iterate over new entries) -+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); -+ try { -+ while (iterator.hasNext()) { -+ action.accept(iterator.next()); - } -- -+ } finally { -+ iterator.finishedIterating(); - } -+ // Paper end - replace with better logic, do not delay removals/additions - } - } diff --git a/patches/server/0755-Optimise-random-block-ticking.patch b/patches/server/0755-Optimise-random-block-ticking.patch new file mode 100644 index 0000000000..6573b51a15 --- /dev/null +++ b/patches/server/0755-Optimise-random-block-ticking.patch @@ -0,0 +1,449 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 20 Jun 2021 16:19:26 -0700 +Subject: [PATCH] Optimise random block ticking + +Massive performance improvement for random block ticking. +The performance increase comes from the fact that the vast +majority of attempted block ticks (~95% in my testing) fail +because the randomly selected block is not tickable. + +Now only tickable blocks are targeted, however this means that +the maximum number of block ticks occurs per chunk. However, +not all chunks are going to be targeted. The percent chance +of a chunk being targeted is based on how many tickable blocks +are in the chunk. +This means that while block ticks are spread out less, the +total number of blocks ticked per world tick remains the same. +Therefore, the chance of a random tickable block being ticked +remains the same. + +diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java +@@ -0,0 +1,65 @@ ++package io.papermc.paper.util.math; ++ ++import net.minecraft.util.RandomSource; ++import net.minecraft.world.level.levelgen.LegacyRandomSource; ++import net.minecraft.world.level.levelgen.PositionalRandomFactory; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class ThreadUnsafeRandom extends LegacyRandomSource { ++ ++ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. ++ private static final long multiplier = 0x5DEECE66DL; ++ private static final long addend = 0xBL; ++ private static final long mask = (1L << 48) - 1; ++ ++ private static long initialScramble(long seed) { ++ return (seed ^ multiplier) & mask; ++ } ++ ++ private long seed; ++ ++ public ThreadUnsafeRandom(long seed) { ++ super(seed); ++ } ++ ++ @Override ++ public RandomSource fork() { ++ return new ThreadUnsafeRandom(this.nextLong()); ++ } ++ ++ @Override ++ public PositionalRandomFactory forkPositional() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setSeed(long seed) { ++ // note: called by Random constructor ++ this.seed = initialScramble(seed); ++ } ++ ++ @Override ++ public int next(int bits) { ++ // avoid the expensive CAS logic used by superclass ++ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); ++ } ++ ++ // Taken from ++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ ++ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c ++ // Original license is public domain ++ public static int fastRandomBounded(final long randomInteger, final long limit) { ++ // randomInteger must be [0, pow(2, 32)) ++ // limit must be [0, pow(2, 32)) ++ return (int)((randomInteger * limit) >>> 32); ++ } ++ ++ @Override ++ public int nextInt(int bound) { ++ // yes this breaks random's spec ++ // however there's nothing that uses this class that relies on it ++ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 92aafd475d58d1ef2e2736d9363c4b1010724fd7..bcd1b2534af33e7a9d184e0ea4c9c0a4b58dacc8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -670,6 +670,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + entityplayer.stopSleepInBed(false, false); + }); + } ++ // Paper start - optimise random block ticking ++ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); ++ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); ++ // Paper end + + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + ChunkPos chunkcoordintpair = chunk.getPos(); +@@ -679,10 +683,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + gameprofilerfiller.push("thunder"); +- BlockPos blockposition; ++ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change + + if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - disable thunder +- blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); ++ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper + if (this.isRainingAt(blockposition)) { + DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); + boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper +@@ -706,64 +710,75 @@ public class ServerLevel extends Level implements WorldGenLevel { + + gameprofilerfiller.popPush("iceandsnow"); + if (!this.paperConfig().environment.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow +- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15)); +- BlockPos blockposition1 = blockposition.below(); ++ // Paper start - optimise chunk ticking ++ this.getRandomBlockPosition(j, 0, k, 15, blockposition); ++ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1; ++ int downY = normalY - 1; ++ blockposition.setY(normalY); ++ // Paper end + Biome biomebase = (Biome) this.getBiome(blockposition).value(); + +- if (biomebase.shouldFreeze(this, blockposition1)) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ // Paper start - optimise chunk ticking ++ blockposition.setY(downY); ++ if (biomebase.shouldFreeze(this, blockposition)) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit ++ // Paper end + } + + if (flag) { ++ blockposition.setY(normalY); // Paper + if (biomebase.shouldSnow(this, blockposition)) { + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit + } ++ blockposition.setY(downY); // Paper + +- BlockState iblockdata = this.getBlockState(blockposition1); ++ BlockState iblockdata = this.getBlockState(blockposition); // Paper + Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitation(); + +- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition1)) { ++ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition)) { // Paper + biomebase_precipitation = Biome.Precipitation.SNOW; + } + +- iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition1, biomebase_precipitation); ++ iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition, biomebase_precipitation); // Paper + } + } + +- gameprofilerfiller.popPush("tickBlocks"); ++ // Paper start - optimise random block ticking ++ gameprofilerfiller.popPush("randomTick"); + timings.chunkTicksBlocks.startTiming(); // Paper + if (randomTickSpeed > 0) { +- LevelChunkSection[] achunksection = chunk.getSections(); +- int l = achunksection.length; +- +- for (int i1 = 0; i1 < l; ++i1) { +- LevelChunkSection chunksection = achunksection[i1]; +- +- if (chunksection.isRandomlyTicking()) { +- int j1 = chunksection.bottomBlockY(); +- +- for (int k1 = 0; k1 < randomTickSpeed; ++k1) { +- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15); +- +- gameprofilerfiller.push("randomTick"); +- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k); ++ LevelChunkSection[] sections = chunk.getSections(); ++ int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); ++ for (int sectionIndex = 0; sectionIndex < sections.length; ++sectionIndex) { ++ LevelChunkSection section = sections[sectionIndex]; ++ if (section == null || section.tickingList.size() == 0) { ++ continue; ++ } + +- if (iblockdata1.isRandomlyTicking()) { +- iblockdata1.randomTick(this, blockposition2, this.random); +- } ++ int yPos = (sectionIndex + minSection) << 4; ++ for (int a = 0; a < randomTickSpeed; ++a) { ++ int tickingBlocks = section.tickingList.size(); ++ int index = this.randomTickRandom.nextInt(16 * 16 * 16); ++ if (index >= tickingBlocks) { ++ continue; ++ } + +- FluidState fluid = iblockdata1.getFluidState(); ++ long raw = section.tickingList.getRaw(index); ++ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); ++ int randomX = location & 15; ++ int randomY = ((location >>> (4 + 4)) & 255) | yPos; ++ int randomZ = (location >>> 4) & 15; + +- if (fluid.isRandomlyTicking()) { +- fluid.randomTick(this, blockposition2, this.random); +- } ++ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); ++ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); + +- gameprofilerfiller.pop(); +- } ++ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); ++ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). ++ // TODO CHECK ON UPDATE + } + } + } +- ++ // Paper end - optimise random block ticking + timings.chunkTicksBlocks.stopTiming(); // Paper + gameprofilerfiller.pop(); + } +diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java +index 62251727788d48a461ea6f7945771d7d6bdc7282..106610ccc74b70b557b01c61262d56c4f1147acf 100644 +--- a/src/main/java/net/minecraft/util/BitStorage.java ++++ b/src/main/java/net/minecraft/util/BitStorage.java +@@ -20,4 +20,15 @@ public interface BitStorage { + void unpack(int[] is); + + BitStorage copy(); ++ ++ // Paper start ++ void forEach(DataBitConsumer consumer); ++ ++ @FunctionalInterface ++ interface DataBitConsumer { ++ ++ void accept(int location, int data); ++ ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java +index 9b81ce9d85cba07e9752c29fb5a842c4b00aa873..36e33923bf48e56c743ed043bcbc66bc32f0422f 100644 +--- a/src/main/java/net/minecraft/util/SimpleBitStorage.java ++++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java +@@ -124,6 +124,28 @@ public class SimpleBitStorage implements BitStorage { + return this.bits; + } + ++ // Paper start ++ @Override ++ public final void forEach(DataBitConsumer consumer) { ++ int i = 0; ++ long[] along = this.data; ++ int j = along.length; ++ ++ for (int k = 0; k < j; ++k) { ++ long l = along[k]; ++ ++ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { ++ consumer.accept(i, (int) (l & this.mask)); ++ l >>= this.bits; ++ ++i; ++ if (i >= this.size) { ++ return; ++ } ++ } ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + int i = 0; +diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java +index 9686ce7536c9924b1b2aced4f013f46759cbc72e..5d8e9bdf5538b19681f21949368d862fab8a89ad 100644 +--- a/src/main/java/net/minecraft/util/ZeroBitStorage.java ++++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java +@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage { + return 0; + } + ++ // Paper start ++ @Override ++ public void forEach(DataBitConsumer consumer) { ++ for(int i = 0; i < this.size; ++i) { ++ consumer.accept(i, 0); ++ } ++ } ++ // Paper end ++ + @Override + public void getAll(IntConsumer action) { + for(int i = 0; i < this.size; ++i) { +diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +index 69c98c2cb2fd8f149a39bbddcbfe0c5c5adc3904..5575730aa6f77a91467c394fa8465c335d73db8e 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java +@@ -83,7 +83,7 @@ public class Turtle extends Animal { + } + + public void setHomePos(BlockPos pos) { +- this.entityData.set(Turtle.HOME_POS, pos); ++ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... + } + + public BlockPos getHomePos() { +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 8eabdd921cc976d9a1b9d3e8c315eeb22de3a968..76a2d00324a85419548005e0aa3cbd8b891b8257 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1302,10 +1302,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public abstract RecipeManager getRecipeManager(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int l) { ++ // Paper start - allow use of mutable pos ++ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); ++ this.getRandomBlockPosition(x, y, z, l, ret); ++ return ret.immutable(); ++ } ++ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { ++ // Paper end + this.randValue = this.randValue * 3 + 1013904223; + int i1 = this.randValue >> 2; + +- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); ++ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call ++ return out; // Paper + } + + public boolean noSave() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index 066874d27495dcaa3dea254b7328257e46920357..c3f1334b2bb97f0633f3ea43b97ee49adfd8bc0d 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -27,6 +27,7 @@ public class LevelChunkSection { + public final PalettedContainer states; + // CraftBukkit start - read/write + private PalettedContainer> biomes; ++ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper + + public LevelChunkSection(int i, PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { + // CraftBukkit end +@@ -85,6 +86,9 @@ public class LevelChunkSection { + --this.nonEmptyBlockCount; + if (iblockdata1.isRandomlyTicking()) { + --this.tickingBlockCount; ++ // Paper start ++ this.tickingList.remove(x, y, z); ++ // Paper end + } + } + +@@ -96,6 +100,9 @@ public class LevelChunkSection { + ++this.nonEmptyBlockCount; + if (state.isRandomlyTicking()) { + ++this.tickingBlockCount; ++ // Paper start ++ this.tickingList.add(x, y, z, state); ++ // Paper end + } + } + +@@ -127,40 +134,31 @@ public class LevelChunkSection { + } + + public void recalcBlockCounts() { +- class a implements PalettedContainer.CountConsumer { +- +- public int nonEmptyBlockCount; +- public int tickingBlockCount; +- public int tickingFluidCount; +- +- a() {} +- +- public void accept(BlockState iblockdata, int i) { +- FluidState fluid = iblockdata.getFluidState(); +- +- if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount += i; +- if (iblockdata.isRandomlyTicking()) { +- this.tickingBlockCount += i; +- } ++ // Paper start - unfuck this ++ this.tickingList.clear(); ++ this.nonEmptyBlockCount = 0; ++ this.tickingBlockCount = 0; ++ this.tickingFluidCount = 0; ++ this.states.forEachLocation((BlockState iblockdata, int i) -> { ++ FluidState fluid = iblockdata.getFluidState(); ++ ++ if (!iblockdata.isAir()) { ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); ++ if (iblockdata.isRandomlyTicking()) { ++ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); ++ this.tickingList.add(i, iblockdata); + } ++ } + +- if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount += i; +- if (fluid.isRandomlyTicking()) { +- this.tickingFluidCount += i; +- } ++ if (!fluid.isEmpty()) { ++ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); ++ if (fluid.isRandomlyTicking()) { ++ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); + } +- + } +- } + +- a a0 = new a(); +- +- this.states.count(a0); +- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; +- this.tickingBlockCount = (short) a0.tickingBlockCount; +- this.tickingFluidCount = (short) a0.tickingFluidCount; ++ }); ++ // Paper end + } + + public PalettedContainer getStates() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index d93118b7a602ceb6ef11ddabbce1d13fb8029a44..5ebde3a4f99b8d017d9a10a30fefc0b7dd011319 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -383,6 +383,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + } + } + ++ // Paper start ++ public void forEachLocation(PalettedContainer.CountConsumer consumer) { ++ this.data.storage.forEach((int location, int data) -> { ++ consumer.accept(this.data.palette.valueFor(data), location); ++ }); ++ } ++ // Paper end ++ + @FunctionalInterface + public interface CountConsumer { + void accept(T object, int count); diff --git a/patches/server/0756-Optimise-non-flush-packet-sending.patch b/patches/server/0756-Optimise-non-flush-packet-sending.patch new file mode 100644 index 0000000000..21c35fd4d5 --- /dev/null +++ b/patches/server/0756-Optimise-non-flush-packet-sending.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Sep 2020 01:49:19 -0700 +Subject: [PATCH] Optimise non-flush packet sending + +Places like entity tracking make heavy use of packet sending, +and internally netty will use some very expensive thread wakeup +calls when scheduling. + +Thanks to various hacks in ProtocolLib as well as other +plugins, we cannot simply use a queue of packets to group +send on execute. We have to call execute for each packet. + +Tux's suggestion here is exactly what was needed - tag +the Runnable indicating it should not make a wakeup call. + +Big thanks to Tux for making this possible as I had given +up on this optimisation before he came along. + +Locally this patch drops the entity tracker tick by a full 1.5x. + +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 51217798bfd549483ce456b44d14089f35642c55..fefda9868fd3c4b3392b2bf4c68c0b4b2f311f31 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -46,6 +46,8 @@ import org.slf4j.Logger; + import org.slf4j.Marker; + import org.slf4j.MarkerFactory; + ++ ++import io.netty.util.concurrent.AbstractEventExecutor; // Paper + public class Connection extends SimpleChannelInboundHandler> { + + private static final float AVERAGE_PACKETS_SMOOTHING = 0.75F; +@@ -396,9 +398,19 @@ public class Connection extends SimpleChannelInboundHandler> { + if (this.channel.eventLoop().inEventLoop()) { + this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper + } else { ++ // Paper start - optimise packets that are not flushed ++ // note: since the type is not dynamic here, we need to actually copy the old executor code ++ // into two branches. On conflict, just re-copy - no changes were made inside the executor code. ++ if (!flush) { ++ AbstractEventExecutor.LazyRunnable run = () -> { ++ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter ++ }; ++ this.channel.eventLoop().execute(run); ++ } else { // Paper end - optimise packets that are not flushed + this.channel.eventLoop().execute(() -> { +- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper ++ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter // Paper - diff on change + }); ++ } // Paper + } + + } diff --git a/patches/server/0756-Optimise-random-block-ticking.patch b/patches/server/0756-Optimise-random-block-ticking.patch deleted file mode 100644 index 6573b51a15..0000000000 --- a/patches/server/0756-Optimise-random-block-ticking.patch +++ /dev/null @@ -1,449 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 16:19:26 -0700 -Subject: [PATCH] Optimise random block ticking - -Massive performance improvement for random block ticking. -The performance increase comes from the fact that the vast -majority of attempted block ticks (~95% in my testing) fail -because the randomly selected block is not tickable. - -Now only tickable blocks are targeted, however this means that -the maximum number of block ticks occurs per chunk. However, -not all chunks are going to be targeted. The percent chance -of a chunk being targeted is based on how many tickable blocks -are in the chunk. -This means that while block ticks are spread out less, the -total number of blocks ticked per world tick remains the same. -Therefore, the chance of a random tickable block being ticked -remains the same. - -diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7d93652c1abbb6aee6eb7c26cf35d4d032ef7b69 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -@@ -0,0 +1,65 @@ -+package io.papermc.paper.util.math; -+ -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class ThreadUnsafeRandom extends LegacyRandomSource { -+ -+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. -+ private static final long multiplier = 0x5DEECE66DL; -+ private static final long addend = 0xBL; -+ private static final long mask = (1L << 48) - 1; -+ -+ private static long initialScramble(long seed) { -+ return (seed ^ multiplier) & mask; -+ } -+ -+ private long seed; -+ -+ public ThreadUnsafeRandom(long seed) { -+ super(seed); -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return new ThreadUnsafeRandom(this.nextLong()); -+ } -+ -+ @Override -+ public PositionalRandomFactory forkPositional() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setSeed(long seed) { -+ // note: called by Random constructor -+ this.seed = initialScramble(seed); -+ } -+ -+ @Override -+ public int next(int bits) { -+ // avoid the expensive CAS logic used by superclass -+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); -+ } -+ -+ // Taken from -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c -+ // Original license is public domain -+ public static int fastRandomBounded(final long randomInteger, final long limit) { -+ // randomInteger must be [0, pow(2, 32)) -+ // limit must be [0, pow(2, 32)) -+ return (int)((randomInteger * limit) >>> 32); -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ // yes this breaks random's spec -+ // however there's nothing that uses this class that relies on it -+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 92aafd475d58d1ef2e2736d9363c4b1010724fd7..bcd1b2534af33e7a9d184e0ea4c9c0a4b58dacc8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -670,6 +670,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - entityplayer.stopSleepInBed(false, false); - }); - } -+ // Paper start - optimise random block ticking -+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); -+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); -+ // Paper end - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { - ChunkPos chunkcoordintpair = chunk.getPos(); -@@ -679,10 +683,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push("thunder"); -- BlockPos blockposition; -+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change - - if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - disable thunder -- blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); -+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); - boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper -@@ -706,64 +710,75 @@ public class ServerLevel extends Level implements WorldGenLevel { - - gameprofilerfiller.popPush("iceandsnow"); - if (!this.paperConfig().environment.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow -- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15)); -- BlockPos blockposition1 = blockposition.below(); -+ // Paper start - optimise chunk ticking -+ this.getRandomBlockPosition(j, 0, k, 15, blockposition); -+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1; -+ int downY = normalY - 1; -+ blockposition.setY(normalY); -+ // Paper end - Biome biomebase = (Biome) this.getBiome(blockposition).value(); - -- if (biomebase.shouldFreeze(this, blockposition1)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ // Paper start - optimise chunk ticking -+ blockposition.setY(downY); -+ if (biomebase.shouldFreeze(this, blockposition)) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ // Paper end - } - - if (flag) { -+ blockposition.setY(normalY); // Paper - if (biomebase.shouldSnow(this, blockposition)) { - org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit - } -+ blockposition.setY(downY); // Paper - -- BlockState iblockdata = this.getBlockState(blockposition1); -+ BlockState iblockdata = this.getBlockState(blockposition); // Paper - Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitation(); - -- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition1)) { -+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.coldEnoughToSnow(blockposition)) { // Paper - biomebase_precipitation = Biome.Precipitation.SNOW; - } - -- iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition1, biomebase_precipitation); -+ iblockdata.getBlock().handlePrecipitation(iblockdata, this, blockposition, biomebase_precipitation); // Paper - } - } - -- gameprofilerfiller.popPush("tickBlocks"); -+ // Paper start - optimise random block ticking -+ gameprofilerfiller.popPush("randomTick"); - timings.chunkTicksBlocks.startTiming(); // Paper - if (randomTickSpeed > 0) { -- LevelChunkSection[] achunksection = chunk.getSections(); -- int l = achunksection.length; -- -- for (int i1 = 0; i1 < l; ++i1) { -- LevelChunkSection chunksection = achunksection[i1]; -- -- if (chunksection.isRandomlyTicking()) { -- int j1 = chunksection.bottomBlockY(); -- -- for (int k1 = 0; k1 < randomTickSpeed; ++k1) { -- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15); -- -- gameprofilerfiller.push("randomTick"); -- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k); -+ LevelChunkSection[] sections = chunk.getSections(); -+ int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); -+ for (int sectionIndex = 0; sectionIndex < sections.length; ++sectionIndex) { -+ LevelChunkSection section = sections[sectionIndex]; -+ if (section == null || section.tickingList.size() == 0) { -+ continue; -+ } - -- if (iblockdata1.isRandomlyTicking()) { -- iblockdata1.randomTick(this, blockposition2, this.random); -- } -+ int yPos = (sectionIndex + minSection) << 4; -+ for (int a = 0; a < randomTickSpeed; ++a) { -+ int tickingBlocks = section.tickingList.size(); -+ int index = this.randomTickRandom.nextInt(16 * 16 * 16); -+ if (index >= tickingBlocks) { -+ continue; -+ } - -- FluidState fluid = iblockdata1.getFluidState(); -+ long raw = section.tickingList.getRaw(index); -+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); -+ int randomX = location & 15; -+ int randomY = ((location >>> (4 + 4)) & 255) | yPos; -+ int randomZ = (location >>> 4) & 15; - -- if (fluid.isRandomlyTicking()) { -- fluid.randomTick(this, blockposition2, this.random); -- } -+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); -+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); - -- gameprofilerfiller.pop(); -- } -+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); -+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). -+ // TODO CHECK ON UPDATE - } - } - } -- -+ // Paper end - optimise random block ticking - timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); - } -diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 62251727788d48a461ea6f7945771d7d6bdc7282..106610ccc74b70b557b01c61262d56c4f1147acf 100644 ---- a/src/main/java/net/minecraft/util/BitStorage.java -+++ b/src/main/java/net/minecraft/util/BitStorage.java -@@ -20,4 +20,15 @@ public interface BitStorage { - void unpack(int[] is); - - BitStorage copy(); -+ -+ // Paper start -+ void forEach(DataBitConsumer consumer); -+ -+ @FunctionalInterface -+ interface DataBitConsumer { -+ -+ void accept(int location, int data); -+ -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java -index 9b81ce9d85cba07e9752c29fb5a842c4b00aa873..36e33923bf48e56c743ed043bcbc66bc32f0422f 100644 ---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java -+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java -@@ -124,6 +124,28 @@ public class SimpleBitStorage implements BitStorage { - return this.bits; - } - -+ // Paper start -+ @Override -+ public final void forEach(DataBitConsumer consumer) { -+ int i = 0; -+ long[] along = this.data; -+ int j = along.length; -+ -+ for (int k = 0; k < j; ++k) { -+ long l = along[k]; -+ -+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { -+ consumer.accept(i, (int) (l & this.mask)); -+ l >>= this.bits; -+ ++i; -+ if (i >= this.size) { -+ return; -+ } -+ } -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - int i = 0; -diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java -index 9686ce7536c9924b1b2aced4f013f46759cbc72e..5d8e9bdf5538b19681f21949368d862fab8a89ad 100644 ---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java -+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java -@@ -46,6 +46,15 @@ public class ZeroBitStorage implements BitStorage { - return 0; - } - -+ // Paper start -+ @Override -+ public void forEach(DataBitConsumer consumer) { -+ for(int i = 0; i < this.size; ++i) { -+ consumer.accept(i, 0); -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - for(int i = 0; i < this.size; ++i) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 69c98c2cb2fd8f149a39bbddcbfe0c5c5adc3904..5575730aa6f77a91467c394fa8465c335d73db8e 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -83,7 +83,7 @@ public class Turtle extends Animal { - } - - public void setHomePos(BlockPos pos) { -- this.entityData.set(Turtle.HOME_POS, pos); -+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... - } - - public BlockPos getHomePos() { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 8eabdd921cc976d9a1b9d3e8c315eeb22de3a968..76a2d00324a85419548005e0aa3cbd8b891b8257 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1302,10 +1302,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public abstract RecipeManager getRecipeManager(); - - public BlockPos getBlockRandomPos(int x, int y, int z, int l) { -+ // Paper start - allow use of mutable pos -+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); -+ this.getRandomBlockPosition(x, y, z, l, ret); -+ return ret.immutable(); -+ } -+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { -+ // Paper end - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; - -- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); -+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call -+ return out; // Paper - } - - public boolean noSave() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index 066874d27495dcaa3dea254b7328257e46920357..c3f1334b2bb97f0633f3ea43b97ee49adfd8bc0d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -27,6 +27,7 @@ public class LevelChunkSection { - public final PalettedContainer states; - // CraftBukkit start - read/write - private PalettedContainer> biomes; -+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper - - public LevelChunkSection(int i, PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { - // CraftBukkit end -@@ -85,6 +86,9 @@ public class LevelChunkSection { - --this.nonEmptyBlockCount; - if (iblockdata1.isRandomlyTicking()) { - --this.tickingBlockCount; -+ // Paper start -+ this.tickingList.remove(x, y, z); -+ // Paper end - } - } - -@@ -96,6 +100,9 @@ public class LevelChunkSection { - ++this.nonEmptyBlockCount; - if (state.isRandomlyTicking()) { - ++this.tickingBlockCount; -+ // Paper start -+ this.tickingList.add(x, y, z, state); -+ // Paper end - } - } - -@@ -127,40 +134,31 @@ public class LevelChunkSection { - } - - public void recalcBlockCounts() { -- class a implements PalettedContainer.CountConsumer { -- -- public int nonEmptyBlockCount; -- public int tickingBlockCount; -- public int tickingFluidCount; -- -- a() {} -- -- public void accept(BlockState iblockdata, int i) { -- FluidState fluid = iblockdata.getFluidState(); -- -- if (!iblockdata.isAir()) { -- this.nonEmptyBlockCount += i; -- if (iblockdata.isRandomlyTicking()) { -- this.tickingBlockCount += i; -- } -+ // Paper start - unfuck this -+ this.tickingList.clear(); -+ this.nonEmptyBlockCount = 0; -+ this.tickingBlockCount = 0; -+ this.tickingFluidCount = 0; -+ this.states.forEachLocation((BlockState iblockdata, int i) -> { -+ FluidState fluid = iblockdata.getFluidState(); -+ -+ if (!iblockdata.isAir()) { -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); -+ if (iblockdata.isRandomlyTicking()) { -+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); -+ this.tickingList.add(i, iblockdata); - } -+ } - -- if (!fluid.isEmpty()) { -- this.nonEmptyBlockCount += i; -- if (fluid.isRandomlyTicking()) { -- this.tickingFluidCount += i; -- } -+ if (!fluid.isEmpty()) { -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); -+ if (fluid.isRandomlyTicking()) { -+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); - } -- - } -- } - -- a a0 = new a(); -- -- this.states.count(a0); -- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; -- this.tickingBlockCount = (short) a0.tickingBlockCount; -- this.tickingFluidCount = (short) a0.tickingFluidCount; -+ }); -+ // Paper end - } - - public PalettedContainer getStates() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index d93118b7a602ceb6ef11ddabbce1d13fb8029a44..5ebde3a4f99b8d017d9a10a30fefc0b7dd011319 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -383,6 +383,14 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -+ // Paper start -+ public void forEachLocation(PalettedContainer.CountConsumer consumer) { -+ this.data.storage.forEach((int location, int data) -> { -+ consumer.accept(this.data.palette.valueFor(data), location); -+ }); -+ } -+ // Paper end -+ - @FunctionalInterface - public interface CountConsumer { - void accept(T object, int count); diff --git a/patches/server/0757-Optimise-nearby-player-lookups.patch b/patches/server/0757-Optimise-nearby-player-lookups.patch new file mode 100644 index 0000000000..f491e4b47c --- /dev/null +++ b/patches/server/0757-Optimise-nearby-player-lookups.patch @@ -0,0 +1,427 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 27 Aug 2020 16:22:52 -0700 +Subject: [PATCH] Optimise nearby player lookups + +Use a distance map to map out close players. +Note that it's important that we cache the distance map value per chunk +since the penalty of a map lookup could outweigh the benefits of +searching less players (as it basically did in the outside range patch). + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 538f21e6bc29c0307441fe4899dc7f600d2d1a04..09f262de1b12b09013f8277b25d13ffcf53b96d8 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -89,6 +89,12 @@ public class ChunkHolder { + this.chunkMap.needsChangeBroadcasting.add(this); + } + // Paper end - optimise chunk tick iteration ++ // Paper start - optimise checkDespawn ++ LevelChunk chunk = this.getFullChunkNowUnchecked(); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(); ++ } ++ // Paper end - optimise checkDespawn + } + + public void onChunkRemove() { +@@ -101,6 +107,12 @@ public class ChunkHolder { + this.chunkMap.needsChangeBroadcasting.remove(this); + } + // Paper end - optimise chunk tick iteration ++ // Paper start - optimise checkDespawn ++ LevelChunk chunk = this.getFullChunkNowUnchecked(); ++ if (chunk != null) { ++ chunk.removeGeneralAreaCache(); ++ } ++ // Paper end - optimise checkDespawn + } + // Paper end + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index f47e2d4a7343f3e3ee68f36688720cedc58861dc..aa1b6b692f9107ef1b5091714f1ecc8b29da9897 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -165,6 +165,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper + public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); + ++ // Paper start - optimise checkDespawn ++ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40; ++ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1); ++ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE; ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap; ++ // Paper end - optimise checkDespawn ++ + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); + public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { +@@ -242,6 +249,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - use distance map to optimise entity tracker + // Note: players need to be explicitly added to distance maps before they can be updated + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); +@@ -260,6 +268,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerGeneralAreaMap.remove(player); // Paper - optimise checkDespawns + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.remove(player); +@@ -280,6 +289,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end - use distance map to optimise entity tracker + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); +@@ -454,6 +464,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + }); + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ // Paper start - optimise checkDespawn ++ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(newState); ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); ++ if (chunk != null) { ++ chunk.updateGeneralAreaCache(newState); ++ } ++ }); ++ // Paper end - optimise checkDespawn + } + + protected ChunkGenerator generator() { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index bcd1b2534af33e7a9d184e0ea4c9c0a4b58dacc8..25149dde919738859f6fb6b2d0405e90d1732f2b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -411,6 +411,84 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Paper end + public final ReferenceOpenHashSet pendingLogin = new ReferenceOpenHashSet<>(); // Paper + ++ // Paper start - optimise checkDespawn ++ public final List playersAffectingSpawning = new java.util.ArrayList<>(); ++ // Paper end - optimise checkDespawn ++ // Paper start - optimise get nearest players for entity AI ++ @Override ++ public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source, ++ double centerX, double centerY, double centerZ) { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; ++ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); ++ ++ if (nearby == null) { ++ return null; ++ } ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ ++ double closestDistanceSquared = Double.MAX_VALUE; ++ ServerPlayer closest = null; ++ ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)_player; ++ ++ double distanceSquared = player.distanceToSqr(centerX, centerY, centerZ); ++ if (distanceSquared < closestDistanceSquared && condition.test(source, player)) { ++ closest = player; ++ closestDistanceSquared = distanceSquared; ++ } ++ } ++ ++ return closest; ++ } ++ ++ @Override ++ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) { ++ return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ()); ++ } ++ ++ @Override ++ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, ++ double d0, double d1, double d2) { ++ return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2); ++ } ++ ++ @Override ++ public List getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; ++ double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5; ++ double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5; ++ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); ++ ++ List ret = new java.util.ArrayList<>(); ++ ++ if (nearby == null) { ++ return ret; ++ } ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)_player; ++ ++ if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) { ++ ret.add(player); ++ } ++ } ++ ++ return ret; ++ } ++ // Paper end - optimise get nearest players for entity AI ++ + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer + public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { + // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error +@@ -513,6 +591,14 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + public void tick(BooleanSupplier shouldKeepTicking) { ++ // Paper start - optimise checkDespawn ++ this.playersAffectingSpawning.clear(); ++ for (ServerPlayer player : this.players) { ++ if (net.minecraft.world.entity.EntitySelector.affectsSpawning.test(player)) { ++ this.playersAffectingSpawning.add(player); ++ } ++ } ++ // Paper end - optimise checkDespawn + ProfilerFiller gameprofilerfiller = this.getProfiler(); + + this.handlingTick = true; +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 5fb88a3b7242a2712a568aaccebe601f89bfee3a..557e90e54439ce0430075403392b5052d5181feb 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -808,7 +808,12 @@ public abstract class Mob extends LivingEntity { + if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.discard(); + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper ++ // Paper start - optimise checkDespawn ++ Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.affectsSpawning); // Paper ++ if (entityhuman == null) { ++ entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0); ++ } ++ // Paper end - optimise checkDespawn + + if (entityhuman != null) { + double d0 = entityhuman.distanceToSqr((Entity) this); +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 76a2d00324a85419548005e0aa3cbd8b891b8257..931de769a3b7c993d151f3ee8e1038d95d3899a3 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -205,6 +205,69 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return this.getChunkIfLoaded(chunkX, chunkZ) != null; + } + // Paper end ++ // Paper start - optimise checkDespawn ++ public final List getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY, ++ double sourceZ, double maxRange, @Nullable Predicate predicate) { ++ LevelChunk chunk; ++ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || ++ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { ++ return this.getNearbyPlayersSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); ++ } ++ ++ List ret = new java.util.ArrayList<>(); ++ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret); ++ return ret; ++ } ++ ++ private List getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY, ++ double sourceZ, double maxRange, @Nullable Predicate predicate) { ++ List ret = new java.util.ArrayList<>(); ++ double maxRangeSquared = maxRange * maxRange; ++ ++ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { ++ if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) { ++ if (predicate == null || predicate.test(player)) { ++ ret.add(player); ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ private net.minecraft.server.level.ServerPlayer getNearestPlayerSlow(@Nullable Entity source, double sourceX, double sourceY, ++ double sourceZ, double maxRange, @Nullable Predicate predicate) { ++ net.minecraft.server.level.ServerPlayer closest = null; ++ double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; ++ ++ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { ++ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); ++ if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) { ++ closest = player; ++ closestRangeSquared = distanceSquared; ++ } ++ } ++ ++ return closest; ++ } ++ ++ ++ public final net.minecraft.server.level.ServerPlayer getNearestPlayer(@Nullable Entity source, double sourceX, double sourceY, ++ double sourceZ, double maxRange, @Nullable Predicate predicate) { ++ LevelChunk chunk; ++ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || ++ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { ++ return this.getNearestPlayerSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); ++ } ++ ++ return chunk.findNearestPlayer(sourceX, sourceY, sourceZ, maxRange, predicate); ++ } ++ ++ @Override ++ public @Nullable Player getNearestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { ++ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate); ++ } ++ // Paper end - optimise checkDespawn + + public abstract ResourceKey getTypeKey(); + +diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +index 4150e8cd7197eac53042d56f0a53a4951f8824ce..e31a2eea9a62ab2c0bed1a97dab6bae231b8cd8b 100644 +--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java ++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java +@@ -259,7 +259,7 @@ public final class NaturalSpawner { + blockposition_mutableblockposition.set(l, i, i1); + double d0 = (double) l + 0.5D; + double d1 = (double) i1 + 0.5D; +- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); ++ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range + + if (entityhuman != null) { + double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); +@@ -332,7 +332,7 @@ public final class NaturalSpawner { + } + + private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { +- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); ++ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); // Paper - diff on change, copy into caller + } + + private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 2981ba61e347b8660082ff946521fc7f219d2c0d..c85380c3bf3bf4448a28a91af78f41c235a583e4 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -235,6 +235,98 @@ public class LevelChunk extends ChunkAccess { + } + } + // Paper end ++ // Paper start - optimise checkDespawn ++ private boolean playerGeneralAreaCacheSet; ++ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playerGeneralAreaCache; ++ ++ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayerGeneralAreaCache() { ++ if (!this.playerGeneralAreaCacheSet) { ++ this.updateGeneralAreaCache(); ++ } ++ return this.playerGeneralAreaCache; ++ } ++ ++ public void updateGeneralAreaCache() { ++ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey)); ++ } ++ ++ public void removeGeneralAreaCache() { ++ this.playerGeneralAreaCacheSet = false; ++ this.playerGeneralAreaCache = null; ++ } ++ ++ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet value) { ++ this.playerGeneralAreaCacheSet = true; ++ this.playerGeneralAreaCache = value; ++ } ++ ++ public net.minecraft.server.level.ServerPlayer findNearestPlayer(double sourceX, double sourceY, double sourceZ, ++ double maxRange, java.util.function.Predicate predicate) { ++ if (!this.playerGeneralAreaCacheSet) { ++ this.updateGeneralAreaCache(); ++ } ++ ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; ++ ++ if (nearby == null) { ++ return null; ++ } ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ double closestDistance = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; ++ net.minecraft.server.level.ServerPlayer closest = null; ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { ++ continue; ++ } ++ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; ++ ++ double distance = player.distanceToSqr(sourceX, sourceY, sourceZ); ++ if (distance < closestDistance && predicate.test(player)) { ++ closest = player; ++ closestDistance = distance; ++ } ++ } ++ ++ return closest; ++ } ++ ++ public void getNearestPlayers(double sourceX, double sourceY, double sourceZ, java.util.function.Predicate predicate, ++ double range, java.util.List ret) { ++ if (!this.playerGeneralAreaCacheSet) { ++ this.updateGeneralAreaCache(); ++ } ++ ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; ++ ++ if (nearby == null) { ++ return; ++ } ++ ++ double rangeSquared = range * range; ++ ++ Object[] backingSet = nearby.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object _player = backingSet[i]; ++ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { ++ continue; ++ } ++ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; ++ ++ if (range >= 0.0) { ++ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); ++ if (distanceSquared > rangeSquared) { ++ continue; ++ } ++ } ++ ++ if (predicate == null || predicate.test(player)) { ++ ret.add(player); ++ } ++ } ++ } ++ // Paper end - optimise checkDespawn + + public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { + this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); diff --git a/patches/server/0757-Optimise-non-flush-packet-sending.patch b/patches/server/0757-Optimise-non-flush-packet-sending.patch deleted file mode 100644 index 21c35fd4d5..0000000000 --- a/patches/server/0757-Optimise-non-flush-packet-sending.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 22 Sep 2020 01:49:19 -0700 -Subject: [PATCH] Optimise non-flush packet sending - -Places like entity tracking make heavy use of packet sending, -and internally netty will use some very expensive thread wakeup -calls when scheduling. - -Thanks to various hacks in ProtocolLib as well as other -plugins, we cannot simply use a queue of packets to group -send on execute. We have to call execute for each packet. - -Tux's suggestion here is exactly what was needed - tag -the Runnable indicating it should not make a wakeup call. - -Big thanks to Tux for making this possible as I had given -up on this optimisation before he came along. - -Locally this patch drops the entity tracker tick by a full 1.5x. - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 51217798bfd549483ce456b44d14089f35642c55..fefda9868fd3c4b3392b2bf4c68c0b4b2f311f31 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -46,6 +46,8 @@ import org.slf4j.Logger; - import org.slf4j.Marker; - import org.slf4j.MarkerFactory; - -+ -+import io.netty.util.concurrent.AbstractEventExecutor; // Paper - public class Connection extends SimpleChannelInboundHandler> { - - private static final float AVERAGE_PACKETS_SMOOTHING = 0.75F; -@@ -396,9 +398,19 @@ public class Connection extends SimpleChannelInboundHandler> { - if (this.channel.eventLoop().inEventLoop()) { - this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - } else { -+ // Paper start - optimise packets that are not flushed -+ // note: since the type is not dynamic here, we need to actually copy the old executor code -+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code. -+ if (!flush) { -+ AbstractEventExecutor.LazyRunnable run = () -> { -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter -+ }; -+ this.channel.eventLoop().execute(run); -+ } else { // Paper end - optimise packets that are not flushed - this.channel.eventLoop().execute(() -> { -- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper -+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter // Paper - diff on change - }); -+ } // Paper - } - - } diff --git a/patches/server/0758-Optimise-WorldServer-notify.patch b/patches/server/0758-Optimise-WorldServer-notify.patch new file mode 100644 index 0000000000..275c5f0927 --- /dev/null +++ b/patches/server/0758-Optimise-WorldServer-notify.patch @@ -0,0 +1,337 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 9 Jul 2020 13:34:59 -0700 +Subject: [PATCH] Optimise WorldServer#notify + +Iterating over all of the navigators in the world is pretty expensive. +Instead, only iterate over navigators in the current region that are +eligible for repathing. + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index aa1b6b692f9107ef1b5091714f1ecc8b29da9897..69b8f5dcae4ea75ea9d63c36b3f5b4383fe232f9 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -302,15 +302,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager; + + public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData { ++ // Paper start - optimise notify() ++ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; ++ ++ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { ++ return this.navigators; ++ } ++ ++ public boolean addToNavigators(final Mob navigator) { ++ if (this.navigators == null) { ++ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); ++ } ++ return this.navigators.add(navigator); ++ } ++ ++ public boolean removeFromNavigators(final Mob navigator) { ++ if (this.navigators == null) { ++ return false; ++ } ++ return this.navigators.remove(navigator); ++ } ++ // Paper end - optimise notify() + } + + public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData { + ++ // Paper start - optimise notify() ++ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; ++ ++ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { ++ return this.navigators; ++ } ++ ++ public boolean addToNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { ++ if (this.navigators == null) { ++ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); ++ } ++ final boolean ret = this.navigators.add(navigator); ++ if (ret) { ++ final DataRegionData data = (DataRegionData)section.getRegion().regionData; ++ if (!data.addToNavigators(navigator)) { ++ throw new IllegalStateException(); ++ } ++ } ++ return ret; ++ } ++ ++ public boolean removeFromNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { ++ if (this.navigators == null) { ++ return false; ++ } ++ final boolean ret = this.navigators.remove(navigator); ++ if (ret) { ++ final DataRegionData data = (DataRegionData)section.getRegion().regionData; ++ if (!data.removeFromNavigators(navigator)) { ++ throw new IllegalStateException(); ++ } ++ } ++ return ret; ++ } ++ // Paper end - optimise notify() ++ + @Override + public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, + final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) { + final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; + final DataRegionData fromData = (DataRegionData)from.regionData; ++ // Paper start - optimise notify() ++ if (sectionData.navigators != null) { ++ for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ if (!fromData.removeFromNavigators(iterator.next())) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ // Paper end - optimise notify() + } + + @Override +@@ -320,6 +386,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; + final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; + final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; ++ // Paper start - optimise notify() ++ if (sectionData.navigators != null) { ++ for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { ++ if (!newRegionData.addToNavigators(iterator.next())) { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ // Paper end - optimise notify() + } + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 25149dde919738859f6fb6b2d0405e90d1732f2b..a7a403d34551453a1e0502fe1f7bc139f645d917 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1122,6 +1122,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public void tickNonPassenger(Entity entity) { + // Paper start - log detailed entity tick information + io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); ++ if (!entity.isRemoved()) this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify + try { + if (currentlyTickingEntity.get() == null) { + currentlyTickingEntity.lazySet(entity); +@@ -1639,9 +1640,18 @@ public class ServerLevel extends Level implements WorldGenLevel { + + if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { + List list = new ObjectArrayList(); +- Iterator iterator = this.navigatingMobs.iterator(); ++ // Paper start - optimise notify() ++ io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4); ++ if (region == null) { ++ return; ++ } ++ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators(); ++ if (navigatorsFromRegion == null) { ++ return; ++ } ++ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigatorsFromRegion.iterator(); + +- while (iterator.hasNext()) { ++ try { while (iterator.hasNext()) { // Paper end - optimise notify() + // CraftBukkit start - fix SPIGOT-6362 + Mob entityinsentient; + try { +@@ -1663,16 +1673,23 @@ public class ServerLevel extends Level implements WorldGenLevel { + + try { + this.isUpdatingNavigations = true; +- iterator = list.iterator(); ++ // Paper start - optimise notify() ++ Iterator navigationIterator = list.iterator(); + +- while (iterator.hasNext()) { +- PathNavigation navigationabstract1 = (PathNavigation) iterator.next(); ++ while (navigationIterator.hasNext()) { ++ PathNavigation navigationabstract1 = navigationIterator.next(); ++ // Paper end - optimise notify() + + navigationabstract1.recomputePath(); + } + } finally { + this.isUpdatingNavigations = false; + } ++ // Paper start - optimise notify() ++ } finally { ++ iterator.finishedIterating(); ++ } ++ // Paper end - optimise notify() + + } + } // Paper +@@ -2470,10 +2487,12 @@ public class ServerLevel extends Level implements WorldGenLevel { + + public void onTickingStart(Entity entity) { + ServerLevel.this.entityTickList.add(entity); ++ ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify + } + + public void onTickingEnd(Entity entity) { + ServerLevel.this.entityTickList.remove(entity); ++ ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Paper - optimise notify + // Paper start - Reset pearls when they stop being ticked + if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { + pearl.cachedOwner = null; +diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +index af53372391d05dd6aa3757556418e8723b8b6d80..3f672d7c2377fca16a6d8d31cf7aaae4f009fdce 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java ++++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +@@ -29,7 +29,7 @@ import net.minecraft.world.phys.Vec3; + + public abstract class PathNavigation { + private static final int MAX_TIME_RECOMPUTE = 20; +- protected final Mob mob; ++ protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor + protected final Level level; + @Nullable + protected Path path; +@@ -42,7 +42,7 @@ public abstract class PathNavigation { + protected long lastTimeoutCheck; + protected double timeoutLimit; + protected float maxDistanceToWaypoint = 0.5F; +- protected boolean hasDelayedRecomputation; ++ protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor + protected long timeLastRecompute; + protected NodeEvaluator nodeEvaluator; + @Nullable +@@ -420,7 +420,7 @@ public abstract class PathNavigation { + public boolean shouldRecomputePath(BlockPos pos) { + if (this.hasDelayedRecomputation) { + return false; +- } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { ++ } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking() + Node node = this.path.getEndNode(); + Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D); + return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex())); +@@ -436,4 +436,11 @@ public abstract class PathNavigation { + public boolean isStuck() { + return this.isStuck; + } ++ ++ // Paper start ++ public boolean isViableForPathRecalculationChecking() { ++ return !this.needsPathRecalculation() && ++ (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index f635b610e68d129aa0ae60c54b83da6943946436..1f0eddb0f3ded42bf312f8933def2f5c9a964651 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -71,6 +71,65 @@ public class PersistentEntitySectionManager implements A + } + // CraftBukkit end + ++ // Paper start - optimise notify() ++ public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ ++ public final void removeNavigatorsFromData(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ ++ public final void addNavigatorsIfPathingToRegion(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ } ++ ++ public final void updateNavigatorsInRegion(Entity entity) { ++ if (!(entity instanceof net.minecraft.world.entity.Mob)) { ++ return; ++ } ++ BlockPos entityPos = entity.blockPosition(); ++ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = ++ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); ++ if (section != null) { ++ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; ++ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { ++ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } else { ++ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); ++ } ++ } ++ } ++ // Paper end - optimise notify() ++ + void removeSectionIfEmpty(long sectionPos, EntitySection section) { + if (section.isEmpty()) { + this.sectionStorage.remove(sectionPos); +@@ -468,11 +527,25 @@ public class PersistentEntitySectionManager implements A + @Override + public void onMove() { + BlockPos blockposition = this.entity.blockPosition(); +- long i = SectionPos.asLong(blockposition); ++ long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section + + if (i != this.currentSectionKey) { + PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper +- Visibility visibility = this.currentSection.getStatus(); ++ Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility ++ // Paper start ++ int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift; ++ int oldChunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey); ++ int oldChunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey); ++ int oldRegionX = oldChunkX >> shift; ++ int oldRegionZ = oldChunkZ >> shift; ++ ++ int newRegionX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(newSectionPos) >> shift; ++ int newRegionZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(newSectionPos) >> shift; ++ ++ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { ++ PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ); ++ } ++ // Paper end + + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), i}); +@@ -484,6 +557,11 @@ public class PersistentEntitySectionManager implements A + entitysection.add(this.entity); + this.currentSection = entitysection; + this.currentSectionKey = i; ++ // Paper start ++ if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && oldVisibility.isTicking() && entitysection.getStatus().isTicking()) { ++ PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity); ++ } ++ // Paper end + this.updateStatus(visibility, entitysection.getStatus()); + } + diff --git a/patches/server/0758-Optimise-nearby-player-lookups.patch b/patches/server/0758-Optimise-nearby-player-lookups.patch deleted file mode 100644 index 539831eea4..0000000000 --- a/patches/server/0758-Optimise-nearby-player-lookups.patch +++ /dev/null @@ -1,427 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 27 Aug 2020 16:22:52 -0700 -Subject: [PATCH] Optimise nearby player lookups - -Use a distance map to map out close players. -Note that it's important that we cache the distance map value per chunk -since the penalty of a map lookup could outweigh the benefits of -searching less players (as it basically did in the outside range patch). - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index df6857ebcddc9a7bab6b82b71bfd447508b078b3..be5a742207623d186c5b936c669eb690c30d0719 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -89,6 +89,12 @@ public class ChunkHolder { - this.chunkMap.needsChangeBroadcasting.add(this); - } - // Paper end - optimise chunk tick iteration -+ // Paper start - optimise checkDespawn -+ LevelChunk chunk = this.getFullChunkNowUnchecked(); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(); -+ } -+ // Paper end - optimise checkDespawn - } - - public void onChunkRemove() { -@@ -101,6 +107,12 @@ public class ChunkHolder { - this.chunkMap.needsChangeBroadcasting.remove(this); - } - // Paper end - optimise chunk tick iteration -+ // Paper start - optimise checkDespawn -+ LevelChunk chunk = this.getFullChunkNowUnchecked(); -+ if (chunk != null) { -+ chunk.removeGeneralAreaCache(); -+ } -+ // Paper end - optimise checkDespawn - } - // Paper end - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index b6da7ae1827c4009dc8b22745e82890e1bb0b5ef..95c49072e4e90a44873c96af8173d364a5614dff 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -165,6 +165,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper - public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>(); - -+ // Paper start - optimise checkDespawn -+ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40; -+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1); -+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE; -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap; -+ // Paper end - optimise checkDespawn -+ - // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() - public final CallbackExecutor callbackExecutor = new CallbackExecutor(); - public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { -@@ -242,6 +249,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - use distance map to optimise entity tracker - // Note: players need to be explicitly added to distance maps before they can be updated - this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -@@ -260,6 +268,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobSpawnMap.remove(player); - this.playerChunkTickRangeMap.remove(player); - // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerGeneralAreaMap.remove(player); // Paper - optimise checkDespawns - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.remove(player); -@@ -280,6 +289,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - use distance map to optimise entity tracker - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { - this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); -@@ -454,6 +464,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - }); - // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -+ // Paper start - optimise checkDespawn -+ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(newState); -+ } -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(newState); -+ } -+ }); -+ // Paper end - optimise checkDespawn - } - - protected ChunkGenerator generator() { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index bcd1b2534af33e7a9d184e0ea4c9c0a4b58dacc8..25149dde919738859f6fb6b2d0405e90d1732f2b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -411,6 +411,84 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Paper end - public final ReferenceOpenHashSet pendingLogin = new ReferenceOpenHashSet<>(); // Paper - -+ // Paper start - optimise checkDespawn -+ public final List playersAffectingSpawning = new java.util.ArrayList<>(); -+ // Paper end - optimise checkDespawn -+ // Paper start - optimise get nearest players for entity AI -+ @Override -+ public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source, -+ double centerX, double centerY, double centerZ) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; -+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); -+ -+ if (nearby == null) { -+ return null; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ -+ double closestDistanceSquared = Double.MAX_VALUE; -+ ServerPlayer closest = null; -+ -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)_player; -+ -+ double distanceSquared = player.distanceToSqr(centerX, centerY, centerZ); -+ if (distanceSquared < closestDistanceSquared && condition.test(source, player)) { -+ closest = player; -+ closestDistanceSquared = distanceSquared; -+ } -+ } -+ -+ return closest; -+ } -+ -+ @Override -+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) { -+ return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ()); -+ } -+ -+ @Override -+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, -+ double d0, double d1, double d2) { -+ return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2); -+ } -+ -+ @Override -+ public List getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; -+ double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5; -+ double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5; -+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); -+ -+ List ret = new java.util.ArrayList<>(); -+ -+ if (nearby == null) { -+ return ret; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)_player; -+ -+ if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) { -+ ret.add(player); -+ } -+ } -+ -+ return ret; -+ } -+ // Paper end - optimise get nearest players for entity AI -+ - // Add env and gen to constructor, IWorldDataServer -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { - // Holder holder = worlddimension.typeHolder(); // CraftBukkit - decompile error -@@ -513,6 +591,14 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void tick(BooleanSupplier shouldKeepTicking) { -+ // Paper start - optimise checkDespawn -+ this.playersAffectingSpawning.clear(); -+ for (ServerPlayer player : this.players) { -+ if (net.minecraft.world.entity.EntitySelector.affectsSpawning.test(player)) { -+ this.playersAffectingSpawning.add(player); -+ } -+ } -+ // Paper end - optimise checkDespawn - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - this.handlingTick = true; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index a49dfe4f81d449c5dd7ba5b8f9af7fec5c54f5de..3646b969fa51b9683ab4137e530c3a6f6fc6c465 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -802,7 +802,12 @@ public abstract class Mob extends LivingEntity { - if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { - this.discard(); - } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { -- Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper -+ // Paper start - optimise checkDespawn -+ Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.affectsSpawning); // Paper -+ if (entityhuman == null) { -+ entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0); -+ } -+ // Paper end - optimise checkDespawn - - if (entityhuman != null) { - double d0 = entityhuman.distanceToSqr((Entity) this); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 45be0dc44b98ca90b68e6ff3278e4de21934d7a2..17c0110fd5ccb8399af797cc892ca285b1fb7964 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -205,6 +205,69 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.getChunkIfLoaded(chunkX, chunkZ) != null; - } - // Paper end -+ // Paper start - optimise checkDespawn -+ public final List getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ LevelChunk chunk; -+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || -+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { -+ return this.getNearbyPlayersSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ List ret = new java.util.ArrayList<>(); -+ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret); -+ return ret; -+ } -+ -+ private List getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ double maxRangeSquared = maxRange * maxRange; -+ -+ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { -+ if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) { -+ if (predicate == null || predicate.test(player)) { -+ ret.add(player); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ private net.minecraft.server.level.ServerPlayer getNearestPlayerSlow(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ net.minecraft.server.level.ServerPlayer closest = null; -+ double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; -+ -+ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { -+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) { -+ closest = player; -+ closestRangeSquared = distanceSquared; -+ } -+ } -+ -+ return closest; -+ } -+ -+ -+ public final net.minecraft.server.level.ServerPlayer getNearestPlayer(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ LevelChunk chunk; -+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || -+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { -+ return this.getNearestPlayerSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ return chunk.findNearestPlayer(sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ @Override -+ public @Nullable Player getNearestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { -+ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate); -+ } -+ // Paper end - optimise checkDespawn - - public abstract ResourceKey getTypeKey(); - -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 4150e8cd7197eac53042d56f0a53a4951f8824ce..e31a2eea9a62ab2c0bed1a97dab6bae231b8cd8b 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -259,7 +259,7 @@ public final class NaturalSpawner { - blockposition_mutableblockposition.set(l, i, i1); - double d0 = (double) l + 0.5D; - double d1 = (double) i1 + 0.5D; -- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); -+ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range - - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); -@@ -332,7 +332,7 @@ public final class NaturalSpawner { - } - - private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { -- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); -+ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerToCenterThan(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isNaturalSpawningAllowed((BlockPos) pos)); // Paper - diff on change, copy into caller - } - - private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 2981ba61e347b8660082ff946521fc7f219d2c0d..c85380c3bf3bf4448a28a91af78f41c235a583e4 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -235,6 +235,98 @@ public class LevelChunk extends ChunkAccess { - } - } - // Paper end -+ // Paper start - optimise checkDespawn -+ private boolean playerGeneralAreaCacheSet; -+ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playerGeneralAreaCache; -+ -+ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayerGeneralAreaCache() { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ return this.playerGeneralAreaCache; -+ } -+ -+ public void updateGeneralAreaCache() { -+ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey)); -+ } -+ -+ public void removeGeneralAreaCache() { -+ this.playerGeneralAreaCacheSet = false; -+ this.playerGeneralAreaCache = null; -+ } -+ -+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet value) { -+ this.playerGeneralAreaCacheSet = true; -+ this.playerGeneralAreaCache = value; -+ } -+ -+ public net.minecraft.server.level.ServerPlayer findNearestPlayer(double sourceX, double sourceY, double sourceZ, -+ double maxRange, java.util.function.Predicate predicate) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; -+ -+ if (nearby == null) { -+ return null; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ double closestDistance = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; -+ net.minecraft.server.level.ServerPlayer closest = null; -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { -+ continue; -+ } -+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; -+ -+ double distance = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distance < closestDistance && predicate.test(player)) { -+ closest = player; -+ closestDistance = distance; -+ } -+ } -+ -+ return closest; -+ } -+ -+ public void getNearestPlayers(double sourceX, double sourceY, double sourceZ, java.util.function.Predicate predicate, -+ double range, java.util.List ret) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; -+ -+ if (nearby == null) { -+ return; -+ } -+ -+ double rangeSquared = range * range; -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { -+ continue; -+ } -+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; -+ -+ if (range >= 0.0) { -+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distanceSquared > rangeSquared) { -+ continue; -+ } -+ } -+ -+ if (predicate == null || predicate.test(player)) { -+ ret.add(player); -+ } -+ } -+ } -+ // Paper end - optimise checkDespawn - - public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { - this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); diff --git a/patches/server/0759-Optimise-WorldServer-notify.patch b/patches/server/0759-Optimise-WorldServer-notify.patch deleted file mode 100644 index 0e414d0146..0000000000 --- a/patches/server/0759-Optimise-WorldServer-notify.patch +++ /dev/null @@ -1,337 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 9 Jul 2020 13:34:59 -0700 -Subject: [PATCH] Optimise WorldServer#notify - -Iterating over all of the navigators in the world is pretty expensive. -Instead, only iterate over navigators in the current region that are -eligible for repathing. - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 95c49072e4e90a44873c96af8173d364a5614dff..b98a918ed6d2fabda5bb596dcd13e52033473ebe 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -302,15 +302,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager; - - public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData { -+ // Paper start - optimise notify() -+ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; -+ -+ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { -+ return this.navigators; -+ } -+ -+ public boolean addToNavigators(final Mob navigator) { -+ if (this.navigators == null) { -+ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); -+ } -+ return this.navigators.add(navigator); -+ } -+ -+ public boolean removeFromNavigators(final Mob navigator) { -+ if (this.navigators == null) { -+ return false; -+ } -+ return this.navigators.remove(navigator); -+ } -+ // Paper end - optimise notify() - } - - public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData { - -+ // Paper start - optimise notify() -+ private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; -+ -+ public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { -+ return this.navigators; -+ } -+ -+ public boolean addToNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { -+ if (this.navigators == null) { -+ this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); -+ } -+ final boolean ret = this.navigators.add(navigator); -+ if (ret) { -+ final DataRegionData data = (DataRegionData)section.getRegion().regionData; -+ if (!data.addToNavigators(navigator)) { -+ throw new IllegalStateException(); -+ } -+ } -+ return ret; -+ } -+ -+ public boolean removeFromNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { -+ if (this.navigators == null) { -+ return false; -+ } -+ final boolean ret = this.navigators.remove(navigator); -+ if (ret) { -+ final DataRegionData data = (DataRegionData)section.getRegion().regionData; -+ if (!data.removeFromNavigators(navigator)) { -+ throw new IllegalStateException(); -+ } -+ } -+ return ret; -+ } -+ // Paper end - optimise notify() -+ - @Override - public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, - final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) { - final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; - final DataRegionData fromData = (DataRegionData)from.regionData; -+ // Paper start - optimise notify() -+ if (sectionData.navigators != null) { -+ for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ if (!fromData.removeFromNavigators(iterator.next())) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ // Paper end - optimise notify() - } - - @Override -@@ -320,6 +386,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; - final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; - final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; -+ // Paper start - optimise notify() -+ if (sectionData.navigators != null) { -+ for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ if (!newRegionData.addToNavigators(iterator.next())) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ // Paper end - optimise notify() - } - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 25149dde919738859f6fb6b2d0405e90d1732f2b..a7a403d34551453a1e0502fe1f7bc139f645d917 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1122,6 +1122,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public void tickNonPassenger(Entity entity) { - // Paper start - log detailed entity tick information - io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); -+ if (!entity.isRemoved()) this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify - try { - if (currentlyTickingEntity.get() == null) { - currentlyTickingEntity.lazySet(entity); -@@ -1639,9 +1640,18 @@ public class ServerLevel extends Level implements WorldGenLevel { - - if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { - List list = new ObjectArrayList(); -- Iterator iterator = this.navigatingMobs.iterator(); -+ // Paper start - optimise notify() -+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4); -+ if (region == null) { -+ return; -+ } -+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators(); -+ if (navigatorsFromRegion == null) { -+ return; -+ } -+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigatorsFromRegion.iterator(); - -- while (iterator.hasNext()) { -+ try { while (iterator.hasNext()) { // Paper end - optimise notify() - // CraftBukkit start - fix SPIGOT-6362 - Mob entityinsentient; - try { -@@ -1663,16 +1673,23 @@ public class ServerLevel extends Level implements WorldGenLevel { - - try { - this.isUpdatingNavigations = true; -- iterator = list.iterator(); -+ // Paper start - optimise notify() -+ Iterator navigationIterator = list.iterator(); - -- while (iterator.hasNext()) { -- PathNavigation navigationabstract1 = (PathNavigation) iterator.next(); -+ while (navigationIterator.hasNext()) { -+ PathNavigation navigationabstract1 = navigationIterator.next(); -+ // Paper end - optimise notify() - - navigationabstract1.recomputePath(); - } - } finally { - this.isUpdatingNavigations = false; - } -+ // Paper start - optimise notify() -+ } finally { -+ iterator.finishedIterating(); -+ } -+ // Paper end - optimise notify() - - } - } // Paper -@@ -2470,10 +2487,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - - public void onTickingStart(Entity entity) { - ServerLevel.this.entityTickList.add(entity); -+ ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify - } - - public void onTickingEnd(Entity entity) { - ServerLevel.this.entityTickList.remove(entity); -+ ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Paper - optimise notify - // Paper start - Reset pearls when they stop being ticked - if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { - pearl.cachedOwner = null; -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index af53372391d05dd6aa3757556418e8723b8b6d80..3f672d7c2377fca16a6d8d31cf7aaae4f009fdce 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -29,7 +29,7 @@ import net.minecraft.world.phys.Vec3; - - public abstract class PathNavigation { - private static final int MAX_TIME_RECOMPUTE = 20; -- protected final Mob mob; -+ protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor - protected final Level level; - @Nullable - protected Path path; -@@ -42,7 +42,7 @@ public abstract class PathNavigation { - protected long lastTimeoutCheck; - protected double timeoutLimit; - protected float maxDistanceToWaypoint = 0.5F; -- protected boolean hasDelayedRecomputation; -+ protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor - protected long timeLastRecompute; - protected NodeEvaluator nodeEvaluator; - @Nullable -@@ -420,7 +420,7 @@ public abstract class PathNavigation { - public boolean shouldRecomputePath(BlockPos pos) { - if (this.hasDelayedRecomputation) { - return false; -- } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { -+ } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking() - Node node = this.path.getEndNode(); - Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D); - return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex())); -@@ -436,4 +436,11 @@ public abstract class PathNavigation { - public boolean isStuck() { - return this.isStuck; - } -+ -+ // Paper start -+ public boolean isViableForPathRecalculationChecking() { -+ return !this.needsPathRecalculation() && -+ (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index f635b610e68d129aa0ae60c54b83da6943946436..1f0eddb0f3ded42bf312f8933def2f5c9a964651 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -71,6 +71,65 @@ public class PersistentEntitySectionManager implements A - } - // CraftBukkit end - -+ // Paper start - optimise notify() -+ public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ -+ public final void removeNavigatorsFromData(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ -+ public final void addNavigatorsIfPathingToRegion(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { -+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ } -+ -+ public final void updateNavigatorsInRegion(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { -+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } else { -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ } -+ // Paper end - optimise notify() -+ - void removeSectionIfEmpty(long sectionPos, EntitySection section) { - if (section.isEmpty()) { - this.sectionStorage.remove(sectionPos); -@@ -468,11 +527,25 @@ public class PersistentEntitySectionManager implements A - @Override - public void onMove() { - BlockPos blockposition = this.entity.blockPosition(); -- long i = SectionPos.asLong(blockposition); -+ long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section - - if (i != this.currentSectionKey) { - PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper -- Visibility visibility = this.currentSection.getStatus(); -+ Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility -+ // Paper start -+ int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift; -+ int oldChunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey); -+ int oldChunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey); -+ int oldRegionX = oldChunkX >> shift; -+ int oldRegionZ = oldChunkZ >> shift; -+ -+ int newRegionX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(newSectionPos) >> shift; -+ int newRegionZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(newSectionPos) >> shift; -+ -+ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { -+ PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ); -+ } -+ // Paper end - - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), i}); -@@ -484,6 +557,11 @@ public class PersistentEntitySectionManager implements A - entitysection.add(this.entity); - this.currentSection = entitysection; - this.currentSectionKey = i; -+ // Paper start -+ if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && oldVisibility.isTicking() && entitysection.getStatus().isTicking()) { -+ PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity); -+ } -+ // Paper end - this.updateStatus(visibility, entitysection.getStatus()); - } - diff --git a/patches/server/0759-Remove-streams-for-villager-AI.patch b/patches/server/0759-Remove-streams-for-villager-AI.patch new file mode 100644 index 0000000000..3af840ec77 --- /dev/null +++ b/patches/server/0759-Remove-streams-for-villager-AI.patch @@ -0,0 +1,216 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 27 Aug 2020 20:51:40 -0700 +Subject: [PATCH] Remove streams for villager AI + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +index 5a5d454b5987bb01d03f91c15b7a6bff46f1de71..c545539fc5d20cc69a0e5d2e261ef46a8f6fa4f0 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java +@@ -30,11 +30,19 @@ public class GateBehavior extends Behavior { + + @Override + protected boolean canStillUse(ServerLevel world, E entity, long time) { +- return this.behaviors.stream().filter((task) -> { +- return task.getStatus() == Behavior.Status.RUNNING; +- }).anyMatch((task) -> { +- return task.canStillUse(world, entity, time); +- }); ++ // Paper start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ if (behavior.canStillUse(world, entity, time)) { ++ return true; ++ } ++ } ++ } ++ return false; ++ // Paper end - remove streams + } + + @Override +@@ -45,25 +53,35 @@ public class GateBehavior extends Behavior { + @Override + protected void start(ServerLevel world, E entity, long time) { + this.orderPolicy.apply(this.behaviors); +- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time); ++ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); // Paper - remove streams + } + + @Override + protected void tick(ServerLevel world, E entity, long time) { +- this.behaviors.stream().filter((task) -> { +- return task.getStatus() == Behavior.Status.RUNNING; +- }).forEach((task) -> { +- task.tickOrStop(world, entity, time); +- }); ++ // Paper start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ behavior.tickOrStop(world, entity, time); ++ } ++ } ++ // Paper end - remove streams + } + + @Override + protected void stop(ServerLevel world, E entity, long time) { +- this.behaviors.stream().filter((task) -> { +- return task.getStatus() == Behavior.Status.RUNNING; +- }).forEach((task) -> { +- task.doStop(world, entity, time); +- }); ++ // Paper start - remove streams ++ List>> entries = this.behaviors.entries; ++ for (int i = 0; i < entries.size(); i++) { ++ ShufflingList.WeightedEntry> entry = entries.get(i); ++ Behavior behavior = entry.getData(); ++ if (behavior.getStatus() == Status.RUNNING) { ++ behavior.doStop(world, entity, time); ++ } ++ } ++ // Paper end - remove streams + this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory); + } + +@@ -94,25 +112,33 @@ public class GateBehavior extends Behavior { + public static enum RunningPolicy { + RUN_ONE { + @Override +- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { +- tasks.filter((task) -> { +- return task.getStatus() == Behavior.Status.STOPPED; +- }).filter((task) -> { +- return task.tryStart(world, entity, time); +- }).findFirst(); ++ // Paper start - remove streams ++ public void apply(List>> tasks, ServerLevel world, E entity, long time) { ++ for (int i = 0; i < tasks.size(); i++) { ++ ShufflingList.WeightedEntry> task = tasks.get(i); ++ Behavior behavior = task.getData(); ++ if (behavior.getStatus() == Status.STOPPED && behavior.tryStart(world, entity, time)) { ++ break; ++ } ++ } ++ // Paper end - remove streams + } + }, + TRY_ALL { + @Override +- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { +- tasks.filter((task) -> { +- return task.getStatus() == Behavior.Status.STOPPED; +- }).forEach((task) -> { +- task.tryStart(world, entity, time); +- }); ++ // Paper start - remove streams ++ public void apply(List>> tasks, ServerLevel world, E entity, long time) { ++ for (int i = 0; i < tasks.size(); i++) { ++ ShufflingList.WeightedEntry> task = tasks.get(i); ++ Behavior behavior = task.getData(); ++ if (behavior.getStatus() == Status.STOPPED) { ++ behavior.tryStart(world, entity, time); ++ } ++ } ++ // Paper end - remove streams + } + }; + +- public abstract void apply(Stream> tasks, ServerLevel world, E entity, long time); ++ public abstract void apply(List>> tasks, ServerLevel world, E entity, long time); // Paper - remove streams + } + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +index 090bba46b6ecd7d0e1415feb678b9b23264fe5e9..ca771d60283d94c00aa65d06ef93071e412357e8 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java ++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java +@@ -12,7 +12,7 @@ import java.util.stream.Stream; + import net.minecraft.util.RandomSource; + + public class ShufflingList { +- protected final List> entries; ++ public final List> entries; // Paper - public + private final RandomSource random = RandomSource.create(); + private final boolean isUnsafe; // Paper + +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index 1dfcc5cba6ffb463acf161a23fff1ca452184290..2c4517850a9692f1c2b1332b58e8312fe1166772 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -25,13 +25,16 @@ public class NearestItemSensor extends Sensor { + protected void doTick(ServerLevel world, Mob entity) { + Brain brain = entity.getBrain(); + List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0D, 16.0D, 32.0D), (itemEntity) -> { +- return true; ++ return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities + }); +- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); ++ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. ++ // Paper start - remove streams + // Paper start - remove streams in favour of lists + ItemEntity nearest = null; +- for (ItemEntity entityItem : list) { +- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) { ++ for (int i = 0; i < list.size(); i++) { ++ ItemEntity entityItem = list.get(i); ++ if (entity.hasLineOfSight(entityItem)) { ++ // Paper end - remove streams + nearest = entityItem; + break; + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +index 312775d0430f793720211dc29bb293503e799d11..75d9c4f011b5a97def215784c92bb57bbb35d06b 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java ++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java +@@ -21,25 +21,30 @@ public class PlayerSensor extends Sensor { + + @Override + protected void doTick(ServerLevel world, LivingEntity entity) { +- List players = new java.util.ArrayList<>(world.players()); +- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); +- players.sort(Comparator.comparingDouble(entity::distanceTo)); ++ // Paper start - remove streams ++ List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); ++ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); + Brain brain = entity.getBrain(); + + brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); + +- Player nearest = null, nearestTargetable = null; +- for (Player player : players) { +- if (Sensor.isEntityTargetable(entity, player)) { +- if (nearest == null) nearest = player; +- if (Sensor.isEntityAttackable(entity, player)) { +- nearestTargetable = player; +- break; // Both variables are assigned, no reason to loop further +- } ++ Player firstTargetable = null; ++ Player firstAttackable = null; ++ for (int index = 0, len = players.size(); index < len; ++index) { ++ Player player = players.get(index); ++ if (firstTargetable == null && isEntityTargetable(entity, player)) { ++ firstTargetable = player; ++ } ++ if (firstAttackable == null && isEntityAttackable(entity, player)) { ++ firstAttackable = player; ++ } ++ ++ if (firstAttackable != null && firstTargetable != null) { ++ break; + } + } +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); +- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); +- // Paper end ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); ++ // Paper end - remove streams + } + } diff --git a/patches/server/0760-Remove-streams-for-villager-AI.patch b/patches/server/0760-Remove-streams-for-villager-AI.patch deleted file mode 100644 index 3af840ec77..0000000000 --- a/patches/server/0760-Remove-streams-for-villager-AI.patch +++ /dev/null @@ -1,216 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 27 Aug 2020 20:51:40 -0700 -Subject: [PATCH] Remove streams for villager AI - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -index 5a5d454b5987bb01d03f91c15b7a6bff46f1de71..c545539fc5d20cc69a0e5d2e261ef46a8f6fa4f0 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -@@ -30,11 +30,19 @@ public class GateBehavior extends Behavior { - - @Override - protected boolean canStillUse(ServerLevel world, E entity, long time) { -- return this.behaviors.stream().filter((task) -> { -- return task.getStatus() == Behavior.Status.RUNNING; -- }).anyMatch((task) -> { -- return task.canStillUse(world, entity, time); -- }); -+ // Paper start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ if (behavior.canStillUse(world, entity, time)) { -+ return true; -+ } -+ } -+ } -+ return false; -+ // Paper end - remove streams - } - - @Override -@@ -45,25 +53,35 @@ public class GateBehavior extends Behavior { - @Override - protected void start(ServerLevel world, E entity, long time) { - this.orderPolicy.apply(this.behaviors); -- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time); -+ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); // Paper - remove streams - } - - @Override - protected void tick(ServerLevel world, E entity, long time) { -- this.behaviors.stream().filter((task) -> { -- return task.getStatus() == Behavior.Status.RUNNING; -- }).forEach((task) -> { -- task.tickOrStop(world, entity, time); -- }); -+ // Paper start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ behavior.tickOrStop(world, entity, time); -+ } -+ } -+ // Paper end - remove streams - } - - @Override - protected void stop(ServerLevel world, E entity, long time) { -- this.behaviors.stream().filter((task) -> { -- return task.getStatus() == Behavior.Status.RUNNING; -- }).forEach((task) -> { -- task.doStop(world, entity, time); -- }); -+ // Paper start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ behavior.doStop(world, entity, time); -+ } -+ } -+ // Paper end - remove streams - this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory); - } - -@@ -94,25 +112,33 @@ public class GateBehavior extends Behavior { - public static enum RunningPolicy { - RUN_ONE { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((task) -> { -- return task.getStatus() == Behavior.Status.STOPPED; -- }).filter((task) -> { -- return task.tryStart(world, entity, time); -- }).findFirst(); -+ // Paper start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (int i = 0; i < tasks.size(); i++) { -+ ShufflingList.WeightedEntry> task = tasks.get(i); -+ Behavior behavior = task.getData(); -+ if (behavior.getStatus() == Status.STOPPED && behavior.tryStart(world, entity, time)) { -+ break; -+ } -+ } -+ // Paper end - remove streams - } - }, - TRY_ALL { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((task) -> { -- return task.getStatus() == Behavior.Status.STOPPED; -- }).forEach((task) -> { -- task.tryStart(world, entity, time); -- }); -+ // Paper start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (int i = 0; i < tasks.size(); i++) { -+ ShufflingList.WeightedEntry> task = tasks.get(i); -+ Behavior behavior = task.getData(); -+ if (behavior.getStatus() == Status.STOPPED) { -+ behavior.tryStart(world, entity, time); -+ } -+ } -+ // Paper end - remove streams - } - }; - -- public abstract void apply(Stream> tasks, ServerLevel world, E entity, long time); -+ public abstract void apply(List>> tasks, ServerLevel world, E entity, long time); // Paper - remove streams - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -index 090bba46b6ecd7d0e1415feb678b9b23264fe5e9..ca771d60283d94c00aa65d06ef93071e412357e8 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -@@ -12,7 +12,7 @@ import java.util.stream.Stream; - import net.minecraft.util.RandomSource; - - public class ShufflingList { -- protected final List> entries; -+ public final List> entries; // Paper - public - private final RandomSource random = RandomSource.create(); - private final boolean isUnsafe; // Paper - -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -index 1dfcc5cba6ffb463acf161a23fff1ca452184290..2c4517850a9692f1c2b1332b58e8312fe1166772 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -@@ -25,13 +25,16 @@ public class NearestItemSensor extends Sensor { - protected void doTick(ServerLevel world, Mob entity) { - Brain brain = entity.getBrain(); - List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0D, 16.0D, 32.0D), (itemEntity) -> { -- return true; -+ return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities - }); -- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); -+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. -+ // Paper start - remove streams - // Paper start - remove streams in favour of lists - ItemEntity nearest = null; -- for (ItemEntity entityItem : list) { -- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) { -+ for (int i = 0; i < list.size(); i++) { -+ ItemEntity entityItem = list.get(i); -+ if (entity.hasLineOfSight(entityItem)) { -+ // Paper end - remove streams - nearest = entityItem; - break; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -index 312775d0430f793720211dc29bb293503e799d11..75d9c4f011b5a97def215784c92bb57bbb35d06b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -@@ -21,25 +21,30 @@ public class PlayerSensor extends Sensor { - - @Override - protected void doTick(ServerLevel world, LivingEntity entity) { -- List players = new java.util.ArrayList<>(world.players()); -- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); -- players.sort(Comparator.comparingDouble(entity::distanceTo)); -+ // Paper start - remove streams -+ List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); -+ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); - Brain brain = entity.getBrain(); - - brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); - -- Player nearest = null, nearestTargetable = null; -- for (Player player : players) { -- if (Sensor.isEntityTargetable(entity, player)) { -- if (nearest == null) nearest = player; -- if (Sensor.isEntityAttackable(entity, player)) { -- nearestTargetable = player; -- break; // Both variables are assigned, no reason to loop further -- } -+ Player firstTargetable = null; -+ Player firstAttackable = null; -+ for (int index = 0, len = players.size(); index < len; ++index) { -+ Player player = players.get(index); -+ if (firstTargetable == null && isEntityTargetable(entity, player)) { -+ firstTargetable = player; -+ } -+ if (firstAttackable == null && isEntityAttackable(entity, player)) { -+ firstAttackable = player; -+ } -+ -+ if (firstAttackable != null && firstTargetable != null) { -+ break; - } - } -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); -- // Paper end -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); -+ // Paper end - remove streams - } - } diff --git a/patches/server/0760-Rewrite-dataconverter-system.patch b/patches/server/0760-Rewrite-dataconverter-system.patch new file mode 100644 index 0000000000..e892c4fc27 --- /dev/null +++ b/patches/server/0760-Rewrite-dataconverter-system.patch @@ -0,0 +1,22747 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Jun 2021 10:43:01 -0700 +Subject: [PATCH] Rewrite dataconverter system + +Please see https://github.com/PaperMC/DataConverter +for details. + +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java +@@ -0,0 +1,54 @@ ++package ca.spottedleaf.dataconverter.converters; ++ ++import java.util.Comparator; ++ ++public abstract class DataConverter { ++ ++ public static final Comparator> LOWEST_VERSION_COMPARATOR = (x, y) -> { ++ return Long.compare(x.getEncodedVersion(), y.getEncodedVersion()); ++ }; ++ ++ protected final int toVersion; ++ protected final int versionStep; ++ ++ public DataConverter(final int toVersion) { ++ this.toVersion = toVersion; ++ this.versionStep = 0; ++ } ++ ++ public DataConverter(final int toVersion, final int versionStep) { ++ this.toVersion = toVersion; ++ this.versionStep = versionStep; ++ } ++ ++ public final int getToVersion() { ++ return this.toVersion; ++ } ++ ++ public final int getVersionStep() { ++ return this.versionStep; ++ } ++ ++ public final long getEncodedVersion() { ++ return encodeVersions(this.toVersion, this.versionStep); ++ } ++ ++ public abstract R convert(final T data, final long sourceVersion, final long toVersion); ++ ++ // step must be in the lower bits, so that encodeVersions(version, step) < encodeVersions(version, step + 1) ++ public static long encodeVersions(final int version, final int step) { ++ return ((long)version << 32) | (step & 0xFFFFFFFFL); ++ } ++ ++ public static int getVersion(final long encoded) { ++ return (int)(encoded >>> 32); ++ } ++ ++ public static int getStep(final long encoded) { ++ return (int)encoded; ++ } ++ ++ public static String encodedToString(final long encoded) { ++ return getVersion(encoded) + "." + getStep(encoded); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++public interface DataHook { ++ ++ public R preHook(final T data, final long fromVersion, final long toVersion); ++ ++ public R postHook(final T data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java +@@ -0,0 +1,7 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++public abstract class DataType { ++ ++ public abstract R convert(final T data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf9fae4451ead4860343b915fb70e3a7cdf0de31 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.converters.datatypes; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public interface DataWalker { ++ ++ public MapType walk(final MapType data, final long fromVersion, final long toVersion); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dde9d36bf6212196caa18f3c9c535aec330a33ed +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; ++import ca.spottedleaf.dataconverter.types.json.JsonMapType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.gson.JsonObject; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import net.minecraft.nbt.CompoundTag; ++ ++public final class MCDataConverter { ++ ++ private static final LongArrayList BREAKPOINTS = MCVersionRegistry.getBreakpoints(); ++ ++ public static T copy(final T type) { ++ if (type instanceof CompoundTag) { ++ return (T)((CompoundTag)type).copy(); ++ } else if (type instanceof JsonObject) { ++ return (T)((JsonObject)type).deepCopy(); ++ } ++ ++ return type; ++ } ++ ++ public static CompoundTag convertTag(final MCDataType type, final CompoundTag data, final int fromVersion, final int toVersion) { ++ final NBTMapType wrapped = new NBTMapType(data); ++ ++ final NBTMapType replaced = (NBTMapType)convert(type, wrapped, fromVersion, toVersion); ++ ++ return replaced == null ? wrapped.getTag() : replaced.getTag(); ++ } ++ ++ public static JsonObject convertJson(final MCDataType type, final JsonObject data, final boolean compressed, final int fromVersion, final int toVersion) { ++ final JsonMapType wrapped = new JsonMapType(data, compressed); ++ ++ final JsonMapType replaced = (JsonMapType)convert(type, wrapped, fromVersion, toVersion); ++ ++ return replaced == null ? wrapped.getJson() : replaced.getJson(); ++ } ++ ++ public static R convert(final DataType type, final T data, int fromVersion, final int toVersion) { ++ Object ret = data; ++ ++ long currentVersion = DataConverter.encodeVersions(fromVersion < 99 ? 99 : fromVersion, Integer.MAX_VALUE); ++ final long nextVersion = DataConverter.encodeVersions(toVersion, Integer.MAX_VALUE); ++ ++ for (int i = 0, len = BREAKPOINTS.size(); i < len; ++i) { ++ final long breakpoint = BREAKPOINTS.getLong(i); ++ ++ if (currentVersion >= breakpoint) { ++ continue; ++ } ++ ++ final Object converted = type.convert((T)ret, currentVersion, Math.min(nextVersion, breakpoint - 1)); ++ if (converted != null) { ++ ret = converted; ++ } ++ ++ currentVersion = Math.min(nextVersion, breakpoint - 1); ++ ++ if (currentVersion == nextVersion) { ++ break; ++ } ++ } ++ ++ if (currentVersion != nextVersion) { ++ final Object converted = type.convert((T)ret, currentVersion, nextVersion); ++ if (converted != null) { ++ ret = converted; ++ } ++ } ++ ++ return (R)ret; ++ } ++ ++ private MCDataConverter() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6817eef740b4dd0ebc00857085f7f01e6d2b7aa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java +@@ -0,0 +1,363 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; ++import it.unimi.dsi.fastutil.ints.IntRBTreeSet; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongComparator; ++import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import org.slf4j.Logger; ++import java.lang.reflect.Field; ++import java.util.Arrays; ++import java.util.Locale; ++ ++public final class MCVersionRegistry { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final Int2ObjectLinkedOpenHashMap VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>(); ++ protected static final IntArrayList VERSION_LIST; ++ protected static final LongArrayList DATA_VERSION_LIST; ++ ++ protected static final IntArrayList DATACONVERTER_VERSIONS_LIST; ++ protected static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet(); ++ protected static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet(); ++ protected static final Int2ObjectLinkedOpenHashMap SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>(); ++ protected static final LongArrayList BREAKPOINTS = new LongArrayList(); ++ static { ++ // Note: Some of these are nameless. ++ // Unless a data version is specified here, it will NOT have converters ran for it. Please add them on update! ++ final int[] converterVersions = new int[] { ++ 99, ++ 100, ++ 101, ++ 102, ++ 105, ++ 106, ++ 107, ++ 108, ++ 109, ++ 110, ++ 111, ++ 113, ++ 135, ++ 143, ++ 147, ++ 165, ++ 501, ++ 502, ++ 505, ++ 700, ++ 701, ++ 702, ++ 703, ++ 704, ++ 705, ++ 804, ++ 806, ++ 808, ++ 808, ++ 813, ++ 816, ++ 820, ++ 1022, ++ 1125, ++ 1344, ++ 1446, ++ 1450, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1451, ++ 1456, ++ 1458, ++ 1460, ++ 1466, ++ 1470, ++ 1474, ++ 1475, ++ 1480, ++ 1481, ++ 1483, ++ 1484, ++ 1486, ++ 1487, ++ 1488, ++ 1490, ++ 1492, ++ 1494, ++ 1496, ++ 1500, ++ 1501, ++ 1502, ++ 1506, ++ 1510, ++ 1514, ++ 1515, ++ 1624, ++ 1800, ++ 1801, ++ 1802, ++ 1803, ++ 1904, ++ 1905, ++ 1906, ++ 1909, ++ 1911, ++ 1914, ++ 1917, ++ 1918, ++ 1920, ++ 1925, ++ 1928, ++ 1929, ++ 1931, ++ 1936, ++ 1946, ++ 1948, ++ 1953, ++ 1955, ++ 1961, ++ 1963, ++ 2100, ++ 2202, ++ 2209, ++ 2211, ++ 2218, ++ 2501, ++ 2502, ++ 2503, ++ 2505, ++ 2508, ++ 2509, ++ 2511, ++ 2514, ++ 2516, ++ 2518, ++ 2519, ++ 2522, ++ 2523, ++ 2527, ++ 2528, ++ 2529, ++ 2531, ++ 2533, ++ 2535, ++ 2550, ++ 2551, ++ 2552, ++ 2553, ++ 2558, ++ 2568, ++ 2671, ++ 2679, ++ 2680, ++ 2684, ++ 2686, ++ 2688, ++ 2690, ++ 2691, ++ 2693, ++ 2696, ++ 2700, ++ 2701, ++ 2702, ++ 2704, ++ 2707, ++ 2710, ++ 2717, ++ 2825, ++ 2831, ++ 2832, ++ 2833, ++ 2838, ++ 2841, ++ 2842, ++ 2843, ++ 2846, ++ 2852, ++ 2967, ++ 2970, ++ 3077, ++ 3078, ++ 3081, ++ 3082, ++ 3083, ++ 3084, ++ 3086, ++ 3087, ++ 3088, ++ 3090, ++ 3093, ++ 3094, ++ 3097, ++ 3108 ++ // All up to 1.19.1-pre2 ++ }; ++ Arrays.sort(converterVersions); ++ ++ DATACONVERTER_VERSIONS_MAJOR.addAll(DATACONVERTER_VERSIONS_LIST = new IntArrayList(converterVersions)); ++ ++ // add sub versions ++ registerSubVersion(MCVersions.V16W38A + 1, 1); ++ ++ registerSubVersion(MCVersions.V17W47A, 1); ++ registerSubVersion(MCVersions.V17W47A, 2); ++ registerSubVersion(MCVersions.V17W47A, 3); ++ registerSubVersion(MCVersions.V17W47A, 4); ++ registerSubVersion(MCVersions.V17W47A, 5); ++ registerSubVersion(MCVersions.V17W47A, 6); ++ registerSubVersion(MCVersions.V17W47A, 7); ++ ++ // register breakpoints here ++ // for all major releases after 1.16, add them. this reduces the work required to determine if a breakpoint ++ // is needed for new converters ++ ++ // Too much changed in this version. ++ registerBreakpoint(MCVersions.V17W47A); ++ registerBreakpoint(MCVersions.V17W47A, Integer.MAX_VALUE); ++ ++ // final release of major version ++ registerBreakpoint(MCVersions.V1_17_1, Integer.MAX_VALUE); ++ ++ // final release of major version ++ registerBreakpoint(MCVersions.V1_18_2, Integer.MAX_VALUE); ++ ++ ++ } ++ ++ static { ++ final Field[] fields = MCVersions.class.getDeclaredFields(); ++ for (final Field field : fields) { ++ final String name = field.getName(); ++ final int value; ++ try { ++ value = field.getInt(null); ++ } catch (final Exception ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ if (VERSION_NAMES.containsKey(value) && value != MCVersions.V15W33B) { // Mojang registered 15w33a and 15w33b under the same id. ++ LOGGER.warn("Error registering version \"" + name + "\", version number '" + value + "' is already associated with \"" + VERSION_NAMES.get(value) + "\""); ++ } ++ ++ VERSION_NAMES.put(value, name.substring(1).replace("_PRE", "-PRE").replace("_RC", "-RC").replace('_', '.').toLowerCase(Locale.ROOT)); ++ } ++ ++ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { ++ if (VERSION_NAMES.containsKey(version)) { ++ continue; ++ } ++ ++ // find closest greatest version above this one ++ int closest = Integer.MAX_VALUE; ++ String closestName = null; ++ for (final int v : VERSION_NAMES.keySet()) { ++ if (v > version && v < closest) { ++ closest = v; ++ closestName = VERSION_NAMES.get(v); ++ } ++ } ++ ++ if (closestName == null) { ++ VERSION_NAMES.put(version, "unregistered_v" + version); ++ } else { ++ VERSION_NAMES.put(version, closestName + "-dev" + (closest - version)); ++ } ++ } ++ ++ // Explicit override for V99, as 99 is very special. ++ VERSION_NAMES.put(99, "pre_converter"); ++ ++ VERSION_LIST = new IntArrayList(new IntRBTreeSet(VERSION_NAMES.keySet())); ++ ++ DATA_VERSION_LIST = new LongArrayList(); ++ for (final int version : VERSION_LIST) { ++ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, 0)); ++ ++ final IntArrayList subVersions = SUBVERSIONS.get(version); ++ if (subVersions == null) { ++ continue; ++ } ++ ++ for (final int step : subVersions) { ++ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, step)); ++ } ++ } ++ ++ DATA_VERSION_LIST.sort((LongComparator)null); ++ ++ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { ++ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, 0)); ++ ++ final IntArrayList subVersions = SUBVERSIONS.get(version); ++ if (subVersions == null) { ++ continue; ++ } ++ ++ for (final int step : subVersions) { ++ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, step)); ++ } ++ } ++ } ++ ++ private static void registerSubVersion(final int version, final int step) { ++ if (DATA_VERSION_LIST != null) { ++ throw new IllegalStateException("Added too late!"); ++ } ++ SUBVERSIONS.computeIfAbsent(version, (final int keyInMap) -> { ++ return new IntArrayList(); ++ }).add(step); ++ } ++ ++ private static void registerBreakpoint(final int version) { ++ registerBreakpoint(version, 0); ++ } ++ ++ private static void registerBreakpoint(final int version, final int step) { ++ BREAKPOINTS.add(DataConverter.encodeVersions(version, step)); ++ } ++ ++ // returns only versions that have dataconverters ++ public static boolean hasDataConverters(final int version) { ++ return DATACONVERTER_VERSIONS_MAJOR.contains(version); ++ } ++ ++ public String getVersionName(final int version) { ++ return VERSION_NAMES.get(version); ++ } ++ ++ public boolean isRegisteredVersion(final int version) { ++ return VERSION_NAMES.containsKey(version); ++ } ++ ++ public static IntArrayList getVersionList() { ++ return VERSION_LIST; ++ } ++ ++ public static LongArrayList getDataVersionList() { ++ return DATA_VERSION_LIST; ++ } ++ ++ public static int getMaxVersion() { ++ return VERSION_LIST.getInt(VERSION_LIST.size() - 1); ++ } ++ ++ public static LongArrayList getBreakpoints() { ++ return BREAKPOINTS; ++ } ++ ++ public static void checkVersion(final long version) { ++ if (!DATACONVERTER_VERSIONS.contains(version)) { ++ throw new IllegalStateException("Version " + DataConverter.encodedToString(version) + " is not registered to have dataconverters, yet has a dataconverter"); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java +new file mode 100644 +index 0000000000000000000000000000000000000000..51f869ba58b3f113017a3c32f68896b6d572dc7b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java +@@ -0,0 +1,423 @@ ++package ca.spottedleaf.dataconverter.minecraft; ++ ++@SuppressWarnings("unused") ++public final class MCVersions { ++ ++ /* https://minecraft.fandom.com/wiki/Data_version */ ++ ++ public static final int V15W32A = 100; ++ public static final int V15W32B = 103; ++ public static final int V15W32C = 104; ++ public static final int V15W33A = 111; ++ public static final int V15W33B = 111; ++ public static final int V15W33C = 112; ++ public static final int V15W34A = 114; ++ public static final int V15W34B = 115; ++ public static final int V15W34C = 116; ++ public static final int V15W34D = 117; ++ public static final int V15W35A = 118; ++ public static final int V15W35B = 119; ++ public static final int V15W35C = 120; ++ public static final int V15W35D = 121; ++ public static final int V15W35E = 122; ++ public static final int V15W36A = 123; ++ public static final int V15W36B = 124; ++ public static final int V15W36C = 125; ++ public static final int V15W36D = 126; ++ public static final int V15W37A = 127; ++ public static final int V15W38A = 128; ++ public static final int V15W38B = 129; ++ public static final int V15W39A = 130; ++ public static final int V15W39B = 131; ++ public static final int V15W39C = 132; ++ public static final int V15W40A = 133; ++ public static final int V15W40B = 134; ++ public static final int V15W41A = 136; ++ public static final int V15W41B = 137; ++ public static final int V15W42A = 138; ++ public static final int V15W43A = 139; ++ public static final int V15W43B = 140; ++ public static final int V15W43C = 141; ++ public static final int V15W44A = 142; ++ public static final int V15W44B = 143; ++ public static final int V15W45A = 145; ++ public static final int V15W46A = 146; ++ public static final int V15W47A = 148; ++ public static final int V15W47B = 149; ++ public static final int V15W47C = 150; ++ public static final int V15W49A = 151; ++ public static final int V15W49B = 152; ++ public static final int V15W50A = 153; ++ public static final int V15W51A = 154; ++ public static final int V15W51B = 155; ++ public static final int V16W02A = 156; ++ public static final int V16W03A = 157; ++ public static final int V16W04A = 158; ++ public static final int V16W05A = 159; ++ public static final int V16W05B = 160; ++ public static final int V16W06A = 161; ++ public static final int V16W07A = 162; ++ public static final int V16W07B = 163; ++ public static final int V1_9_PRE1 = 164; ++ public static final int V1_9_PRE2 = 165; ++ public static final int V1_9_PRE3 = 167; ++ public static final int V1_9_PRE4 = 168; ++ public static final int V1_9 = 169; ++ public static final int V1_9_1_PRE1 = 170; ++ public static final int V1_9_1_PRE2 = 171; ++ public static final int V1_9_1_PRE3 = 172; ++ public static final int V1_9_1 = 175; ++ public static final int V1_9_2 = 176; ++ public static final int V16W14A = 177; ++ public static final int V16W15A = 178; ++ public static final int V16W15B = 179; ++ public static final int V1_9_3_PRE1 = 180; ++ public static final int V1_9_3_PRE2 = 181; ++ public static final int V1_9_3_PRE3 = 182; ++ public static final int V1_9_3 = 183; ++ public static final int V1_9_4 = 184; ++ public static final int V16W20A = 501; ++ public static final int V16W21A = 503; ++ public static final int V16W21B = 504; ++ public static final int V1_10_PRE1 = 506; ++ public static final int V1_10_PRE2 = 507; ++ public static final int V1_10 = 510; ++ public static final int V1_10_1 = 511; ++ public static final int V1_10_2 = 512; ++ public static final int V16W32A = 800; ++ public static final int V16W32B = 801; ++ public static final int V16W33A = 802; ++ public static final int V16W35A = 803; ++ public static final int V16W36A = 805; ++ public static final int V16W38A = 807; ++ public static final int V16W39A = 809; ++ public static final int V16W39B = 811; ++ public static final int V16W39C = 812; ++ public static final int V16W40A = 813; ++ public static final int V16W41A = 814; ++ public static final int V16W42A = 815; ++ public static final int V16W43A = 816; ++ public static final int V16W44A = 817; ++ public static final int V1_11_PRE1 = 818; ++ public static final int V1_11 = 819; ++ public static final int V16W50A = 920; ++ public static final int V1_11_1 = 921; ++ public static final int V1_11_2 = 922; ++ public static final int V17W06A = 1022; ++ public static final int V17W13A = 1122; ++ public static final int V17W13B = 1123; ++ public static final int V17W14A = 1124; ++ public static final int V17W15A = 1125; ++ public static final int V17W16A = 1126; ++ public static final int V17W16B = 1127; ++ public static final int V17W17A = 1128; ++ public static final int V17W17B = 1129; ++ public static final int V17W18A = 1130; ++ public static final int V17W18B = 1131; ++ public static final int V1_12_PRE1 = 1132; ++ public static final int V1_12_PRE2 = 1133; ++ public static final int V1_12_PRE3 = 1134; ++ public static final int V1_12_PRE4 = 1135; ++ public static final int V1_12_PRE5 = 1136; ++ public static final int V1_12_PRE6 = 1137; ++ public static final int V1_12_PRE7 = 1138; ++ public static final int V1_12 = 1139; ++ public static final int V17W31A = 1239; ++ public static final int V1_12_1_PRE1 = 1240; ++ public static final int V1_12_1 = 1241; ++ public static final int V1_12_2_PRE1 = 1341; ++ public static final int V1_12_2_PRE2 = 1342; ++ public static final int V1_12_2 = 1343; ++ public static final int V17W43A = 1444; ++ public static final int V17W43B = 1445; ++ public static final int V17W45A = 1447; ++ public static final int V17W45B = 1448; ++ public static final int V17W46A = 1449; ++ public static final int V17W47A = 1451; ++ public static final int V17W47B = 1452; ++ public static final int V17W48A = 1453; ++ public static final int V17W49A = 1454; ++ public static final int V17W49B = 1455; ++ public static final int V17W50A = 1457; ++ public static final int V18W01A = 1459; ++ public static final int V18W02A = 1461; ++ public static final int V18W03A = 1462; ++ public static final int V18W03B = 1463; ++ public static final int V18W05A = 1464; ++ public static final int V18W06A = 1466; ++ public static final int V18W07A = 1467; ++ public static final int V18W07B = 1468; ++ public static final int V18W07C = 1469; ++ public static final int V18W08A = 1470; ++ public static final int V18W08B = 1471; ++ public static final int V18W09A = 1472; ++ public static final int V18W10A = 1473; ++ public static final int V18W10B = 1474; ++ public static final int V18W10C = 1476; ++ public static final int V18W10D = 1477; ++ public static final int V18W11A = 1478; ++ public static final int V18W14A = 1479; ++ public static final int V18W14B = 1481; ++ public static final int V18W15A = 1482; ++ public static final int V18W16A = 1483; ++ public static final int V18W19A = 1484; ++ public static final int V18W19B = 1485; ++ public static final int V18W20A = 1489; ++ public static final int V18W20B = 1491; ++ public static final int V18W20C = 1493; ++ public static final int V18W21A = 1495; ++ public static final int V18W21B = 1496; ++ public static final int V18W22A = 1497; ++ public static final int V18W22B = 1498; ++ public static final int V18W22C = 1499; ++ public static final int V1_13_PRE1 = 1501; ++ public static final int V1_13_PRE2 = 1502; ++ public static final int V1_13_PRE3 = 1503; ++ public static final int V1_13_PRE4 = 1504; ++ public static final int V1_13_PRE5 = 1511; ++ public static final int V1_13_PRE6 = 1512; ++ public static final int V1_13_PRE7 = 1513; ++ public static final int V1_13_PRE8 = 1516; ++ public static final int V1_13_PRE9 = 1517; ++ public static final int V1_13_PRE10 = 1518; ++ public static final int V1_13 = 1519; ++ public static final int V18W30A = 1620; ++ public static final int V18W30B = 1621; ++ public static final int V18W31A = 1622; ++ public static final int V18W32A = 1623; ++ public static final int V18W33A = 1625; ++ public static final int V1_13_1_PRE1 = 1626; ++ public static final int V1_13_1_PRE2 = 1627; ++ public static final int V1_13_1 = 1628; ++ public static final int V1_13_2_PRE1 = 1629; ++ public static final int V1_13_2_PRE2 = 1630; ++ public static final int V1_13_2 = 1631; ++ public static final int V18W43A = 1901; ++ public static final int V18W43B = 1902; ++ public static final int V18W43C = 1903; ++ public static final int V18W44A = 1907; ++ public static final int V18W45A = 1908; ++ public static final int V18W46A = 1910; ++ public static final int V18W47A = 1912; ++ public static final int V18W47B = 1913; ++ public static final int V18W48A = 1914; ++ public static final int V18W48B = 1915; ++ public static final int V18W49A = 1916; ++ public static final int V18W50A = 1919; ++ public static final int V19W02A = 1921; ++ public static final int V19W03A = 1922; ++ public static final int V19W03B = 1923; ++ public static final int V19W03C = 1924; ++ public static final int V19W04A = 1926; ++ public static final int V19W04B = 1927; ++ public static final int V19W05A = 1930; ++ public static final int V19W06A = 1931; ++ public static final int V19W07A = 1932; ++ public static final int V19W08A = 1933; ++ public static final int V19W08B = 1934; ++ public static final int V19W09A = 1935; ++ public static final int V19W11A = 1937; ++ public static final int V19W11B = 1938; ++ public static final int V19W12A = 1940; ++ public static final int V19W12B = 1941; ++ public static final int V19W13A = 1942; ++ public static final int V19W13B = 1943; ++ public static final int V19W14A = 1944; ++ public static final int V19W14B = 1945; ++ public static final int V1_14_PRE1 = 1947; ++ public static final int V1_14_PRE2 = 1948; ++ public static final int V1_14_PRE3 = 1949; ++ public static final int V1_14_PRE4 = 1950; ++ public static final int V1_14_PRE5 = 1951; ++ public static final int V1_14 = 1952; ++ public static final int V1_14_1_PRE1 = 1955; ++ public static final int V1_14_1_PRE2 = 1956; ++ public static final int V1_14_1 = 1957; ++ public static final int V1_14_2_PRE1 = 1958; ++ public static final int V1_14_2_PRE2 = 1959; ++ public static final int V1_14_2_PRE3 = 1960; ++ public static final int V1_14_2_PRE4 = 1962; ++ public static final int V1_14_2 = 1963; ++ public static final int V1_14_3_PRE1 = 1964; ++ public static final int V1_14_3_PRE2 = 1965; ++ public static final int V1_14_3_PRE3 = 1966; ++ public static final int V1_14_3_PRE4 = 1967; ++ public static final int V1_14_3 = 1968; ++ public static final int V1_14_4_PRE1 = 1969; ++ public static final int V1_14_4_PRE2 = 1970; ++ public static final int V1_14_4_PRE3 = 1971; ++ public static final int V1_14_4_PRE4 = 1972; ++ public static final int V1_14_4_PRE5 = 1973; ++ public static final int V1_14_4_PRE6 = 1974; ++ public static final int V1_14_4_PRE7 = 1975; ++ public static final int V1_14_4 = 1976; ++ public static final int V19W34A = 2200; ++ public static final int V19W35A = 2201; ++ public static final int V19W36A = 2203; ++ public static final int V19W37A = 2204; ++ public static final int V19W38A = 2205; ++ public static final int V19W38B = 2206; ++ public static final int V19W39A = 2207; ++ public static final int V19W40A = 2208; ++ public static final int V19W41A = 2210; ++ public static final int V19W42A = 2212; ++ public static final int V19W44A = 2213; ++ public static final int V19W45A = 2214; ++ public static final int V19W45B = 2215; ++ public static final int V19W46A = 2216; ++ public static final int V19W46B = 2217; ++ public static final int V1_15_PRE1 = 2218; ++ public static final int V1_15_PRE2 = 2219; ++ public static final int V1_15_PRE3 = 2220; ++ public static final int V1_15_PRE4 = 2221; ++ public static final int V1_15_PRE5 = 2222; ++ public static final int V1_15_PRE6 = 2223; ++ public static final int V1_15_PRE7 = 2224; ++ public static final int V1_15 = 2225; ++ public static final int V1_15_1_PRE1 = 2226; ++ public static final int V1_15_1 = 2227; ++ public static final int V1_15_2_PRE1 = 2228; ++ public static final int V1_15_2_PRE2 = 2229; ++ public static final int V1_15_2 = 2230; ++ public static final int V20W06A = 2504; ++ public static final int V20W07A = 2506; ++ public static final int V20W08A = 2507; ++ public static final int V20W09A = 2510; ++ public static final int V20W10A = 2512; ++ public static final int V20W11A = 2513; ++ public static final int V20W12A = 2515; ++ public static final int V20W13A = 2520; ++ public static final int V20W13B = 2521; ++ public static final int V20W14A = 2524; ++ public static final int V20W15A = 2525; ++ public static final int V20W16A = 2526; ++ public static final int V20W17A = 2529; ++ public static final int V20W18A = 2532; ++ public static final int V20W19A = 2534; ++ public static final int V20W20A = 2536; ++ public static final int V20W20B = 2537; ++ public static final int V20W21A = 2554; ++ public static final int V20W22A = 2555; ++ public static final int V1_16_PRE1 = 2556; ++ public static final int V1_16_PRE2 = 2557; ++ public static final int V1_16_PRE3 = 2559; ++ public static final int V1_16_PRE4 = 2560; ++ public static final int V1_16_PRE5 = 2561; ++ public static final int V1_16_PRE6 = 2562; ++ public static final int V1_16_PRE7 = 2563; ++ public static final int V1_16_PRE8 = 2564; ++ public static final int V1_16_RC1 = 2565; ++ public static final int V1_16 = 2566; ++ public static final int V1_16_1 = 2567; ++ public static final int V20W27A = 2569; ++ public static final int V20W28A = 2570; ++ public static final int V20W29A = 2571; ++ public static final int V20W30A = 2572; ++ public static final int V1_16_2_PRE1 = 2573; ++ public static final int V1_16_2_PRE2 = 2574; ++ public static final int V1_16_2_PRE3 = 2575; ++ public static final int V1_16_2_RC1 = 2576; ++ public static final int V1_16_2_RC2 = 2577; ++ public static final int V1_16_2 = 2578; ++ public static final int V1_16_3_RC1 = 2579; ++ public static final int V1_16_3 = 2580; ++ public static final int V1_16_4_PRE1 = 2581; ++ public static final int V1_16_4_PRE2 = 2582; ++ public static final int V1_16_4_RC1 = 2583; ++ public static final int V1_16_4 = 2584; ++ public static final int V1_16_5_RC1 = 2585; ++ public static final int V1_16_5 = 2586; ++ public static final int V20W45A = 2681; ++ public static final int V20W46A = 2682; ++ public static final int V20W48A = 2683; ++ public static final int V20W49A = 2685; ++ public static final int V20W51A = 2687; ++ public static final int V21W03A = 2689; ++ public static final int V21W05A = 2690; ++ public static final int V21W05B = 2692; ++ public static final int V21W06A = 2694; ++ public static final int V21W07A = 2695; ++ public static final int V21W08A = 2697; ++ public static final int V21W08B = 2698; ++ public static final int V21W10A = 2699; ++ public static final int V21W11A = 2703; ++ public static final int V21W13A = 2705; ++ public static final int V21W14A = 2706; ++ public static final int V21W15A = 2709; ++ public static final int V21W16A = 2711; ++ public static final int V21W17A = 2712; ++ public static final int V21W18A = 2713; ++ public static final int V21W19A = 2714; ++ public static final int V21W20A = 2715; ++ public static final int V1_17_PRE1 = 2716; ++ public static final int V1_17_PRE2 = 2718; ++ public static final int V1_17_PRE3 = 2719; ++ public static final int V1_17_PRE4 = 2720; ++ public static final int V1_17_PRE5 = 2721; ++ public static final int V1_17_RC1 = 2722; ++ public static final int V1_17_RC2 = 2723; ++ public static final int V1_17 = 2724; ++ public static final int V1_17_1_PRE1 = 2725; ++ public static final int V1_17_1_PRE2 = 2726; ++ public static final int V1_17_1_PRE3 = 2727; ++ public static final int V1_17_1_RC1 = 2728; ++ public static final int V1_17_1_RC2 = 2729; ++ public static final int V1_17_1 = 2730; ++ public static final int V21W37A = 2834; ++ public static final int V21W38A = 2835; ++ public static final int V21W39A = 2836; ++ public static final int V21W40A = 2838; ++ public static final int V21W41A = 2839; ++ public static final int V21W42A = 2840; ++ public static final int V21W43A = 2844; ++ public static final int V21W44A = 2845; ++ public static final int V1_18_PRE1 = 2847; ++ public static final int V1_18_PRE2 = 2848; ++ public static final int V1_18_PRE3 = 2849; ++ public static final int V1_18_PRE4 = 2850; ++ public static final int V1_18_PRE5 = 2851; ++ public static final int V1_18_PRE6 = 2853; ++ public static final int V1_18_PRE7 = 2854; ++ public static final int V1_18_PRE8 = 2855; ++ public static final int V1_18_RC1 = 2856; ++ public static final int V1_18_RC2 = 2857; ++ public static final int V1_18_RC3 = 2858; ++ public static final int V1_18_RC4 = 2859; ++ public static final int V1_18 = 2860; ++ public static final int V1_18_1_PRE1 = 2861; ++ public static final int V1_18_1_RC1 = 2862; ++ public static final int V1_18_1_RC2 = 2863; ++ public static final int V1_18_1_RC3 = 2864; ++ public static final int V1_18_1 = 2865; ++ public static final int V22W03A = 2966; ++ public static final int V22W05A = 2967; ++ public static final int V22W06A = 2968; ++ public static final int V22W07A = 2969; ++ public static final int V1_18_2_PRE1 = 2971; ++ public static final int V1_18_2_PRE2 = 2972; ++ public static final int V1_18_2_PRE3 = 2973; ++ public static final int V1_18_2_RC1 = 2974; ++ public static final int V1_18_2 = 2975; ++ public static final int V22W11A = 3080; ++ public static final int V22W12A = 3082; ++ public static final int V22W13A = 3085; ++ public static final int V22W14A = 3088; ++ public static final int V22W15A = 3089; ++ public static final int V22W16A = 3091; ++ public static final int V22W16B = 3092; ++ public static final int V22W17A = 3093; ++ public static final int V22W18A = 3095; ++ public static final int V22W19A = 3096; ++ public static final int V1_19_PRE1 = 3098; ++ public static final int V1_19_PRE2 = 3099; ++ public static final int V1_19_PRE3 = 3100; ++ public static final int V1_19_PRE4 = 3101; ++ public static final int V1_19_PRE5 = 3102; ++ public static final int V1_19_RC1 = 3103; ++ public static final int V1_19_RC2 = 3104; ++ public static final int V1_19 = 3105; ++ public static final int V22W24A = 3106; ++ public static final int V1_19_1_PRE1 = 3107; ++ public static final int V1_19_1_RC1 = 3109; ++ public static final int V1_19_1_PRE2 = 3110; ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.advancements; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractAdvancementsRename { ++ ++ private ConverterAbstractAdvancementsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameKeys(data, renamer); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.advancements; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterCriteriaRename extends DataConverter, MapType> { ++ ++ public final String path; ++ public final Function conversion; ++ ++ public ConverterCriteriaRename(final int toVersion, final String path, final Function conversion) { ++ super(toVersion); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ public ConverterCriteriaRename(final int toVersion, final int versionStep, final String path, final Function conversion) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType advancement = data.getMap(this.path); ++ if (advancement == null) { ++ return null; ++ } ++ ++ final MapType criteria = advancement.getMap("criteria"); ++ if (criteria == null) { ++ return null; ++ } ++ ++ RenameHelper.renameKeys(criteria, this.conversion); ++ ++ return null; ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ba9daaab1abd53a3fbdebd78e05ba363251188c6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.blockname; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractBlockRename { ++ ++ private ConverterAbstractBlockRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.BLOCK_NAME, renamer); ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String name = data.getString("Name"); ++ if (name != null) { ++ final String converted = renamer.apply(name); ++ if (converted != null) { ++ data.setString("Name", converted); ++ } ++ } ++ return null; ++ } ++ }); ++ } ++ ++ public static void registerAndFixJigsaw(final int version, final Function renamer) { ++ registerAndFixJigsaw(version, 0, renamer); ++ } ++ ++ public static void registerAndFixJigsaw(final int version, final int subVersion, final Function renamer) { ++ register(version, subVersion, renamer); ++ // TODO check on update, minecraft:jigsaw can change ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String finalState = data.getString("final_state"); ++ if (finalState == null || finalState.isEmpty()) { ++ return null; ++ } ++ ++ final int nbtStart1 = finalState.indexOf('['); ++ final int nbtStart2 = finalState.indexOf('{'); ++ int stateNameEnd = finalState.length(); ++ if (nbtStart1 > 0) { ++ stateNameEnd = Math.min(stateNameEnd, nbtStart1); ++ } ++ ++ if (nbtStart2 > 0) { ++ stateNameEnd = Math.min(stateNameEnd, nbtStart2); ++ } ++ ++ final String blockStateName = finalState.substring(0, stateNameEnd); ++ final String converted = renamer.apply(blockStateName); ++ if (converted == null) { ++ return null; ++ } ++ ++ final String convertedState = converted.concat(finalState.substring(stateNameEnd)); ++ data.setString("final_state", convertedState); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java +@@ -0,0 +1,1016 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.chunk; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.datafixers.DataFixUtils; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import net.minecraft.util.datafix.PackedBitStorage; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.BitSet; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.Map; ++import java.util.Objects; ++ ++import static it.unimi.dsi.fastutil.HashCommon.arraySize; ++ ++public final class ConverterFlattenChunk extends DataConverter, MapType> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ static final BitSet VIRTUAL_SET = new BitSet(256); ++ static final BitSet IDS_NEEDING_FIX_SET = new BitSet(256); ++ ++ static { ++ IDS_NEEDING_FIX_SET.set(2); ++ IDS_NEEDING_FIX_SET.set(3); ++ IDS_NEEDING_FIX_SET.set(110); ++ IDS_NEEDING_FIX_SET.set(140); ++ IDS_NEEDING_FIX_SET.set(144); ++ IDS_NEEDING_FIX_SET.set(25); ++ IDS_NEEDING_FIX_SET.set(86); ++ IDS_NEEDING_FIX_SET.set(26); ++ IDS_NEEDING_FIX_SET.set(176); ++ IDS_NEEDING_FIX_SET.set(177); ++ IDS_NEEDING_FIX_SET.set(175); ++ IDS_NEEDING_FIX_SET.set(64); ++ IDS_NEEDING_FIX_SET.set(71); ++ IDS_NEEDING_FIX_SET.set(193); ++ IDS_NEEDING_FIX_SET.set(194); ++ IDS_NEEDING_FIX_SET.set(195); ++ IDS_NEEDING_FIX_SET.set(196); ++ IDS_NEEDING_FIX_SET.set(197); ++ ++ VIRTUAL_SET.set(54); ++ VIRTUAL_SET.set(146); ++ VIRTUAL_SET.set(25); ++ VIRTUAL_SET.set(26); ++ VIRTUAL_SET.set(51); ++ VIRTUAL_SET.set(53); ++ VIRTUAL_SET.set(67); ++ VIRTUAL_SET.set(108); ++ VIRTUAL_SET.set(109); ++ VIRTUAL_SET.set(114); ++ VIRTUAL_SET.set(128); ++ VIRTUAL_SET.set(134); ++ VIRTUAL_SET.set(135); ++ VIRTUAL_SET.set(136); ++ VIRTUAL_SET.set(156); ++ VIRTUAL_SET.set(163); ++ VIRTUAL_SET.set(164); ++ VIRTUAL_SET.set(180); ++ VIRTUAL_SET.set(203); ++ VIRTUAL_SET.set(55); ++ VIRTUAL_SET.set(85); ++ VIRTUAL_SET.set(113); ++ VIRTUAL_SET.set(188); ++ VIRTUAL_SET.set(189); ++ VIRTUAL_SET.set(190); ++ VIRTUAL_SET.set(191); ++ VIRTUAL_SET.set(192); ++ VIRTUAL_SET.set(93); ++ VIRTUAL_SET.set(94); ++ VIRTUAL_SET.set(101); ++ VIRTUAL_SET.set(102); ++ VIRTUAL_SET.set(160); ++ VIRTUAL_SET.set(106); ++ VIRTUAL_SET.set(107); ++ VIRTUAL_SET.set(183); ++ VIRTUAL_SET.set(184); ++ VIRTUAL_SET.set(185); ++ VIRTUAL_SET.set(186); ++ VIRTUAL_SET.set(187); ++ VIRTUAL_SET.set(132); ++ VIRTUAL_SET.set(139); ++ VIRTUAL_SET.set(199); ++ } ++ ++ static final boolean[] VIRTUAL = toBooleanArray(VIRTUAL_SET); ++ static final boolean[] IDS_NEEDING_FIX = toBooleanArray(IDS_NEEDING_FIX_SET); ++ ++ private static boolean[] toBooleanArray(final BitSet set) { ++ final boolean[] ret = new boolean[4096]; ++ for (int i = 0; i < 4096; ++i) { ++ ret[i] = set.get(i); ++ } ++ ++ return ret; ++ } ++ ++ static final MapType PUMPKIN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:pumpkin'}"); ++ static final MapType SNOWY_PODZOL = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:podzol',Properties:{snowy:'true'}}"); ++ static final MapType SNOWY_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:grass_block',Properties:{snowy:'true'}}"); ++ static final MapType SNOWY_MYCELIUM = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); ++ static final MapType UPPER_SUNFLOWER = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:sunflower',Properties:{half:'upper'}}"); ++ static final MapType UPPER_LILAC = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:lilac',Properties:{half:'upper'}}"); ++ static final MapType UPPER_TALL_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:tall_grass',Properties:{half:'upper'}}"); ++ static final MapType UPPER_LARGE_FERN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:large_fern',Properties:{half:'upper'}}"); ++ static final MapType UPPER_ROSE_BUSH = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:rose_bush',Properties:{half:'upper'}}"); ++ static final MapType UPPER_PEONY = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:peony',Properties:{half:'upper'}}"); ++ ++ static final Map> FLOWER_POT_MAP = new HashMap<>(); ++ static { ++ FLOWER_POT_MAP.put("minecraft:air0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:flower_pot'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_poppy'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_blue_orchid'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_allium'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_azure_bluet'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_orange_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower6", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_white_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower7", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_pink_tulip'}")); ++ FLOWER_POT_MAP.put("minecraft:red_flower8", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oxeye_daisy'}")); ++ FLOWER_POT_MAP.put("minecraft:yellow_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dandelion'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oak_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_spruce_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_birch_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_jungle_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_acacia_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:sapling5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dark_oak_sapling'}")); ++ FLOWER_POT_MAP.put("minecraft:red_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_mushroom'}")); ++ FLOWER_POT_MAP.put("minecraft:brown_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_brown_mushroom'}")); ++ FLOWER_POT_MAP.put("minecraft:deadbush0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dead_bush'}")); ++ FLOWER_POT_MAP.put("minecraft:tallgrass2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_fern'}")); ++ FLOWER_POT_MAP.put("minecraft:cactus0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_cactus'}")); // we change default to empty ++ } ++ ++ static final Map> SKULL_MAP = new HashMap<>(); ++ static { ++ mapSkull(SKULL_MAP, 0, "skeleton", "skull"); ++ mapSkull(SKULL_MAP, 1, "wither_skeleton", "skull"); ++ mapSkull(SKULL_MAP, 2, "zombie", "head"); ++ mapSkull(SKULL_MAP, 3, "player", "head"); ++ mapSkull(SKULL_MAP, 4, "creeper", "head"); ++ mapSkull(SKULL_MAP, 5, "dragon", "head"); ++ }; ++ ++ private static void mapSkull(final Map> into, final int oldId, final String newId, final String skullType) { ++ into.put(oldId + "north", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'north'}}")); ++ into.put(oldId + "east", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'east'}}")); ++ into.put(oldId + "south", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'south'}}")); ++ into.put(oldId + "west", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'west'}}")); ++ ++ for (int rotation = 0; rotation < 16; ++rotation) { ++ into.put(oldId + "" + rotation, ++ HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_" + skullType + "',Properties:{rotation:'" + rotation + "'}}")); ++ } ++ } ++ ++ static final Map> DOOR_MAP = new HashMap<>(); ++ static { ++ mapDoor(DOOR_MAP, "oak_door", 1024); ++ mapDoor(DOOR_MAP, "iron_door", 1136); ++ mapDoor(DOOR_MAP, "spruce_door", 3088); ++ mapDoor(DOOR_MAP, "birch_door", 3104); ++ mapDoor(DOOR_MAP, "jungle_door", 3120); ++ mapDoor(DOOR_MAP, "acacia_door", 3136); ++ mapDoor(DOOR_MAP, "dark_oak_door", 3152); ++ }; ++ ++ private static void mapDoor(final Map> into, final String type, final int oldId) { ++ into.put("minecraft:" + type + "eastlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId))); ++ into.put("minecraft:" + type + "eastlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 4))); ++ into.put("minecraft:" + type + "eastlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastupperleftfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 8))); ++ into.put("minecraft:" + type + "eastupperleftfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 10))); ++ into.put("minecraft:" + type + "eastupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "eastupperrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 9))); ++ into.put("minecraft:" + type + "eastupperrightfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 11))); ++ into.put("minecraft:" + type + "eastupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "eastupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 3))); ++ into.put("minecraft:" + type + "northlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 7))); ++ into.put("minecraft:" + type + "northlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "northupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "northupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 1))); ++ into.put("minecraft:" + type + "southlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 5))); ++ into.put("minecraft:" + type + "southlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "southupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "southupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 2))); ++ into.put("minecraft:" + type + "westlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 6))); ++ into.put("minecraft:" + type + "westlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}")); ++ into.put("minecraft:" + type + "westupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}")); ++ into.put("minecraft:" + type + "westupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}")); ++ } ++ ++ static final Map> NOTE_BLOCK_MAP = new HashMap<>(); ++ static { ++ for(int note = 0; note < 26; ++note) { ++ NOTE_BLOCK_MAP.put("true" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'true',note:'" + note + "'}}")); ++ NOTE_BLOCK_MAP.put("false" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'false',note:'" + note + "'}}")); ++ } ++ } ++ ++ static final Int2ObjectOpenHashMap DYE_COLOR_MAP = new Int2ObjectOpenHashMap<>(); ++ static { ++ DYE_COLOR_MAP.put(0, "white"); ++ DYE_COLOR_MAP.put(1, "orange"); ++ DYE_COLOR_MAP.put(2, "magenta"); ++ DYE_COLOR_MAP.put(3, "light_blue"); ++ DYE_COLOR_MAP.put(4, "yellow"); ++ DYE_COLOR_MAP.put(5, "lime"); ++ DYE_COLOR_MAP.put(6, "pink"); ++ DYE_COLOR_MAP.put(7, "gray"); ++ DYE_COLOR_MAP.put(8, "light_gray"); ++ DYE_COLOR_MAP.put(9, "cyan"); ++ DYE_COLOR_MAP.put(10, "purple"); ++ DYE_COLOR_MAP.put(11, "blue"); ++ DYE_COLOR_MAP.put(12, "brown"); ++ DYE_COLOR_MAP.put(13, "green"); ++ DYE_COLOR_MAP.put(14, "red"); ++ DYE_COLOR_MAP.put(15, "black"); ++ } ++ ++ static final Map> BED_BLOCK_MAP = new HashMap<>(); ++ ++ static { ++ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { ++ if (!Objects.equals(entry.getValue(), "red")) { ++ addBeds(BED_BLOCK_MAP, entry.getIntKey(), entry.getValue()); ++ } ++ } ++ } ++ ++ private static void addBeds(final Map> into, final int colourId, final String colourName) { ++ into.put("southfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}")); ++ into.put("westfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}")); ++ into.put("northfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}")); ++ into.put("eastfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}")); ++ into.put("southfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'head'}}")); ++ into.put("westfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'head'}}")); ++ into.put("northfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'head'}}")); ++ into.put("eastfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'head'}}")); ++ into.put("southtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'true',part:'head'}}")); ++ into.put("westtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'true',part:'head'}}")); ++ into.put("northtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'true',part:'head'}}")); ++ into.put("easttruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'true',part:'head'}}")); ++ } ++ ++ static final Map> BANNER_BLOCK_MAP = new HashMap<>(); ++ ++ static { ++ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { ++ if (!Objects.equals(entry.getValue(), "white")) { ++ addBanners(BANNER_BLOCK_MAP, 15 - entry.getIntKey(), entry.getValue()); ++ } ++ } ++ } ++ ++ private static void addBanners(final Map> into, final int colourId, final String colourName) { ++ for(int rotation = 0; rotation < 16; ++rotation) { ++ into.put("" + rotation + "_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_banner',Properties:{rotation:'" + rotation + "'}}")); ++ } ++ ++ into.put("north_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'north'}}")); ++ into.put("south_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'south'}}")); ++ into.put("west_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'west'}}")); ++ into.put("east_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'east'}}")); ++ } ++ ++ static final MapType AIR = Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(0)); ++ ++ public ConverterFlattenChunk() { ++ super(MCVersions.V17W47A, 1); ++ } ++ ++ static String getName(final MapType blockState) { ++ return blockState.getString("Name"); ++ } ++ ++ static String getProperty(final MapType blockState, final String propertyName) { ++ final MapType properties = blockState.getMap("Properties"); ++ if (properties == null) { ++ return ""; ++ } ++ ++ return properties.getString(propertyName, ""); ++ } ++ ++ static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { ++ if (noBack) { ++ if (noRight) { ++ return 2; ++ } else if (noLeft) { ++ return 128; ++ } else { ++ return 1; ++ } ++ } else if (noForward) { ++ if (noLeft) { ++ return 32; ++ } else if (noRight) { ++ return 8; ++ } else { ++ return 16; ++ } ++ } else if (noRight) { ++ return 4; ++ } else if (noLeft) { ++ return 64; ++ } else { ++ return 0; ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ if (!level.hasKey("Sections", ObjectType.LIST)) { ++ return null; ++ } ++ ++ data.setMap("Level", new UpgradeChunk(level).writeBackToLevel()); ++ ++ return null; ++ } ++ ++ static enum Direction { ++ DOWN(AxisDirection.NEGATIVE, Axis.Y), ++ UP(AxisDirection.POSITIVE, Axis.Y), ++ NORTH(AxisDirection.NEGATIVE, Axis.Z), ++ SOUTH(AxisDirection.POSITIVE, Axis.Z), ++ WEST(AxisDirection.NEGATIVE, Axis.X), ++ EAST(AxisDirection.POSITIVE, Axis.X); ++ ++ private final Axis axis; ++ private final AxisDirection axisDirection; ++ ++ private Direction(final AxisDirection axisDirection, final Axis axis) { ++ this.axis = axis; ++ this.axisDirection = axisDirection; ++ } ++ ++ public AxisDirection getAxisDirection() { ++ return this.axisDirection; ++ } ++ ++ public Axis getAxis() { ++ return this.axis; ++ } ++ ++ public static enum AxisDirection { ++ POSITIVE(1), ++ NEGATIVE(-1); ++ ++ private final int step; ++ ++ private AxisDirection(final int step) { ++ this.step = step; ++ } ++ ++ public int getStep() { ++ return this.step; ++ } ++ } ++ ++ public static enum Axis { ++ X, Y, Z; ++ } ++ } ++ ++ static class DataLayer { ++ private final byte[] data; ++ ++ public DataLayer() { ++ this.data = new byte[2048]; ++ } ++ ++ public DataLayer(final byte[] data) { ++ this.data = data; ++ if (data.length != 2048) { ++ throw new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + data.length); ++ } ++ } ++ ++ public static DataLayer getOrNull(final byte[] data) { ++ return data == null ? null : new DataLayer(data); ++ } ++ ++ public static DataLayer getOrCreate(final byte[] data) { ++ return data == null ? new DataLayer() : new DataLayer(data); ++ } ++ ++ public int get(final int index) { ++ final byte value = this.data[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ public int get(final int x, final int y, final int z) { ++ final int index = y << 8 | z << 4 | x; ++ final byte value = this.data[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ } ++ ++ static final class UpgradeChunk { ++ int sides; ++ ++ final Section[] sections = new Section[16]; ++ final MapType level; ++ final int blockX; ++ final int blockZ; ++ final Int2ObjectLinkedOpenHashMap> tileEntities = new Int2ObjectLinkedOpenHashMap<>(16); ++ ++ public UpgradeChunk(final MapType level) { ++ this.level = level; ++ this.blockX = level.getInt("xPos") << 4; ++ this.blockZ = level.getInt("zPos") << 4; ++ ++ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ if (tileEntities != null) { ++ for (int i = 0, len = tileEntities.size(); i < len; ++i) { ++ final MapType tileEntity = tileEntities.getMap(i); ++ ++ final int x = (tileEntity.getInt("x") - this.blockX) & 15; ++ final int y = tileEntity.getInt("y"); ++ final int z = (tileEntity.getInt("z") - this.blockZ) & 15; ++ final int index = (y << 8) | (z << 4) | x; ++ if (this.tileEntities.put(index, tileEntity) != null) { ++ LOGGER.warn("In chunk: {}x{} found a duplicate block entity at position (ConverterFlattenChunk): [{}, {}, {}]", this.blockX, this.blockZ, x, y, z); ++ } ++ } ++ } ++ ++ final boolean convertedFromAlphaFormat = level.getBoolean("convertedFromAlphaFormat"); ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType sectionData = sections.getMap(i); ++ final Section section = new Section(sectionData); ++ ++ if (section.y < 0 || section.y > 15) { ++ LOGGER.warn("In chunk: {}x{} found an invalid chunk section y (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); ++ continue; ++ } ++ ++ if (this.sections[section.y] != null) { ++ LOGGER.warn("In chunk: {}x{} found a duplicate chunk section (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); ++ } ++ ++ this.sides = section.upgrade(this.sides); ++ this.sections[section.y] = section; ++ } ++ } ++ ++ for (final Section section : this.sections) { ++ if (section == null) { ++ continue; ++ } ++ ++ final int yIndex = section.y << (8 + 4); ++ ++ for (final Iterator> iterator = section.toFix.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ final Int2ObjectMap.Entry fixEntry = iterator.next(); ++ final IntIterator positionIterator = fixEntry.getValue().iterator(); ++ switch (fixEntry.getIntKey()) { ++ case 2: { // grass block ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"minecraft:grass_block".equals(getName(blockState))) { ++ continue; ++ } ++ ++ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { ++ this.setBlock(position, SNOWY_GRASS); ++ } ++ } ++ break; ++ } ++ case 3: { // dirt ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"minecraft:podzol".equals(getName(blockState))) { ++ continue; ++ } ++ ++ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { ++ this.setBlock(position, SNOWY_PODZOL); ++ } ++ } ++ break; ++ } ++ case 25: { // note block ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.removeBlockEntity(position); ++ if (tile != null) { ++ final String state = Boolean.toString(tile.getBoolean("powered")) + (byte) Math.min(Math.max(tile.getInt("note"), 0), 24); ++ this.setBlock(position, NOTE_BLOCK_MAP.getOrDefault(state, NOTE_BLOCK_MAP.get("false0"))); ++ } ++ } ++ break; ++ } ++ case 26: { // bed ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ ++ if (tile == null) { ++ continue; ++ } ++ ++ final MapType blockState = this.getBlock(position); ++ ++ final int colour = tile.getInt("color"); ++ if (colour != 14 && colour >= 0 && colour < 16) { ++ final String state = getProperty(blockState, "facing") + getProperty(blockState, "occupied") + getProperty(blockState, "part") + colour; ++ ++ final MapType update = BED_BLOCK_MAP.get(state); ++ if (update != null) { ++ this.setBlock(position, update); ++ } ++ } ++ } ++ break; ++ } ++ case 64: // oak door ++ case 71: // iron door ++ case 193: // spruce door ++ case 194: // birch door ++ case 195: // jungle door ++ case 196: // acacia door ++ case 197: { // dark oak door ++ // aka the door updater ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!getName(blockState).endsWith("_door")) { ++ continue; ++ } ++ ++ if (!"lower".equals(getProperty(blockState, "half"))) { ++ continue; ++ } ++ ++ final int positionAbove = relative(position, Direction.UP); ++ final MapType blockStateAbove = this.getBlock(positionAbove); ++ ++ final String name = getName(blockState); ++ if (name.equals(getName(blockStateAbove))) { ++ final String facingBelow = getProperty(blockState, "facing"); ++ final String openBelow = getProperty(blockState, "open"); ++ final String hingeAbove = convertedFromAlphaFormat ? "left" : getProperty(blockStateAbove, "hinge"); ++ final String poweredAbove = convertedFromAlphaFormat ? "false" : getProperty(blockStateAbove, "powered"); ++ ++ this.setBlock(position, DOOR_MAP.get(name + facingBelow + "lower" + hingeAbove + openBelow + poweredAbove)); ++ this.setBlock(positionAbove, DOOR_MAP.get(name + facingBelow + "upper" + hingeAbove + openBelow + poweredAbove)); ++ } ++ } ++ break; ++ } ++ case 86: { // pumpkin ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ ++ // I guess this is some terrible hack to convert carved pumpkins from world gen into ++ // regular pumpkins? ++ ++ if ("minecraft:carved_pumpkin".equals(getName(blockState))) { ++ final String downName = getName(this.getBlock(relative(position, Direction.DOWN))); ++ if ("minecraft:grass_block".equals(downName) || "minecraft:dirt".equals(downName)) { ++ this.setBlock(position, PUMPKIN); ++ } ++ } ++ } ++ break; ++ } ++ case 110: { // mycelium ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if ("minecraft:mycelium".equals(getName(blockState))) { ++ final String nameAbove = getName(this.getBlock(relative(position, Direction.UP))); ++ if ("minecraft:snow".equals(nameAbove) || "minecraft:snow_layer".equals(nameAbove)) { ++ this.setBlock(position, SNOWY_MYCELIUM); ++ } ++ } ++ } ++ break; ++ } ++ case 140: { // flower pot ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.removeBlockEntity(position); ++ if (tile == null) { ++ continue; ++ } ++ ++ final String item; ++ if (tile.hasKey("Item", ObjectType.NUMBER)) { ++ // the item name converter should have migrated to number, however no legacy converter ++ // ever did this. so we can get data with versions above v102 (old worlds, converted prior to DFU) ++ // that didn't convert. so just do it here. ++ item = HelperItemNameV102.getNameFromId(tile.getInt("Item")); ++ } else { ++ item = tile.getString("Item", ""); ++ } ++ ++ final String state = item + tile.getInt("Data"); ++ this.setBlock(position, FLOWER_POT_MAP.getOrDefault(state, FLOWER_POT_MAP.get("minecraft:air0"))); ++ } ++ break; ++ } ++ case 144: { // mob head ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ if (tile == null) { ++ continue; ++ } ++ ++ final String typeString = Integer.toString(tile.getInt("SkullType")); ++ final String facing = getProperty(this.getBlock(position), "facing"); ++ final String state; ++ if (!"up".equals(facing) && !"down".equals(facing)) { ++ state = typeString + facing; ++ } else { ++ state = typeString + tile.getInt("Rot"); ++ } ++ ++ tile.remove("SkullType"); ++ tile.remove("facing"); ++ tile.remove("Rot"); ++ ++ this.setBlock(position, SKULL_MAP.getOrDefault(state, SKULL_MAP.get("0north"))); ++ } ++ break; ++ } ++ case 175: { // sunflower ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType blockState = this.getBlock(position); ++ if (!"upper".equals(getProperty(blockState, "half"))) { ++ continue; ++ } ++ ++ final MapType blockStateBelow = this.getBlock(relative(position, Direction.DOWN)); ++ final String nameBelow = getName(blockStateBelow); ++ switch (nameBelow) { ++ case "minecraft:sunflower": ++ this.setBlock(position, UPPER_SUNFLOWER); ++ break; ++ case "minecraft:lilac": ++ this.setBlock(position, UPPER_LILAC); ++ break; ++ case "minecraft:tall_grass": ++ this.setBlock(position, UPPER_TALL_GRASS); ++ break; ++ case "minecraft:large_fern": ++ this.setBlock(position, UPPER_LARGE_FERN); ++ break; ++ case "minecraft:rose_bush": ++ this.setBlock(position, UPPER_ROSE_BUSH); ++ break; ++ case "minecraft:peony": ++ this.setBlock(position, UPPER_PEONY); ++ break; ++ } ++ } ++ break; ++ } ++ case 176: // free standing banner ++ case 177: { // wall mounted banner ++ while (positionIterator.hasNext()) { ++ final int position = positionIterator.nextInt() | yIndex; ++ final MapType tile = this.getBlockEntity(position); ++ ++ if (tile == null) { ++ continue; ++ } ++ ++ final MapType blockState = this.getBlock(position); ++ ++ final int base = tile.getInt("Base"); ++ if (base != 15 && base >= 0 && base < 16) { ++ final String state = getProperty(blockState, fixEntry.getIntKey() == 176 ? "rotation" : "facing") + "_" + base; ++ final MapType update = BANNER_BLOCK_MAP.get(state); ++ if (update != null) { ++ this.setBlock(position, update); ++ } ++ } ++ } ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ private MapType getBlockEntity(final int index) { ++ return this.tileEntities.get(index); ++ } ++ ++ private MapType removeBlockEntity(final int index) { ++ return this.tileEntities.remove(index); ++ } ++ ++ public static int relative(final int index, final Direction direction) { ++ switch (direction.getAxis()) { ++ case X: ++ int j = (index & 15) + direction.getAxisDirection().getStep(); ++ return j >= 0 && j <= 15 ? index & -16 | j : -1; ++ case Y: ++ int k = (index >> 8) + direction.getAxisDirection().getStep(); ++ return k >= 0 && k <= 255 ? index & 255 | k << 8 : -1; ++ case Z: ++ int l = (index >> 4 & 15) + direction.getAxisDirection().getStep(); ++ return l >= 0 && l <= 15 ? index & -241 | l << 4 : -1; ++ default: ++ return -1; ++ } ++ } ++ ++ private void setBlock(final int index, final MapType blockState) { ++ if (index >= 0 && index <= 65535) { ++ final Section section = this.getSection(index); ++ if (section != null) { ++ section.setBlock(index & 4095, blockState); ++ } ++ } ++ } ++ ++ private Section getSection(final int index) { ++ final int y = index >> 12; ++ return y < this.sections.length ? this.sections[y] : null; ++ } ++ ++ public MapType getBlock(int i) { ++ if (i >= 0 && i <= 65535) { ++ final Section section = this.getSection(i); ++ return section == null ? AIR : section.getBlock(i & 4095); ++ } else { ++ return AIR; ++ } ++ } ++ ++ public MapType writeBackToLevel() { ++ if (this.tileEntities.isEmpty()) { ++ this.level.remove("TileEntities"); ++ } else { ++ final ListType tileEntities = Types.NBT.createEmptyList(); ++ this.tileEntities.values().forEach(tileEntities::addMap); ++ this.level.setList("TileEntities", tileEntities); ++ } ++ ++ final MapType indices = Types.NBT.createEmptyMap(); ++ final ListType sections = Types.NBT.createEmptyList(); ++ for (final Section section : this.sections) { ++ if (section == null) { ++ continue; ++ } ++ ++ sections.addMap(section.writeBackToSection()); ++ indices.setInts(Integer.toString(section.y), Arrays.copyOf(section.update.elements(), section.update.size())); ++ } ++ ++ this.level.setList("Sections", sections); ++ ++ final MapType upgradeData = Types.NBT.createEmptyMap(); ++ upgradeData.setByte("Sides", (byte)this.sides); ++ upgradeData.setMap("Indices", indices); ++ ++ this.level.setMap("UpgradeData", upgradeData); ++ ++ return this.level; ++ } ++ } ++ ++ static class Section { ++ final Palette palette = new Palette(); ++ ++ static final class Palette extends Reference2IntOpenHashMap> { ++ ++ final ListType paletteStates = Types.NBT.createEmptyList(); ++ ++ private int find(final MapType k) { ++ if (((k) == (null))) ++ return containsNullKey ? n : -(n + 1); ++ MapType curr; ++ final Object[] key = this.key; ++ int pos; ++ // The starting point. ++ if (((curr = (MapType)key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) ++ return -(pos + 1); ++ if (((k) == (curr))) ++ return pos; ++ // There's always an unused entry. ++ while (true) { ++ if (((curr = (MapType)key[pos = (pos + 1) & mask]) == (null))) ++ return -(pos + 1); ++ if (((k) == (curr))) ++ return pos; ++ } ++ } ++ ++ private void insert(final int pos, final MapType k, final int v) { ++ if (pos == n) ++ containsNullKey = true; ++ ((Object[])key)[pos] = k; ++ value[pos] = v; ++ if (size++ >= maxFill) ++ rehash(arraySize(size + 1, f)); ++ } ++ ++ private MapType[] byId = new MapType[4]; ++ private MapType last = null; ++ ++ public int getOrCreateId(final MapType k) { ++ if (k == this.last) { ++ return this.size - 1; ++ } ++ final int pos = find(k); ++ if (pos >= 0) { ++ return this.value[pos]; ++ } ++ ++ final int insert = this.size; ++ MapType inPalette = k; ++ ++ if ("%%FILTER_ME%%".equals(getName(k))) { ++ inPalette = AIR; ++ } ++ ++ if (insert >= this.byId.length) { ++ this.byId = Arrays.copyOf(this.byId, this.byId.length * 2); ++ this.byId[insert] = k; ++ } else { ++ this.byId[insert] = k; ++ } ++ this.paletteStates.addMap(inPalette); ++ ++ this.last = k; ++ ++ this.insert(-pos - 1, k, insert); ++ ++ return insert; ++ } ++ ++ } ++ ++ final MapType section; ++ final boolean hasData; ++ final Int2ObjectLinkedOpenHashMap toFix = new Int2ObjectLinkedOpenHashMap<>(); ++ final IntArrayList update = new IntArrayList(); ++ final int y; ++ final int[] buffer = new int[4096]; ++ ++ public Section(final MapType section) { ++ this.section = section; ++ this.y = section.getInt("Y"); ++ this.hasData = section.hasKey("Blocks", ObjectType.BYTE_ARRAY); ++ } ++ ++ public MapType getBlock(final int index) { ++ if (index >= 0 && index <= 4095) { ++ final MapType state = this.palette.byId[this.buffer[index]]; ++ return state == null ? AIR : state; ++ } else { ++ return AIR; ++ } ++ } ++ ++ public void setBlock(final int index, final MapType blockState) { ++ this.buffer[index] = this.palette.getOrCreateId(blockState); ++ } ++ ++ public int upgrade(int sides) { ++ if (!this.hasData) { ++ return sides; ++ } ++ ++ final byte[] blocks = this.section.getBytes("Blocks"); ++ final DataLayer data = DataLayer.getOrNull(this.section.getBytes("Data")); ++ final DataLayer add = DataLayer.getOrNull(this.section.getBytes("Add")); ++ ++ this.palette.getOrCreateId(AIR); ++ ++ for (int index = 0; index < 4096; ++index) { ++ final int x = index & 15; ++ final int z = index >> 4 & 15; ++ ++ int blockStateId = (blocks[index] & 255) << 4; ++ if (data != null) { ++ blockStateId |= data.get(index); ++ } ++ if (add != null) { ++ blockStateId |= add.get(index) << 12; ++ } ++ if (IDS_NEEDING_FIX[blockStateId >>> 4]) { ++ this.addFix(blockStateId >>> 4, index); ++ } ++ ++ if (VIRTUAL[blockStateId >>> 4]) { ++ final int additionalSides = getSideMask(x == 0, x == 15, z == 0, z == 15); ++ if (additionalSides == 0) { ++ this.update.add(index); ++ } else { ++ sides |= additionalSides; ++ } ++ } ++ ++ this.setBlock(index, HelperBlockFlatteningV1450.getNBTForId(blockStateId)); ++ } ++ ++ return sides; ++ } ++ ++ private void addFix(final int block, final int index) { ++ this.toFix.computeIfAbsent(block, (final int keyInMap) -> { ++ return new IntArrayList(); ++ }).add(index); ++ } ++ ++ // Note: modifies the current section and returns it. ++ public MapType writeBackToSection() { ++ if (!this.hasData) { ++ return this.section; ++ } ++ ++ this.section.setList("Palette", this.palette.paletteStates.copy()); // deep copy to ensure palette compound tags are NOT shared ++ ++ final int bitSize = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); ++ final PackedBitStorage packedIds = new PackedBitStorage(bitSize, 4096); ++ ++ for(int index = 0; index < this.buffer.length; ++index) { ++ packedIds.set(index, this.buffer[index]); ++ } ++ ++ this.section.setLongs("BlockStates", packedIds.getRaw()); ++ ++ this.section.remove("Blocks"); ++ this.section.remove("Data"); ++ this.section.remove("Add"); ++ ++ return this.section; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06c075e643415d98b73734b749d9043091ebf9e5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractEntityRename { ++ ++ private ConverterAbstractEntityRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(version) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ final String converted = renamer.apply(id); ++ ++ if (converted != null) { ++ data.setString("id", converted); ++ } ++ ++ return null; ++ } ++ }); ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ENTITY_NAME, renamer); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java +new file mode 100644 +index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.IntFunction; ++ ++public final class ConverterEntityToVariant extends DataConverter, MapType> { ++ ++ public final String path; ++ public final IntFunction conversion; ++ ++ public ConverterEntityToVariant(final int toVersion, final String path, final IntFunction conversion) { ++ super(toVersion); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ public ConverterEntityToVariant(final int toVersion, final int versionStep, final String path, final IntFunction conversion) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.conversion = conversion; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number value = data.getNumber(this.path); ++ if (value == null) { ++ // nothing to do, DFU does the same ++ return null; ++ } ++ ++ final String converted = this.conversion.apply(value.intValue()); ++ ++ if (converted == null) { ++ throw new NullPointerException("Conversion " + this.conversion + " cannot return null value!"); ++ } ++ ++ // DFU doesn't appear to remove the old field, so why should we? ++ ++ data.setString("variant", converted); ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.function.Function; ++ ++public final class ConverterEntityVariantRename extends DataConverter, MapType> { ++ ++ private final Function renamer; ++ ++ public ConverterEntityVariantRename(final int toVersion, final Function renamer) { ++ super(toVersion); ++ this.renamer = renamer; ++ } ++ ++ public ConverterEntityVariantRename(final int toVersion, final int versionStep, final Function renamer) { ++ super(toVersion, versionStep); ++ this.renamer = renamer; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String variant = data.getString("variant"); ++ ++ if (variant == null) { ++ return null; ++ } ++ ++ final String rename = this.renamer.apply(variant); ++ ++ if (rename != null) { ++ data.setString("variant", rename); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java +new file mode 100644 +index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java +@@ -0,0 +1,371 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.entity; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class ConverterFlattenEntity extends DataConverter, MapType> { ++ ++ private static final Map BLOCK_NAME_TO_ID = new HashMap<>(); ++ static { ++ BLOCK_NAME_TO_ID.put("minecraft:air", 0); ++ BLOCK_NAME_TO_ID.put("minecraft:stone", 1); ++ BLOCK_NAME_TO_ID.put("minecraft:grass", 2); ++ BLOCK_NAME_TO_ID.put("minecraft:dirt", 3); ++ BLOCK_NAME_TO_ID.put("minecraft:cobblestone", 4); ++ BLOCK_NAME_TO_ID.put("minecraft:planks", 5); ++ BLOCK_NAME_TO_ID.put("minecraft:sapling", 6); ++ BLOCK_NAME_TO_ID.put("minecraft:bedrock", 7); ++ BLOCK_NAME_TO_ID.put("minecraft:flowing_water", 8); ++ BLOCK_NAME_TO_ID.put("minecraft:water", 9); ++ BLOCK_NAME_TO_ID.put("minecraft:flowing_lava", 10); ++ BLOCK_NAME_TO_ID.put("minecraft:lava", 11); ++ BLOCK_NAME_TO_ID.put("minecraft:sand", 12); ++ BLOCK_NAME_TO_ID.put("minecraft:gravel", 13); ++ BLOCK_NAME_TO_ID.put("minecraft:gold_ore", 14); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_ore", 15); ++ BLOCK_NAME_TO_ID.put("minecraft:coal_ore", 16); ++ BLOCK_NAME_TO_ID.put("minecraft:log", 17); ++ BLOCK_NAME_TO_ID.put("minecraft:leaves", 18); ++ BLOCK_NAME_TO_ID.put("minecraft:sponge", 19); ++ BLOCK_NAME_TO_ID.put("minecraft:glass", 20); ++ BLOCK_NAME_TO_ID.put("minecraft:lapis_ore", 21); ++ BLOCK_NAME_TO_ID.put("minecraft:lapis_block", 22); ++ BLOCK_NAME_TO_ID.put("minecraft:dispenser", 23); ++ BLOCK_NAME_TO_ID.put("minecraft:sandstone", 24); ++ BLOCK_NAME_TO_ID.put("minecraft:noteblock", 25); ++ BLOCK_NAME_TO_ID.put("minecraft:bed", 26); ++ BLOCK_NAME_TO_ID.put("minecraft:golden_rail", 27); ++ BLOCK_NAME_TO_ID.put("minecraft:detector_rail", 28); ++ BLOCK_NAME_TO_ID.put("minecraft:sticky_piston", 29); ++ BLOCK_NAME_TO_ID.put("minecraft:web", 30); ++ BLOCK_NAME_TO_ID.put("minecraft:tallgrass", 31); ++ BLOCK_NAME_TO_ID.put("minecraft:deadbush", 32); ++ BLOCK_NAME_TO_ID.put("minecraft:piston", 33); ++ BLOCK_NAME_TO_ID.put("minecraft:piston_head", 34); ++ BLOCK_NAME_TO_ID.put("minecraft:wool", 35); ++ BLOCK_NAME_TO_ID.put("minecraft:piston_extension", 36); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_flower", 37); ++ BLOCK_NAME_TO_ID.put("minecraft:red_flower", 38); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom", 39); ++ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom", 40); ++ BLOCK_NAME_TO_ID.put("minecraft:gold_block", 41); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_block", 42); ++ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab", 43); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_slab", 44); ++ BLOCK_NAME_TO_ID.put("minecraft:brick_block", 45); ++ BLOCK_NAME_TO_ID.put("minecraft:tnt", 46); ++ BLOCK_NAME_TO_ID.put("minecraft:bookshelf", 47); ++ BLOCK_NAME_TO_ID.put("minecraft:mossy_cobblestone", 48); ++ BLOCK_NAME_TO_ID.put("minecraft:obsidian", 49); ++ BLOCK_NAME_TO_ID.put("minecraft:torch", 50); ++ BLOCK_NAME_TO_ID.put("minecraft:fire", 51); ++ BLOCK_NAME_TO_ID.put("minecraft:mob_spawner", 52); ++ BLOCK_NAME_TO_ID.put("minecraft:oak_stairs", 53); ++ BLOCK_NAME_TO_ID.put("minecraft:chest", 54); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_wire", 55); ++ BLOCK_NAME_TO_ID.put("minecraft:diamond_ore", 56); ++ BLOCK_NAME_TO_ID.put("minecraft:diamond_block", 57); ++ BLOCK_NAME_TO_ID.put("minecraft:crafting_table", 58); ++ BLOCK_NAME_TO_ID.put("minecraft:wheat", 59); ++ BLOCK_NAME_TO_ID.put("minecraft:farmland", 60); ++ BLOCK_NAME_TO_ID.put("minecraft:furnace", 61); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_furnace", 62); ++ BLOCK_NAME_TO_ID.put("minecraft:standing_sign", 63); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_door", 64); ++ BLOCK_NAME_TO_ID.put("minecraft:ladder", 65); ++ BLOCK_NAME_TO_ID.put("minecraft:rail", 66); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_stairs", 67); ++ BLOCK_NAME_TO_ID.put("minecraft:wall_sign", 68); ++ BLOCK_NAME_TO_ID.put("minecraft:lever", 69); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_pressure_plate", 70); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_door", 71); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_pressure_plate", 72); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_ore", 73); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_ore", 74); ++ BLOCK_NAME_TO_ID.put("minecraft:unlit_redstone_torch", 75); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_torch", 76); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_button", 77); ++ BLOCK_NAME_TO_ID.put("minecraft:snow_layer", 78); ++ BLOCK_NAME_TO_ID.put("minecraft:ice", 79); ++ BLOCK_NAME_TO_ID.put("minecraft:snow", 80); ++ BLOCK_NAME_TO_ID.put("minecraft:cactus", 81); ++ BLOCK_NAME_TO_ID.put("minecraft:clay", 82); ++ BLOCK_NAME_TO_ID.put("minecraft:reeds", 83); ++ BLOCK_NAME_TO_ID.put("minecraft:jukebox", 84); ++ BLOCK_NAME_TO_ID.put("minecraft:fence", 85); ++ BLOCK_NAME_TO_ID.put("minecraft:pumpkin", 86); ++ BLOCK_NAME_TO_ID.put("minecraft:netherrack", 87); ++ BLOCK_NAME_TO_ID.put("minecraft:soul_sand", 88); ++ BLOCK_NAME_TO_ID.put("minecraft:glowstone", 89); ++ BLOCK_NAME_TO_ID.put("minecraft:portal", 90); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_pumpkin", 91); ++ BLOCK_NAME_TO_ID.put("minecraft:cake", 92); ++ BLOCK_NAME_TO_ID.put("minecraft:unpowered_repeater", 93); ++ BLOCK_NAME_TO_ID.put("minecraft:powered_repeater", 94); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_glass", 95); ++ BLOCK_NAME_TO_ID.put("minecraft:trapdoor", 96); ++ BLOCK_NAME_TO_ID.put("minecraft:monster_egg", 97); ++ BLOCK_NAME_TO_ID.put("minecraft:stonebrick", 98); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom_block", 99); ++ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom_block", 100); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_bars", 101); ++ BLOCK_NAME_TO_ID.put("minecraft:glass_pane", 102); ++ BLOCK_NAME_TO_ID.put("minecraft:melon_block", 103); ++ BLOCK_NAME_TO_ID.put("minecraft:pumpkin_stem", 104); ++ BLOCK_NAME_TO_ID.put("minecraft:melon_stem", 105); ++ BLOCK_NAME_TO_ID.put("minecraft:vine", 106); ++ BLOCK_NAME_TO_ID.put("minecraft:fence_gate", 107); ++ BLOCK_NAME_TO_ID.put("minecraft:brick_stairs", 108); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_brick_stairs", 109); ++ BLOCK_NAME_TO_ID.put("minecraft:mycelium", 110); ++ BLOCK_NAME_TO_ID.put("minecraft:waterlily", 111); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick", 112); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_fence", 113); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_stairs", 114); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_wart", 115); ++ BLOCK_NAME_TO_ID.put("minecraft:enchanting_table", 116); ++ BLOCK_NAME_TO_ID.put("minecraft:brewing_stand", 117); ++ BLOCK_NAME_TO_ID.put("minecraft:cauldron", 118); ++ BLOCK_NAME_TO_ID.put("minecraft:end_portal", 119); ++ BLOCK_NAME_TO_ID.put("minecraft:end_portal_frame", 120); ++ BLOCK_NAME_TO_ID.put("minecraft:end_stone", 121); ++ BLOCK_NAME_TO_ID.put("minecraft:dragon_egg", 122); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_lamp", 123); ++ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_lamp", 124); ++ BLOCK_NAME_TO_ID.put("minecraft:double_wooden_slab", 125); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_slab", 126); ++ BLOCK_NAME_TO_ID.put("minecraft:cocoa", 127); ++ BLOCK_NAME_TO_ID.put("minecraft:sandstone_stairs", 128); ++ BLOCK_NAME_TO_ID.put("minecraft:emerald_ore", 129); ++ BLOCK_NAME_TO_ID.put("minecraft:ender_chest", 130); ++ BLOCK_NAME_TO_ID.put("minecraft:tripwire_hook", 131); ++ BLOCK_NAME_TO_ID.put("minecraft:tripwire", 132); ++ BLOCK_NAME_TO_ID.put("minecraft:emerald_block", 133); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_stairs", 134); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_stairs", 135); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_stairs", 136); ++ BLOCK_NAME_TO_ID.put("minecraft:command_block", 137); ++ BLOCK_NAME_TO_ID.put("minecraft:beacon", 138); ++ BLOCK_NAME_TO_ID.put("minecraft:cobblestone_wall", 139); ++ BLOCK_NAME_TO_ID.put("minecraft:flower_pot", 140); ++ BLOCK_NAME_TO_ID.put("minecraft:carrots", 141); ++ BLOCK_NAME_TO_ID.put("minecraft:potatoes", 142); ++ BLOCK_NAME_TO_ID.put("minecraft:wooden_button", 143); ++ BLOCK_NAME_TO_ID.put("minecraft:skull", 144); ++ BLOCK_NAME_TO_ID.put("minecraft:anvil", 145); ++ BLOCK_NAME_TO_ID.put("minecraft:trapped_chest", 146); ++ BLOCK_NAME_TO_ID.put("minecraft:light_weighted_pressure_plate", 147); ++ BLOCK_NAME_TO_ID.put("minecraft:heavy_weighted_pressure_plate", 148); ++ BLOCK_NAME_TO_ID.put("minecraft:unpowered_comparator", 149); ++ BLOCK_NAME_TO_ID.put("minecraft:powered_comparator", 150); ++ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector", 151); ++ BLOCK_NAME_TO_ID.put("minecraft:redstone_block", 152); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_ore", 153); ++ BLOCK_NAME_TO_ID.put("minecraft:hopper", 154); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_block", 155); ++ BLOCK_NAME_TO_ID.put("minecraft:quartz_stairs", 156); ++ BLOCK_NAME_TO_ID.put("minecraft:activator_rail", 157); ++ BLOCK_NAME_TO_ID.put("minecraft:dropper", 158); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_hardened_clay", 159); ++ BLOCK_NAME_TO_ID.put("minecraft:stained_glass_pane", 160); ++ BLOCK_NAME_TO_ID.put("minecraft:leaves2", 161); ++ BLOCK_NAME_TO_ID.put("minecraft:log2", 162); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_stairs", 163); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_stairs", 164); ++ BLOCK_NAME_TO_ID.put("minecraft:slime", 165); ++ BLOCK_NAME_TO_ID.put("minecraft:barrier", 166); ++ BLOCK_NAME_TO_ID.put("minecraft:iron_trapdoor", 167); ++ BLOCK_NAME_TO_ID.put("minecraft:prismarine", 168); ++ BLOCK_NAME_TO_ID.put("minecraft:sea_lantern", 169); ++ BLOCK_NAME_TO_ID.put("minecraft:hay_block", 170); ++ BLOCK_NAME_TO_ID.put("minecraft:carpet", 171); ++ BLOCK_NAME_TO_ID.put("minecraft:hardened_clay", 172); ++ BLOCK_NAME_TO_ID.put("minecraft:coal_block", 173); ++ BLOCK_NAME_TO_ID.put("minecraft:packed_ice", 174); ++ BLOCK_NAME_TO_ID.put("minecraft:double_plant", 175); ++ BLOCK_NAME_TO_ID.put("minecraft:standing_banner", 176); ++ BLOCK_NAME_TO_ID.put("minecraft:wall_banner", 177); ++ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector_inverted", 178); ++ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone", 179); ++ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone_stairs", 180); ++ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab2", 181); ++ BLOCK_NAME_TO_ID.put("minecraft:stone_slab2", 182); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence_gate", 183); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_fence_gate", 184); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence_gate", 185); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence_gate", 186); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence_gate", 187); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence", 188); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_fence", 189); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence", 190); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence", 191); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence", 192); ++ BLOCK_NAME_TO_ID.put("minecraft:spruce_door", 193); ++ BLOCK_NAME_TO_ID.put("minecraft:birch_door", 194); ++ BLOCK_NAME_TO_ID.put("minecraft:jungle_door", 195); ++ BLOCK_NAME_TO_ID.put("minecraft:acacia_door", 196); ++ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_door", 197); ++ BLOCK_NAME_TO_ID.put("minecraft:end_rod", 198); ++ BLOCK_NAME_TO_ID.put("minecraft:chorus_plant", 199); ++ BLOCK_NAME_TO_ID.put("minecraft:chorus_flower", 200); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_block", 201); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_pillar", 202); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_stairs", 203); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_double_slab", 204); ++ BLOCK_NAME_TO_ID.put("minecraft:purpur_slab", 205); ++ BLOCK_NAME_TO_ID.put("minecraft:end_bricks", 206); ++ BLOCK_NAME_TO_ID.put("minecraft:beetroots", 207); ++ BLOCK_NAME_TO_ID.put("minecraft:grass_path", 208); ++ BLOCK_NAME_TO_ID.put("minecraft:end_gateway", 209); ++ BLOCK_NAME_TO_ID.put("minecraft:repeating_command_block", 210); ++ BLOCK_NAME_TO_ID.put("minecraft:chain_command_block", 211); ++ BLOCK_NAME_TO_ID.put("minecraft:frosted_ice", 212); ++ BLOCK_NAME_TO_ID.put("minecraft:magma", 213); ++ BLOCK_NAME_TO_ID.put("minecraft:nether_wart_block", 214); ++ BLOCK_NAME_TO_ID.put("minecraft:red_nether_brick", 215); ++ BLOCK_NAME_TO_ID.put("minecraft:bone_block", 216); ++ BLOCK_NAME_TO_ID.put("minecraft:structure_void", 217); ++ BLOCK_NAME_TO_ID.put("minecraft:observer", 218); ++ BLOCK_NAME_TO_ID.put("minecraft:white_shulker_box", 219); ++ BLOCK_NAME_TO_ID.put("minecraft:orange_shulker_box", 220); ++ BLOCK_NAME_TO_ID.put("minecraft:magenta_shulker_box", 221); ++ BLOCK_NAME_TO_ID.put("minecraft:light_blue_shulker_box", 222); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_shulker_box", 223); ++ BLOCK_NAME_TO_ID.put("minecraft:lime_shulker_box", 224); ++ BLOCK_NAME_TO_ID.put("minecraft:pink_shulker_box", 225); ++ BLOCK_NAME_TO_ID.put("minecraft:gray_shulker_box", 226); ++ BLOCK_NAME_TO_ID.put("minecraft:silver_shulker_box", 227); ++ BLOCK_NAME_TO_ID.put("minecraft:cyan_shulker_box", 228); ++ BLOCK_NAME_TO_ID.put("minecraft:purple_shulker_box", 229); ++ BLOCK_NAME_TO_ID.put("minecraft:blue_shulker_box", 230); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_shulker_box", 231); ++ BLOCK_NAME_TO_ID.put("minecraft:green_shulker_box", 232); ++ BLOCK_NAME_TO_ID.put("minecraft:red_shulker_box", 233); ++ BLOCK_NAME_TO_ID.put("minecraft:black_shulker_box", 234); ++ BLOCK_NAME_TO_ID.put("minecraft:white_glazed_terracotta", 235); ++ BLOCK_NAME_TO_ID.put("minecraft:orange_glazed_terracotta", 236); ++ BLOCK_NAME_TO_ID.put("minecraft:magenta_glazed_terracotta", 237); ++ BLOCK_NAME_TO_ID.put("minecraft:light_blue_glazed_terracotta", 238); ++ BLOCK_NAME_TO_ID.put("minecraft:yellow_glazed_terracotta", 239); ++ BLOCK_NAME_TO_ID.put("minecraft:lime_glazed_terracotta", 240); ++ BLOCK_NAME_TO_ID.put("minecraft:pink_glazed_terracotta", 241); ++ BLOCK_NAME_TO_ID.put("minecraft:gray_glazed_terracotta", 242); ++ BLOCK_NAME_TO_ID.put("minecraft:silver_glazed_terracotta", 243); ++ BLOCK_NAME_TO_ID.put("minecraft:cyan_glazed_terracotta", 244); ++ BLOCK_NAME_TO_ID.put("minecraft:purple_glazed_terracotta", 245); ++ BLOCK_NAME_TO_ID.put("minecraft:blue_glazed_terracotta", 246); ++ BLOCK_NAME_TO_ID.put("minecraft:brown_glazed_terracotta", 247); ++ BLOCK_NAME_TO_ID.put("minecraft:green_glazed_terracotta", 248); ++ BLOCK_NAME_TO_ID.put("minecraft:red_glazed_terracotta", 249); ++ BLOCK_NAME_TO_ID.put("minecraft:black_glazed_terracotta", 250); ++ BLOCK_NAME_TO_ID.put("minecraft:concrete", 251); ++ BLOCK_NAME_TO_ID.put("minecraft:concrete_powder", 252); ++ BLOCK_NAME_TO_ID.put("minecraft:structure_block", 255); ++ } ++ ++ protected static final int VERSION = MCVersions.V17W47A; ++ ++ protected final String[] paths; ++ ++ public ConverterFlattenEntity(final String... paths) { ++ super(VERSION, 3); ++ this.paths = paths; ++ } ++ ++ private static void register(final String id, final String... paths) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, new ConverterFlattenEntity(paths)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:falling_block", new DataConverter<>(VERSION, 3) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int blockId; ++ if (data.hasKey("Block")) { ++ final Number id = data.getNumber("Block"); ++ if (id != null) { ++ blockId = id.intValue(); ++ } else { ++ blockId = getBlockId(data.getString("Block")); ++ } ++ } else { ++ final Number tileId = data.getNumber("TileID"); ++ if (tileId != null) { ++ blockId = tileId.intValue(); ++ } else { ++ blockId = data.getByte("Tile") & 255; ++ } ++ } ++ ++ final int blockData = data.getInt("Data") & 15; ++ ++ data.remove("Block"); // from type update ++ data.remove("Data"); ++ data.remove("TileID"); ++ data.remove("Tile"); ++ ++ // key is from type update ++ data.setMap("BlockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++ }); ++ register("minecraft:enderman", "carried", "carriedData", "carriedBlockState"); ++ register("minecraft:arrow", "inTile", "inData", "inBlockState"); ++ register("minecraft:spectral_arrow", "inTile", "inData", "inBlockState"); ++ register("minecraft:egg", "inTile"); ++ register("minecraft:ender_pearl", "inTile"); ++ register("minecraft:fireball", "inTile"); ++ register("minecraft:potion", "inTile"); ++ register("minecraft:small_fireball", "inTile"); ++ register("minecraft:snowball", "inTile"); ++ register("minecraft:wither_skull", "inTile"); ++ register("minecraft:xp_bottle", "inTile"); ++ register("minecraft:commandblock_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:chest_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:furnace_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:tnt_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:hopper_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ register("minecraft:spawner_minecart", "DisplayTile", "DisplayData", "DisplayState"); ++ } ++ ++ public static int getBlockId(final String block) { ++ final Integer ret = BLOCK_NAME_TO_ID.get(block); ++ return ret == null ? 0 : ret.intValue(); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (this.paths.length == 1) { ++ data.remove(this.paths[0]); ++ return null; ++ } ++ final String idPath = this.paths[0]; ++ final String dataPath = this.paths[1]; ++ final String outputStatePath = this.paths[2]; ++ ++ final int blockId; ++ if (data.hasKey(idPath, ObjectType.NUMBER)) { ++ blockId = data.getInt(idPath); ++ } else { ++ blockId = getBlockId(data.getString(idPath)); ++ } ++ ++ final int blockData = data.getInt(dataPath) & 15; ++ ++ data.remove(idPath); // from type update ++ data.remove(dataPath); ++ ++ data.setMap(outputStatePath, HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class AddFlagIfAbsent extends DataConverter, MapType> { ++ ++ public final String path; ++ public final boolean dfl; ++ ++ public AddFlagIfAbsent(final int toVersion, final String path, final boolean dfl) { ++ super(toVersion); ++ this.path = path; ++ this.dfl = dfl; ++ } ++ ++ public AddFlagIfAbsent(final int toVersion, final int versionStep, final String path, final boolean dfl) { ++ super(toVersion, versionStep); ++ this.path = path; ++ this.dfl = dfl; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey(this.path)) { ++ data.setBoolean(this.path, this.dfl); ++ } ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractStringValueTypeRename { ++ ++ private ConverterAbstractStringValueTypeRename() {} ++ ++ public static void register(final int version, final MCValueType type, final Function renamer) { ++ register(version, 0, type, renamer); ++ } ++ public static void register(final int version, final int subVersion, final MCValueType type, final Function renamer) { ++ type.addConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ final String ret = (data instanceof String) ? renamer.apply((String)data) : null; ++ return ret == data ? null : ret; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java +new file mode 100644 +index 0000000000000000000000000000000000000000..02ee521dc0f61f3f01d443c46c1066d1ecbeea7f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java +@@ -0,0 +1,1830 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import net.minecraft.nbt.TagParser; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class HelperBlockFlatteningV1450 { ++ ++ protected static final MapType[] FLATTENED_BY_ID = new MapType[4096]; ++ protected static final MapType[] BLOCK_DEFAULTS = new MapType[4096]; ++ ++ private static final Object2IntOpenHashMap> ID_BY_OLD_NBT = new Object2IntOpenHashMap>(64, 0.7f) { ++ @Override ++ public int put(final MapType o, final int v) { ++ if (this.containsKey(o)) { ++ throw new RuntimeException("Already contains mapping for " + o); ++ } ++ ++ return super.put(o, v); ++ } ++ }; ++ static { ++ ID_BY_OLD_NBT.defaultReturnValue(-1); ++ } ++ ++ private static final Object2IntOpenHashMap ID_BY_OLD_NAME = new Object2IntOpenHashMap(64, 0.7f) { ++ @Override ++ public int put(final String o, final int v) { ++ if (this.containsKey(o)) { ++ throw new RuntimeException("Already contains mapping for " + o); ++ } ++ ++ return super.put(o, v); ++ } ++ }; ++ static { ++ ID_BY_OLD_NAME.defaultReturnValue(-1); ++ } ++ ++ // map used to ensure that each parsed block state contains no duplicates ++ protected static final Map, MapType> IDENTITY_ENSURE = new HashMap<>(); ++ ++ public static MapType parseTag(final String blockstate) { ++ try { ++ final MapType ret = new NBTMapType(TagParser.parseTag(blockstate.replace('\'', '"'))); ++ ++ synchronized (IDENTITY_ENSURE) { ++ final MapType identity = IDENTITY_ENSURE.putIfAbsent(ret, ret); ++ ++ return identity == null ? ret : identity; ++ } ++ ++ } catch (final Exception ex) { ++ throw new RuntimeException("Exception parsing " + blockstate, ex); ++ } ++ } ++ ++ private static void register(final int id, final String flattened, final String... preFlattenings) { ++ final MapType flattenedNBT = parseTag(flattened); ++ if (FLATTENED_BY_ID[id] != null) { ++ throw new RuntimeException("Mapping already exists for id " + id); ++ } ++ FLATTENED_BY_ID[id] = flattenedNBT; ++ ++ // it's important that we register ids from smallest to largest, so that ++ // the default is going to be correct ++ final int block = id >> 4; ++ if (BLOCK_DEFAULTS[block] == null) { ++ BLOCK_DEFAULTS[block] = flattenedNBT; ++ } ++ ++ for (final String preFlattening : preFlattenings) { ++ final MapType preFlatteningNBT = parseTag(preFlattening); ++ final String name = preFlatteningNBT.getString("Name"); ++ if (name == null) { ++ throw new RuntimeException("Name does not exist for pre flattenings for id " + id); ++ } ++ ++ // putIfAbsent so we default to the lowest id, which is going to be the block default ++ ID_BY_OLD_NAME.putIfAbsent(name, id); ++ ID_BY_OLD_NBT.put(preFlatteningNBT, id); ++ } ++ } ++ ++ private static void finalizeMaps() { ++ for(int i = 0; i < FLATTENED_BY_ID.length; ++i) { ++ if (FLATTENED_BY_ID[i] == null) { ++ FLATTENED_BY_ID[i] = BLOCK_DEFAULTS[i >> 4]; ++ } ++ } ++ } ++ ++ public static MapType flattenNBT(final MapType old) { ++ final int id = ID_BY_OLD_NBT.getInt(old); ++ final MapType ret = getNBTForIdRaw(id); ++ ++ return ret == null ? old : ret; ++ } ++ ++ public static String getNewBlockName(final String old) { ++ final int id = ID_BY_OLD_NAME.getInt(old); ++ final MapType ret = getNBTForIdRaw(id); ++ return ret == null ? old : ret.getString("Name"); ++ } ++ ++ public static String getNameForId(final int block) { ++ final MapType nbt = getNBTForIdRaw(block); ++ return nbt == null ? "minecraft:air" : nbt.getString("Name"); ++ } ++ ++ protected static MapType getNBTForIdRaw(final int block) { ++ return block >= 0 && block < FLATTENED_BY_ID.length ? FLATTENED_BY_ID[block] : null; ++ } ++ ++ public static MapType getNBTForId(final int block) { ++ MapType ret = getNBTForIdRaw(block); ++ return ret == null ? FLATTENED_BY_ID[0] : ret; ++ } ++ ++ private HelperBlockFlatteningV1450() {} ++ ++ static { ++ ID_BY_OLD_NBT.defaultReturnValue(-1); ++ register(0, "{Name:'minecraft:air'}", "{Name:'minecraft:air'}"); ++ register(16, "{Name:'minecraft:stone'}", "{Name:'minecraft:stone',Properties:{variant:'stone'}}"); ++ register(17, "{Name:'minecraft:granite'}", "{Name:'minecraft:stone',Properties:{variant:'granite'}}"); ++ register(18, "{Name:'minecraft:polished_granite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_granite'}}"); ++ register(19, "{Name:'minecraft:diorite'}", "{Name:'minecraft:stone',Properties:{variant:'diorite'}}"); ++ register(20, "{Name:'minecraft:polished_diorite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_diorite'}}"); ++ register(21, "{Name:'minecraft:andesite'}", "{Name:'minecraft:stone',Properties:{variant:'andesite'}}"); ++ register(22, "{Name:'minecraft:polished_andesite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_andesite'}}"); ++ register(32, "{Name:'minecraft:grass_block',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'true'}}"); ++ register(48, "{Name:'minecraft:dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'dirt'}}"); ++ register(49, "{Name:'minecraft:coarse_dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'coarse_dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'coarse_dirt'}}"); ++ register(50, "{Name:'minecraft:podzol',Properties:{snowy:'false'}}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'podzol'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'podzol'}}"); ++ register(64, "{Name:'minecraft:cobblestone'}", "{Name:'minecraft:cobblestone'}"); ++ register(80, "{Name:'minecraft:oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'oak'}}"); ++ register(81, "{Name:'minecraft:spruce_planks'}", "{Name:'minecraft:planks',Properties:{variant:'spruce'}}"); ++ register(82, "{Name:'minecraft:birch_planks'}", "{Name:'minecraft:planks',Properties:{variant:'birch'}}"); ++ register(83, "{Name:'minecraft:jungle_planks'}", "{Name:'minecraft:planks',Properties:{variant:'jungle'}}"); ++ register(84, "{Name:'minecraft:acacia_planks'}", "{Name:'minecraft:planks',Properties:{variant:'acacia'}}"); ++ register(85, "{Name:'minecraft:dark_oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'dark_oak'}}"); ++ register(96, "{Name:'minecraft:oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'oak'}}"); ++ register(97, "{Name:'minecraft:spruce_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'spruce'}}"); ++ register(98, "{Name:'minecraft:birch_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'birch'}}"); ++ register(99, "{Name:'minecraft:jungle_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'jungle'}}"); ++ register(100, "{Name:'minecraft:acacia_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'acacia'}}"); ++ register(101, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'dark_oak'}}"); ++ register(104, "{Name:'minecraft:oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'oak'}}"); ++ register(105, "{Name:'minecraft:spruce_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'spruce'}}"); ++ register(106, "{Name:'minecraft:birch_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'birch'}}"); ++ register(107, "{Name:'minecraft:jungle_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'jungle'}}"); ++ register(108, "{Name:'minecraft:acacia_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'acacia'}}"); ++ register(109, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'dark_oak'}}"); ++ register(112, "{Name:'minecraft:bedrock'}", "{Name:'minecraft:bedrock'}"); ++ register(128, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:flowing_water',Properties:{level:'0'}}"); ++ register(129, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:flowing_water',Properties:{level:'1'}}"); ++ register(130, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:flowing_water',Properties:{level:'2'}}"); ++ register(131, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:flowing_water',Properties:{level:'3'}}"); ++ register(132, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:flowing_water',Properties:{level:'4'}}"); ++ register(133, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:flowing_water',Properties:{level:'5'}}"); ++ register(134, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:flowing_water',Properties:{level:'6'}}"); ++ register(135, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:flowing_water',Properties:{level:'7'}}"); ++ register(136, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:flowing_water',Properties:{level:'8'}}"); ++ register(137, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:flowing_water',Properties:{level:'9'}}"); ++ register(138, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:flowing_water',Properties:{level:'10'}}"); ++ register(139, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:flowing_water',Properties:{level:'11'}}"); ++ register(140, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:flowing_water',Properties:{level:'12'}}"); ++ register(141, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:flowing_water',Properties:{level:'13'}}"); ++ register(142, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:flowing_water',Properties:{level:'14'}}"); ++ register(143, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:flowing_water',Properties:{level:'15'}}"); ++ register(144, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:water',Properties:{level:'0'}}"); ++ register(145, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:water',Properties:{level:'1'}}"); ++ register(146, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:water',Properties:{level:'2'}}"); ++ register(147, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:water',Properties:{level:'3'}}"); ++ register(148, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:water',Properties:{level:'4'}}"); ++ register(149, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:water',Properties:{level:'5'}}"); ++ register(150, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:water',Properties:{level:'6'}}"); ++ register(151, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:water',Properties:{level:'7'}}"); ++ register(152, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:water',Properties:{level:'8'}}"); ++ register(153, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:water',Properties:{level:'9'}}"); ++ register(154, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:water',Properties:{level:'10'}}"); ++ register(155, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:water',Properties:{level:'11'}}"); ++ register(156, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:water',Properties:{level:'12'}}"); ++ register(157, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:water',Properties:{level:'13'}}"); ++ register(158, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:water',Properties:{level:'14'}}"); ++ register(159, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:water',Properties:{level:'15'}}"); ++ register(160, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'0'}}"); ++ register(161, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'1'}}"); ++ register(162, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'2'}}"); ++ register(163, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'3'}}"); ++ register(164, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'4'}}"); ++ register(165, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'5'}}"); ++ register(166, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'6'}}"); ++ register(167, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'7'}}"); ++ register(168, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'8'}}"); ++ register(169, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'9'}}"); ++ register(170, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'10'}}"); ++ register(171, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'11'}}"); ++ register(172, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'12'}}"); ++ register(173, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'13'}}"); ++ register(174, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'14'}}"); ++ register(175, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'15'}}"); ++ register(176, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:lava',Properties:{level:'0'}}"); ++ register(177, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:lava',Properties:{level:'1'}}"); ++ register(178, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:lava',Properties:{level:'2'}}"); ++ register(179, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:lava',Properties:{level:'3'}}"); ++ register(180, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:lava',Properties:{level:'4'}}"); ++ register(181, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:lava',Properties:{level:'5'}}"); ++ register(182, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:lava',Properties:{level:'6'}}"); ++ register(183, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:lava',Properties:{level:'7'}}"); ++ register(184, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:lava',Properties:{level:'8'}}"); ++ register(185, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:lava',Properties:{level:'9'}}"); ++ register(186, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:lava',Properties:{level:'10'}}"); ++ register(187, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:lava',Properties:{level:'11'}}"); ++ register(188, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:lava',Properties:{level:'12'}}"); ++ register(189, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:lava',Properties:{level:'13'}}"); ++ register(190, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:lava',Properties:{level:'14'}}"); ++ register(191, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:lava',Properties:{level:'15'}}"); ++ register(192, "{Name:'minecraft:sand'}", "{Name:'minecraft:sand',Properties:{variant:'sand'}}"); ++ register(193, "{Name:'minecraft:red_sand'}", "{Name:'minecraft:sand',Properties:{variant:'red_sand'}}"); ++ register(208, "{Name:'minecraft:gravel'}", "{Name:'minecraft:gravel'}"); ++ register(224, "{Name:'minecraft:gold_ore'}", "{Name:'minecraft:gold_ore'}"); ++ register(240, "{Name:'minecraft:iron_ore'}", "{Name:'minecraft:iron_ore'}"); ++ register(256, "{Name:'minecraft:coal_ore'}", "{Name:'minecraft:coal_ore'}"); ++ register(272, "{Name:'minecraft:oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'oak'}}"); ++ register(273, "{Name:'minecraft:spruce_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'spruce'}}"); ++ register(274, "{Name:'minecraft:birch_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'birch'}}"); ++ register(275, "{Name:'minecraft:jungle_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'jungle'}}"); ++ register(276, "{Name:'minecraft:oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'oak'}}"); ++ register(277, "{Name:'minecraft:spruce_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'spruce'}}"); ++ register(278, "{Name:'minecraft:birch_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'birch'}}"); ++ register(279, "{Name:'minecraft:jungle_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'jungle'}}"); ++ register(280, "{Name:'minecraft:oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'oak'}}"); ++ register(281, "{Name:'minecraft:spruce_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'spruce'}}"); ++ register(282, "{Name:'minecraft:birch_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'birch'}}"); ++ register(283, "{Name:'minecraft:jungle_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'jungle'}}"); ++ register(284, "{Name:'minecraft:oak_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'oak'}}"); ++ register(285, "{Name:'minecraft:spruce_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'spruce'}}"); ++ register(286, "{Name:'minecraft:birch_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'birch'}}"); ++ register(287, "{Name:'minecraft:jungle_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'jungle'}}"); ++ register(288, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'oak'}}"); ++ register(289, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'spruce'}}"); ++ register(290, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'birch'}}"); ++ register(291, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'jungle'}}"); ++ register(292, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'oak'}}"); ++ register(293, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'spruce'}}"); ++ register(294, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'birch'}}"); ++ register(295, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'jungle'}}"); ++ register(296, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'oak'}}"); ++ register(297, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'spruce'}}"); ++ register(298, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'birch'}}"); ++ register(299, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'jungle'}}"); ++ register(300, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'oak'}}"); ++ register(301, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'spruce'}}"); ++ register(302, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'birch'}}"); ++ register(303, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'jungle'}}"); ++ register(304, "{Name:'minecraft:sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'false'}}"); ++ register(305, "{Name:'minecraft:wet_sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'true'}}"); ++ register(320, "{Name:'minecraft:glass'}", "{Name:'minecraft:glass'}"); ++ register(336, "{Name:'minecraft:lapis_ore'}", "{Name:'minecraft:lapis_ore'}"); ++ register(352, "{Name:'minecraft:lapis_block'}", "{Name:'minecraft:lapis_block'}"); ++ register(368, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}"); ++ register(369, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}"); ++ register(370, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}"); ++ register(371, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}"); ++ register(372, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}"); ++ register(373, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}"); ++ register(376, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}"); ++ register(377, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}"); ++ register(378, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}"); ++ register(379, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}"); ++ register(380, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}"); ++ register(381, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}"); ++ register(384, "{Name:'minecraft:sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'sandstone'}}"); ++ register(385, "{Name:'minecraft:chiseled_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'chiseled_sandstone'}}"); ++ register(386, "{Name:'minecraft:cut_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'smooth_sandstone'}}"); ++ register(400, "{Name:'minecraft:note_block'}", "{Name:'minecraft:noteblock'}"); ++ register(416, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'foot'}}"); ++ register(417, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'foot'}}"); ++ register(418, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'foot'}}"); ++ register(419, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'foot'}}"); ++ register(424, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'head'}}"); ++ register(425, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'head'}}"); ++ register(426, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'head'}}"); ++ register(427, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'head'}}"); ++ register(428, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'head'}}"); ++ register(429, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'head'}}"); ++ register(430, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'head'}}"); ++ register(431, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'head'}}"); ++ register(432, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(433, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(434, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(435, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(436, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(437, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(440, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(441, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(442, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(443, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(444, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(445, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(448, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(449, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(450, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(451, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(452, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(453, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(456, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(457, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(458, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(459, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(460, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(461, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(464, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}"); ++ register(465, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}"); ++ register(466, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}"); ++ register(467, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}"); ++ register(468, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}"); ++ register(469, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}"); ++ register(472, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}"); ++ register(473, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}"); ++ register(474, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}"); ++ register(475, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}"); ++ register(476, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}"); ++ register(477, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}"); ++ register(480, "{Name:'minecraft:cobweb'}", "{Name:'minecraft:web'}"); ++ register(496, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:tallgrass',Properties:{type:'dead_bush'}}"); ++ register(497, "{Name:'minecraft:grass'}", "{Name:'minecraft:tallgrass',Properties:{type:'tall_grass'}}"); ++ register(498, "{Name:'minecraft:fern'}", "{Name:'minecraft:tallgrass',Properties:{type:'fern'}}"); ++ register(512, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:deadbush'}"); ++ register(528, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}"); ++ register(529, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}"); ++ register(530, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}"); ++ register(531, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}"); ++ register(532, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}"); ++ register(533, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}"); ++ register(536, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}"); ++ register(537, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}"); ++ register(538, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}"); ++ register(539, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}"); ++ register(540, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}"); ++ register(541, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}"); ++ register(544, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'normal'}}"); ++ register(545, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'normal'}}"); ++ register(546, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'normal'}}"); ++ register(547, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'normal'}}"); ++ register(548, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'normal'}}"); ++ register(549, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'normal'}}"); ++ register(552, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'sticky'}}"); ++ register(553, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'sticky'}}"); ++ register(554, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'sticky'}}"); ++ register(555, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'sticky'}}"); ++ register(556, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'sticky'}}"); ++ register(557, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'sticky'}}"); ++ register(560, "{Name:'minecraft:white_wool'}", "{Name:'minecraft:wool',Properties:{color:'white'}}"); ++ register(561, "{Name:'minecraft:orange_wool'}", "{Name:'minecraft:wool',Properties:{color:'orange'}}"); ++ register(562, "{Name:'minecraft:magenta_wool'}", "{Name:'minecraft:wool',Properties:{color:'magenta'}}"); ++ register(563, "{Name:'minecraft:light_blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'light_blue'}}"); ++ register(564, "{Name:'minecraft:yellow_wool'}", "{Name:'minecraft:wool',Properties:{color:'yellow'}}"); ++ register(565, "{Name:'minecraft:lime_wool'}", "{Name:'minecraft:wool',Properties:{color:'lime'}}"); ++ register(566, "{Name:'minecraft:pink_wool'}", "{Name:'minecraft:wool',Properties:{color:'pink'}}"); ++ register(567, "{Name:'minecraft:gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'gray'}}"); ++ register(568, "{Name:'minecraft:light_gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'silver'}}"); ++ register(569, "{Name:'minecraft:cyan_wool'}", "{Name:'minecraft:wool',Properties:{color:'cyan'}}"); ++ register(570, "{Name:'minecraft:purple_wool'}", "{Name:'minecraft:wool',Properties:{color:'purple'}}"); ++ register(571, "{Name:'minecraft:blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'blue'}}"); ++ register(572, "{Name:'minecraft:brown_wool'}", "{Name:'minecraft:wool',Properties:{color:'brown'}}"); ++ register(573, "{Name:'minecraft:green_wool'}", "{Name:'minecraft:wool',Properties:{color:'green'}}"); ++ register(574, "{Name:'minecraft:red_wool'}", "{Name:'minecraft:wool',Properties:{color:'red'}}"); ++ register(575, "{Name:'minecraft:black_wool'}", "{Name:'minecraft:wool',Properties:{color:'black'}}"); ++ register(576, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'normal'}}"); ++ register(577, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'normal'}}"); ++ register(578, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'normal'}}"); ++ register(579, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'normal'}}"); ++ register(580, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'normal'}}"); ++ register(581, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'normal'}}"); ++ register(584, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'sticky'}}"); ++ register(585, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'sticky'}}"); ++ register(586, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'sticky'}}"); ++ register(587, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'sticky'}}"); ++ register(588, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'sticky'}}"); ++ register(589, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'sticky'}}"); ++ register(592, "{Name:'minecraft:dandelion'}", "{Name:'minecraft:yellow_flower',Properties:{type:'dandelion'}}"); ++ register(608, "{Name:'minecraft:poppy'}", "{Name:'minecraft:red_flower',Properties:{type:'poppy'}}"); ++ register(609, "{Name:'minecraft:blue_orchid'}", "{Name:'minecraft:red_flower',Properties:{type:'blue_orchid'}}"); ++ register(610, "{Name:'minecraft:allium'}", "{Name:'minecraft:red_flower',Properties:{type:'allium'}}"); ++ register(611, "{Name:'minecraft:azure_bluet'}", "{Name:'minecraft:red_flower',Properties:{type:'houstonia'}}"); ++ register(612, "{Name:'minecraft:red_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'red_tulip'}}"); ++ register(613, "{Name:'minecraft:orange_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'orange_tulip'}}"); ++ register(614, "{Name:'minecraft:white_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'white_tulip'}}"); ++ register(615, "{Name:'minecraft:pink_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'pink_tulip'}}"); ++ register(616, "{Name:'minecraft:oxeye_daisy'}", "{Name:'minecraft:red_flower',Properties:{type:'oxeye_daisy'}}"); ++ register(624, "{Name:'minecraft:brown_mushroom'}", "{Name:'minecraft:brown_mushroom'}"); ++ register(640, "{Name:'minecraft:red_mushroom'}", "{Name:'minecraft:red_mushroom'}"); ++ register(656, "{Name:'minecraft:gold_block'}", "{Name:'minecraft:gold_block'}"); ++ register(672, "{Name:'minecraft:iron_block'}", "{Name:'minecraft:iron_block'}"); ++ register(688, "{Name:'minecraft:stone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone'}}"); ++ register(689, "{Name:'minecraft:sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'sandstone'}}"); ++ register(690, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'wood_old'}}"); ++ register(691, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'cobblestone'}}"); ++ register(692, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'brick'}}"); ++ register(693, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone_brick'}}"); ++ register(694, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'nether_brick'}}"); ++ register(695, "{Name:'minecraft:quartz_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'quartz'}}"); ++ register(696, "{Name:'minecraft:smooth_stone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone'}}"); ++ register(697, "{Name:'minecraft:smooth_sandstone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'sandstone'}}"); ++ register(698, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'wood_old'}}"); ++ register(699, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'cobblestone'}}"); ++ register(700, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'brick'}}"); ++ register(701, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone_brick'}}"); ++ register(702, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'nether_brick'}}"); ++ register(703, "{Name:'minecraft:smooth_quartz'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'quartz'}}"); ++ register(704, "{Name:'minecraft:stone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone'}}"); ++ register(705, "{Name:'minecraft:sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'sandstone'}}"); ++ register(706, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'wood_old'}}"); ++ register(707, "{Name:'minecraft:cobblestone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'cobblestone'}}"); ++ register(708, "{Name:'minecraft:brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'brick'}}"); ++ register(709, "{Name:'minecraft:stone_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone_brick'}}"); ++ register(710, "{Name:'minecraft:nether_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'nether_brick'}}"); ++ register(711, "{Name:'minecraft:quartz_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'quartz'}}"); ++ register(712, "{Name:'minecraft:stone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone'}}"); ++ register(713, "{Name:'minecraft:sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'sandstone'}}"); ++ register(714, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'wood_old'}}"); ++ register(715, "{Name:'minecraft:cobblestone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'cobblestone'}}"); ++ register(716, "{Name:'minecraft:brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'brick'}}"); ++ register(717, "{Name:'minecraft:stone_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone_brick'}}"); ++ register(718, "{Name:'minecraft:nether_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'nether_brick'}}"); ++ register(719, "{Name:'minecraft:quartz_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'quartz'}}"); ++ register(720, "{Name:'minecraft:bricks'}", "{Name:'minecraft:brick_block'}"); ++ register(736, "{Name:'minecraft:tnt',Properties:{unstable:'false'}}", "{Name:'minecraft:tnt',Properties:{explode:'false'}}"); ++ register(737, "{Name:'minecraft:tnt',Properties:{unstable:'true'}}", "{Name:'minecraft:tnt',Properties:{explode:'true'}}"); ++ register(752, "{Name:'minecraft:bookshelf'}", "{Name:'minecraft:bookshelf'}"); ++ register(768, "{Name:'minecraft:mossy_cobblestone'}", "{Name:'minecraft:mossy_cobblestone'}"); ++ register(784, "{Name:'minecraft:obsidian'}", "{Name:'minecraft:obsidian'}"); ++ register(801, "{Name:'minecraft:wall_torch',Properties:{facing:'east'}}", "{Name:'minecraft:torch',Properties:{facing:'east'}}"); ++ register(802, "{Name:'minecraft:wall_torch',Properties:{facing:'west'}}", "{Name:'minecraft:torch',Properties:{facing:'west'}}"); ++ register(803, "{Name:'minecraft:wall_torch',Properties:{facing:'south'}}", "{Name:'minecraft:torch',Properties:{facing:'south'}}"); ++ register(804, "{Name:'minecraft:wall_torch',Properties:{facing:'north'}}", "{Name:'minecraft:torch',Properties:{facing:'north'}}"); ++ register(805, "{Name:'minecraft:torch'}", "{Name:'minecraft:torch',Properties:{facing:'up'}}"); ++ register(816, "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(817, "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(818, "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(819, "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(820, "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(821, "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(822, "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(823, "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(824, "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(825, "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(826, "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(827, "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(828, "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(829, "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(830, "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(831, "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(832, "{Name:'minecraft:mob_spawner'}", "{Name:'minecraft:mob_spawner'}"); ++ register(848, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(849, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(850, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(851, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(852, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(853, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(854, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(855, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(866, "{Name:'minecraft:chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'north'}}"); ++ register(867, "{Name:'minecraft:chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'south'}}"); ++ register(868, "{Name:'minecraft:chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'west'}}"); ++ register(869, "{Name:'minecraft:chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'east'}}"); ++ register(880, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'up'}}"); ++ register(881, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'up'}}"); ++ register(882, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'up'}}"); ++ register(883, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'up'}}"); ++ register(884, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'up'}}"); ++ register(885, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'up'}}"); ++ register(886, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'up'}}"); ++ register(887, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'up'}}"); ++ register(888, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'up'}}"); ++ register(889, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'up'}}"); ++ register(890, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'up'}}"); ++ register(891, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'up'}}"); ++ register(892, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'up'}}"); ++ register(893, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'up'}}"); ++ register(894, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'up'}}"); ++ register(895, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'up'}}"); ++ register(896, "{Name:'minecraft:diamond_ore'}", "{Name:'minecraft:diamond_ore'}"); ++ register(912, "{Name:'minecraft:diamond_block'}", "{Name:'minecraft:diamond_block'}"); ++ register(928, "{Name:'minecraft:crafting_table'}", "{Name:'minecraft:crafting_table'}"); ++ register(944, "{Name:'minecraft:wheat',Properties:{age:'0'}}", "{Name:'minecraft:wheat',Properties:{age:'0'}}"); ++ register(945, "{Name:'minecraft:wheat',Properties:{age:'1'}}", "{Name:'minecraft:wheat',Properties:{age:'1'}}"); ++ register(946, "{Name:'minecraft:wheat',Properties:{age:'2'}}", "{Name:'minecraft:wheat',Properties:{age:'2'}}"); ++ register(947, "{Name:'minecraft:wheat',Properties:{age:'3'}}", "{Name:'minecraft:wheat',Properties:{age:'3'}}"); ++ register(948, "{Name:'minecraft:wheat',Properties:{age:'4'}}", "{Name:'minecraft:wheat',Properties:{age:'4'}}"); ++ register(949, "{Name:'minecraft:wheat',Properties:{age:'5'}}", "{Name:'minecraft:wheat',Properties:{age:'5'}}"); ++ register(950, "{Name:'minecraft:wheat',Properties:{age:'6'}}", "{Name:'minecraft:wheat',Properties:{age:'6'}}"); ++ register(951, "{Name:'minecraft:wheat',Properties:{age:'7'}}", "{Name:'minecraft:wheat',Properties:{age:'7'}}"); ++ register(960, "{Name:'minecraft:farmland',Properties:{moisture:'0'}}", "{Name:'minecraft:farmland',Properties:{moisture:'0'}}"); ++ register(961, "{Name:'minecraft:farmland',Properties:{moisture:'1'}}", "{Name:'minecraft:farmland',Properties:{moisture:'1'}}"); ++ register(962, "{Name:'minecraft:farmland',Properties:{moisture:'2'}}", "{Name:'minecraft:farmland',Properties:{moisture:'2'}}"); ++ register(963, "{Name:'minecraft:farmland',Properties:{moisture:'3'}}", "{Name:'minecraft:farmland',Properties:{moisture:'3'}}"); ++ register(964, "{Name:'minecraft:farmland',Properties:{moisture:'4'}}", "{Name:'minecraft:farmland',Properties:{moisture:'4'}}"); ++ register(965, "{Name:'minecraft:farmland',Properties:{moisture:'5'}}", "{Name:'minecraft:farmland',Properties:{moisture:'5'}}"); ++ register(966, "{Name:'minecraft:farmland',Properties:{moisture:'6'}}", "{Name:'minecraft:farmland',Properties:{moisture:'6'}}"); ++ register(967, "{Name:'minecraft:farmland',Properties:{moisture:'7'}}", "{Name:'minecraft:farmland',Properties:{moisture:'7'}}"); ++ register(978, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'north'}}"); ++ register(979, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'south'}}"); ++ register(980, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'west'}}"); ++ register(981, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'east'}}"); ++ register(994, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'north'}}"); ++ register(995, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'south'}}"); ++ register(996, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'west'}}"); ++ register(997, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'east'}}"); ++ register(1008, "{Name:'minecraft:sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}"); ++ register(1009, "{Name:'minecraft:sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}"); ++ register(1010, "{Name:'minecraft:sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}"); ++ register(1011, "{Name:'minecraft:sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}"); ++ register(1012, "{Name:'minecraft:sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}"); ++ register(1013, "{Name:'minecraft:sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}"); ++ register(1014, "{Name:'minecraft:sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}"); ++ register(1015, "{Name:'minecraft:sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}"); ++ register(1016, "{Name:'minecraft:sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}"); ++ register(1017, "{Name:'minecraft:sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}"); ++ register(1018, "{Name:'minecraft:sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}"); ++ register(1019, "{Name:'minecraft:sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}"); ++ register(1020, "{Name:'minecraft:sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}"); ++ register(1021, "{Name:'minecraft:sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}"); ++ register(1022, "{Name:'minecraft:sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}"); ++ register(1023, "{Name:'minecraft:sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}"); ++ register(1024, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1025, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1026, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1027, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1028, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1029, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1030, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1031, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1032, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1033, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(1034, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(1035, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(1036, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1037, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1038, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1039, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1042, "{Name:'minecraft:ladder',Properties:{facing:'north'}}", "{Name:'minecraft:ladder',Properties:{facing:'north'}}"); ++ register(1043, "{Name:'minecraft:ladder',Properties:{facing:'south'}}", "{Name:'minecraft:ladder',Properties:{facing:'south'}}"); ++ register(1044, "{Name:'minecraft:ladder',Properties:{facing:'west'}}", "{Name:'minecraft:ladder',Properties:{facing:'west'}}"); ++ register(1045, "{Name:'minecraft:ladder',Properties:{facing:'east'}}", "{Name:'minecraft:ladder',Properties:{facing:'east'}}"); ++ register(1056, "{Name:'minecraft:rail',Properties:{shape:'north_south'}}", "{Name:'minecraft:rail',Properties:{shape:'north_south'}}"); ++ register(1057, "{Name:'minecraft:rail',Properties:{shape:'east_west'}}", "{Name:'minecraft:rail',Properties:{shape:'east_west'}}"); ++ register(1058, "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}"); ++ register(1059, "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}"); ++ register(1060, "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}"); ++ register(1061, "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}"); ++ register(1062, "{Name:'minecraft:rail',Properties:{shape:'south_east'}}", "{Name:'minecraft:rail',Properties:{shape:'south_east'}}"); ++ register(1063, "{Name:'minecraft:rail',Properties:{shape:'south_west'}}", "{Name:'minecraft:rail',Properties:{shape:'south_west'}}"); ++ register(1064, "{Name:'minecraft:rail',Properties:{shape:'north_west'}}", "{Name:'minecraft:rail',Properties:{shape:'north_west'}}"); ++ register(1065, "{Name:'minecraft:rail',Properties:{shape:'north_east'}}", "{Name:'minecraft:rail',Properties:{shape:'north_east'}}"); ++ register(1072, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1073, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1074, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1075, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1076, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1077, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1078, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1079, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1090, "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}"); ++ register(1091, "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}"); ++ register(1092, "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}"); ++ register(1093, "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}"); ++ register(1104, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'false'}}"); ++ register(1105, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'false'}}"); ++ register(1106, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'false'}}"); ++ register(1107, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'false'}}"); ++ register(1108, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'false'}}"); ++ register(1109, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'false'}}"); ++ register(1110, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'false'}}"); ++ register(1111, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'false'}}"); ++ register(1112, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'true'}}"); ++ register(1113, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'true'}}"); ++ register(1114, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'true'}}"); ++ register(1115, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'true'}}"); ++ register(1116, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'true'}}"); ++ register(1117, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'true'}}"); ++ register(1118, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'true'}}"); ++ register(1119, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'true'}}"); ++ register(1120, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}"); ++ register(1121, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}"); ++ register(1136, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1137, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1138, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1139, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(1140, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1141, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1142, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1143, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(1144, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1145, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(1146, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(1147, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(1148, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1149, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1150, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1151, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(1152, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'false'}}"); ++ register(1153, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'true'}}"); ++ register(1168, "{Name:'minecraft:redstone_ore',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_ore'}"); ++ register(1184, "{Name:'minecraft:redstone_ore',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_ore'}"); ++ register(1201, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'east'}}"); ++ register(1202, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'west'}}"); ++ register(1203, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'south'}}"); ++ register(1204, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'north'}}"); ++ register(1205, "{Name:'minecraft:redstone_torch',Properties:{lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'up'}}"); ++ register(1217, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'east'}}"); ++ register(1218, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'west'}}"); ++ register(1219, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'south'}}"); ++ register(1220, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'north'}}"); ++ register(1221, "{Name:'minecraft:redstone_torch',Properties:{lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'up'}}"); ++ register(1232, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'false'}}"); ++ register(1233, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'false'}}"); ++ register(1234, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'false'}}"); ++ register(1235, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'false'}}"); ++ register(1236, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'false'}}"); ++ register(1237, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'false'}}"); ++ register(1240, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'true'}}"); ++ register(1241, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'true'}}"); ++ register(1242, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'true'}}"); ++ register(1243, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'true'}}"); ++ register(1244, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'true'}}"); ++ register(1245, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'true'}}"); ++ register(1248, "{Name:'minecraft:snow',Properties:{layers:'1'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'1'}}"); ++ register(1249, "{Name:'minecraft:snow',Properties:{layers:'2'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'2'}}"); ++ register(1250, "{Name:'minecraft:snow',Properties:{layers:'3'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'3'}}"); ++ register(1251, "{Name:'minecraft:snow',Properties:{layers:'4'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'4'}}"); ++ register(1252, "{Name:'minecraft:snow',Properties:{layers:'5'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'5'}}"); ++ register(1253, "{Name:'minecraft:snow',Properties:{layers:'6'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'6'}}"); ++ register(1254, "{Name:'minecraft:snow',Properties:{layers:'7'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'7'}}"); ++ register(1255, "{Name:'minecraft:snow',Properties:{layers:'8'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'8'}}"); ++ register(1264, "{Name:'minecraft:ice'}", "{Name:'minecraft:ice'}"); ++ register(1280, "{Name:'minecraft:snow_block'}", "{Name:'minecraft:snow'}"); ++ register(1296, "{Name:'minecraft:cactus',Properties:{age:'0'}}", "{Name:'minecraft:cactus',Properties:{age:'0'}}"); ++ register(1297, "{Name:'minecraft:cactus',Properties:{age:'1'}}", "{Name:'minecraft:cactus',Properties:{age:'1'}}"); ++ register(1298, "{Name:'minecraft:cactus',Properties:{age:'2'}}", "{Name:'minecraft:cactus',Properties:{age:'2'}}"); ++ register(1299, "{Name:'minecraft:cactus',Properties:{age:'3'}}", "{Name:'minecraft:cactus',Properties:{age:'3'}}"); ++ register(1300, "{Name:'minecraft:cactus',Properties:{age:'4'}}", "{Name:'minecraft:cactus',Properties:{age:'4'}}"); ++ register(1301, "{Name:'minecraft:cactus',Properties:{age:'5'}}", "{Name:'minecraft:cactus',Properties:{age:'5'}}"); ++ register(1302, "{Name:'minecraft:cactus',Properties:{age:'6'}}", "{Name:'minecraft:cactus',Properties:{age:'6'}}"); ++ register(1303, "{Name:'minecraft:cactus',Properties:{age:'7'}}", "{Name:'minecraft:cactus',Properties:{age:'7'}}"); ++ register(1304, "{Name:'minecraft:cactus',Properties:{age:'8'}}", "{Name:'minecraft:cactus',Properties:{age:'8'}}"); ++ register(1305, "{Name:'minecraft:cactus',Properties:{age:'9'}}", "{Name:'minecraft:cactus',Properties:{age:'9'}}"); ++ register(1306, "{Name:'minecraft:cactus',Properties:{age:'10'}}", "{Name:'minecraft:cactus',Properties:{age:'10'}}"); ++ register(1307, "{Name:'minecraft:cactus',Properties:{age:'11'}}", "{Name:'minecraft:cactus',Properties:{age:'11'}}"); ++ register(1308, "{Name:'minecraft:cactus',Properties:{age:'12'}}", "{Name:'minecraft:cactus',Properties:{age:'12'}}"); ++ register(1309, "{Name:'minecraft:cactus',Properties:{age:'13'}}", "{Name:'minecraft:cactus',Properties:{age:'13'}}"); ++ register(1310, "{Name:'minecraft:cactus',Properties:{age:'14'}}", "{Name:'minecraft:cactus',Properties:{age:'14'}}"); ++ register(1311, "{Name:'minecraft:cactus',Properties:{age:'15'}}", "{Name:'minecraft:cactus',Properties:{age:'15'}}"); ++ register(1312, "{Name:'minecraft:clay'}", "{Name:'minecraft:clay'}"); ++ register(1328, "{Name:'minecraft:sugar_cane',Properties:{age:'0'}}", "{Name:'minecraft:reeds',Properties:{age:'0'}}"); ++ register(1329, "{Name:'minecraft:sugar_cane',Properties:{age:'1'}}", "{Name:'minecraft:reeds',Properties:{age:'1'}}"); ++ register(1330, "{Name:'minecraft:sugar_cane',Properties:{age:'2'}}", "{Name:'minecraft:reeds',Properties:{age:'2'}}"); ++ register(1331, "{Name:'minecraft:sugar_cane',Properties:{age:'3'}}", "{Name:'minecraft:reeds',Properties:{age:'3'}}"); ++ register(1332, "{Name:'minecraft:sugar_cane',Properties:{age:'4'}}", "{Name:'minecraft:reeds',Properties:{age:'4'}}"); ++ register(1333, "{Name:'minecraft:sugar_cane',Properties:{age:'5'}}", "{Name:'minecraft:reeds',Properties:{age:'5'}}"); ++ register(1334, "{Name:'minecraft:sugar_cane',Properties:{age:'6'}}", "{Name:'minecraft:reeds',Properties:{age:'6'}}"); ++ register(1335, "{Name:'minecraft:sugar_cane',Properties:{age:'7'}}", "{Name:'minecraft:reeds',Properties:{age:'7'}}"); ++ register(1336, "{Name:'minecraft:sugar_cane',Properties:{age:'8'}}", "{Name:'minecraft:reeds',Properties:{age:'8'}}"); ++ register(1337, "{Name:'minecraft:sugar_cane',Properties:{age:'9'}}", "{Name:'minecraft:reeds',Properties:{age:'9'}}"); ++ register(1338, "{Name:'minecraft:sugar_cane',Properties:{age:'10'}}", "{Name:'minecraft:reeds',Properties:{age:'10'}}"); ++ register(1339, "{Name:'minecraft:sugar_cane',Properties:{age:'11'}}", "{Name:'minecraft:reeds',Properties:{age:'11'}}"); ++ register(1340, "{Name:'minecraft:sugar_cane',Properties:{age:'12'}}", "{Name:'minecraft:reeds',Properties:{age:'12'}}"); ++ register(1341, "{Name:'minecraft:sugar_cane',Properties:{age:'13'}}", "{Name:'minecraft:reeds',Properties:{age:'13'}}"); ++ register(1342, "{Name:'minecraft:sugar_cane',Properties:{age:'14'}}", "{Name:'minecraft:reeds',Properties:{age:'14'}}"); ++ register(1343, "{Name:'minecraft:sugar_cane',Properties:{age:'15'}}", "{Name:'minecraft:reeds',Properties:{age:'15'}}"); ++ register(1344, "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}"); ++ register(1345, "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}"); ++ register(1360, "{Name:'minecraft:oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1376, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'south'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'south'}}"); ++ register(1377, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'west'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'west'}}"); ++ register(1378, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'north'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'north'}}"); ++ register(1379, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'east'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'east'}}"); ++ register(1392, "{Name:'minecraft:netherrack'}", "{Name:'minecraft:netherrack'}"); ++ register(1408, "{Name:'minecraft:soul_sand'}", "{Name:'minecraft:soul_sand'}"); ++ register(1424, "{Name:'minecraft:glowstone'}", "{Name:'minecraft:glowstone'}"); ++ register(1441, "{Name:'minecraft:portal',Properties:{axis:'x'}}", "{Name:'minecraft:portal',Properties:{axis:'x'}}"); ++ register(1442, "{Name:'minecraft:portal',Properties:{axis:'z'}}", "{Name:'minecraft:portal',Properties:{axis:'z'}}"); ++ register(1456, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'south'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'south'}}"); ++ register(1457, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'west'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'west'}}"); ++ register(1458, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'north'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'north'}}"); ++ register(1459, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'east'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'east'}}"); ++ register(1472, "{Name:'minecraft:cake',Properties:{bites:'0'}}", "{Name:'minecraft:cake',Properties:{bites:'0'}}"); ++ register(1473, "{Name:'minecraft:cake',Properties:{bites:'1'}}", "{Name:'minecraft:cake',Properties:{bites:'1'}}"); ++ register(1474, "{Name:'minecraft:cake',Properties:{bites:'2'}}", "{Name:'minecraft:cake',Properties:{bites:'2'}}"); ++ register(1475, "{Name:'minecraft:cake',Properties:{bites:'3'}}", "{Name:'minecraft:cake',Properties:{bites:'3'}}"); ++ register(1476, "{Name:'minecraft:cake',Properties:{bites:'4'}}", "{Name:'minecraft:cake',Properties:{bites:'4'}}"); ++ register(1477, "{Name:'minecraft:cake',Properties:{bites:'5'}}", "{Name:'minecraft:cake',Properties:{bites:'5'}}"); ++ register(1478, "{Name:'minecraft:cake',Properties:{bites:'6'}}", "{Name:'minecraft:cake',Properties:{bites:'6'}}"); ++ register(1488, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); ++ register(1489, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); ++ register(1490, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); ++ register(1491, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); ++ register(1492, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); ++ register(1493, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); ++ register(1494, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); ++ register(1495, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); ++ register(1496, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); ++ register(1497, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); ++ register(1498, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); ++ register(1499, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); ++ register(1500, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); ++ register(1501, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); ++ register(1502, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); ++ register(1503, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); ++ register(1504, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); ++ register(1505, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); ++ register(1506, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); ++ register(1507, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); ++ register(1508, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); ++ register(1509, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); ++ register(1510, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); ++ register(1511, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); ++ register(1512, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); ++ register(1513, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); ++ register(1514, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); ++ register(1515, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); ++ register(1516, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); ++ register(1517, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); ++ register(1518, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); ++ register(1519, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); ++ register(1520, "{Name:'minecraft:white_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'white'}}"); ++ register(1521, "{Name:'minecraft:orange_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'orange'}}"); ++ register(1522, "{Name:'minecraft:magenta_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'magenta'}}"); ++ register(1523, "{Name:'minecraft:light_blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'light_blue'}}"); ++ register(1524, "{Name:'minecraft:yellow_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'yellow'}}"); ++ register(1525, "{Name:'minecraft:lime_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'lime'}}"); ++ register(1526, "{Name:'minecraft:pink_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'pink'}}"); ++ register(1527, "{Name:'minecraft:gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'gray'}}"); ++ register(1528, "{Name:'minecraft:light_gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'silver'}}"); ++ register(1529, "{Name:'minecraft:cyan_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'cyan'}}"); ++ register(1530, "{Name:'minecraft:purple_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'purple'}}"); ++ register(1531, "{Name:'minecraft:blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'blue'}}"); ++ register(1532, "{Name:'minecraft:brown_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'brown'}}"); ++ register(1533, "{Name:'minecraft:green_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'green'}}"); ++ register(1534, "{Name:'minecraft:red_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'red'}}"); ++ register(1535, "{Name:'minecraft:black_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'black'}}"); ++ register(1536, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); ++ register(1537, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); ++ register(1538, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); ++ register(1539, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); ++ register(1540, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); ++ register(1541, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); ++ register(1542, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); ++ register(1543, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); ++ register(1544, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); ++ register(1545, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); ++ register(1546, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); ++ register(1547, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); ++ register(1548, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); ++ register(1549, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); ++ register(1550, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); ++ register(1551, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); ++ register(1552, "{Name:'minecraft:infested_stone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone'}}"); ++ register(1553, "{Name:'minecraft:infested_cobblestone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cobblestone'}}"); ++ register(1554, "{Name:'minecraft:infested_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone_brick'}}"); ++ register(1555, "{Name:'minecraft:infested_mossy_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'mossy_brick'}}"); ++ register(1556, "{Name:'minecraft:infested_cracked_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cracked_brick'}}"); ++ register(1557, "{Name:'minecraft:infested_chiseled_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'chiseled_brick'}}"); ++ register(1568, "{Name:'minecraft:stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'stonebrick'}}"); ++ register(1569, "{Name:'minecraft:mossy_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'mossy_stonebrick'}}"); ++ register(1570, "{Name:'minecraft:cracked_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'cracked_stonebrick'}}"); ++ register(1571, "{Name:'minecraft:chiseled_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'chiseled_stonebrick'}}"); ++ register(1584, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_inside'}}"); ++ register(1585, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_west'}}"); ++ register(1586, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north'}}"); ++ register(1587, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_east'}}"); ++ register(1588, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'west'}}"); ++ register(1589, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'center'}}"); ++ register(1590, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'east'}}"); ++ register(1591, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_west'}}"); ++ register(1592, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south'}}"); ++ register(1593, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_east'}}"); ++ register(1594, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'stem'}}"); ++ register(1595, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1596, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1597, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1598, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_outside'}}"); ++ register(1599, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_stem'}}"); ++ register(1600, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_inside'}}"); ++ register(1601, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_west'}}"); ++ register(1602, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north'}}"); ++ register(1603, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_east'}}"); ++ register(1604, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'west'}}"); ++ register(1605, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'center'}}"); ++ register(1606, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'east'}}"); ++ register(1607, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_west'}}"); ++ register(1608, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south'}}"); ++ register(1609, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_east'}}"); ++ register(1610, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'stem'}}"); ++ register(1611, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1612, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1613, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); ++ register(1614, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_outside'}}"); ++ register(1615, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_stem'}}"); ++ register(1616, "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1632, "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1648, "{Name:'minecraft:melon_block'}", "{Name:'minecraft:melon_block'}"); ++ register(1664, "{Name:'minecraft:pumpkin_stem',Properties:{age:'0'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'west'}}"); ++ register(1665, "{Name:'minecraft:pumpkin_stem',Properties:{age:'1'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'west'}}"); ++ register(1666, "{Name:'minecraft:pumpkin_stem',Properties:{age:'2'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'west'}}"); ++ register(1667, "{Name:'minecraft:pumpkin_stem',Properties:{age:'3'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'west'}}"); ++ register(1668, "{Name:'minecraft:pumpkin_stem',Properties:{age:'4'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'west'}}"); ++ register(1669, "{Name:'minecraft:pumpkin_stem',Properties:{age:'5'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'west'}}"); ++ register(1670, "{Name:'minecraft:pumpkin_stem',Properties:{age:'6'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'west'}}"); ++ register(1671, "{Name:'minecraft:pumpkin_stem',Properties:{age:'7'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'west'}}"); ++ register(1680, "{Name:'minecraft:melon_stem',Properties:{age:'0'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'west'}}"); ++ register(1681, "{Name:'minecraft:melon_stem',Properties:{age:'1'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'west'}}"); ++ register(1682, "{Name:'minecraft:melon_stem',Properties:{age:'2'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'west'}}"); ++ register(1683, "{Name:'minecraft:melon_stem',Properties:{age:'3'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'west'}}"); ++ register(1684, "{Name:'minecraft:melon_stem',Properties:{age:'4'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'west'}}"); ++ register(1685, "{Name:'minecraft:melon_stem',Properties:{age:'5'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'west'}}"); ++ register(1686, "{Name:'minecraft:melon_stem',Properties:{age:'6'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'west'}}"); ++ register(1687, "{Name:'minecraft:melon_stem',Properties:{age:'7'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'west'}}"); ++ register(1696, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}"); ++ register(1697, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}"); ++ register(1698, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}"); ++ register(1699, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}"); ++ register(1700, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}"); ++ register(1701, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}"); ++ register(1702, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}"); ++ register(1703, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(1704, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}"); ++ register(1705, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}"); ++ register(1706, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}"); ++ register(1707, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}"); ++ register(1708, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}"); ++ register(1709, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}"); ++ register(1710, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}"); ++ register(1711, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(1712, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1713, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1714, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1715, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(1716, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1717, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1718, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1719, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(1720, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1721, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1722, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1723, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(1724, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1725, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1726, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1727, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(1728, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1729, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1730, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1731, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1732, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1733, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1734, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1735, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1744, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1745, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1746, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1747, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1748, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1749, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1750, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1751, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1760, "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); ++ register(1776, "{Name:'minecraft:lily_pad'}", "{Name:'minecraft:waterlily'}"); ++ register(1792, "{Name:'minecraft:nether_bricks'}", "{Name:'minecraft:nether_brick'}"); ++ register(1808, "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(1824, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(1825, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(1826, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(1827, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(1828, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(1829, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(1830, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(1831, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(1840, "{Name:'minecraft:nether_wart',Properties:{age:'0'}}", "{Name:'minecraft:nether_wart',Properties:{age:'0'}}"); ++ register(1841, "{Name:'minecraft:nether_wart',Properties:{age:'1'}}", "{Name:'minecraft:nether_wart',Properties:{age:'1'}}"); ++ register(1842, "{Name:'minecraft:nether_wart',Properties:{age:'2'}}", "{Name:'minecraft:nether_wart',Properties:{age:'2'}}"); ++ register(1843, "{Name:'minecraft:nether_wart',Properties:{age:'3'}}", "{Name:'minecraft:nether_wart',Properties:{age:'3'}}"); ++ register(1856, "{Name:'minecraft:enchanting_table'}", "{Name:'minecraft:enchanting_table'}"); ++ register(1872, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}"); ++ register(1873, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}"); ++ register(1874, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}"); ++ register(1875, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}"); ++ register(1876, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}"); ++ register(1877, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}"); ++ register(1878, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}"); ++ register(1879, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}"); ++ register(1888, "{Name:'minecraft:cauldron',Properties:{level:'0'}}", "{Name:'minecraft:cauldron',Properties:{level:'0'}}"); ++ register(1889, "{Name:'minecraft:cauldron',Properties:{level:'1'}}", "{Name:'minecraft:cauldron',Properties:{level:'1'}}"); ++ register(1890, "{Name:'minecraft:cauldron',Properties:{level:'2'}}", "{Name:'minecraft:cauldron',Properties:{level:'2'}}"); ++ register(1891, "{Name:'minecraft:cauldron',Properties:{level:'3'}}", "{Name:'minecraft:cauldron',Properties:{level:'3'}}"); ++ register(1904, "{Name:'minecraft:end_portal'}", "{Name:'minecraft:end_portal'}"); ++ register(1920, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}"); ++ register(1921, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}"); ++ register(1922, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}"); ++ register(1923, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}"); ++ register(1924, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}"); ++ register(1925, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}"); ++ register(1926, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}"); ++ register(1927, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}"); ++ register(1936, "{Name:'minecraft:end_stone'}", "{Name:'minecraft:end_stone'}"); ++ register(1952, "{Name:'minecraft:dragon_egg'}", "{Name:'minecraft:dragon_egg'}"); ++ register(1968, "{Name:'minecraft:redstone_lamp',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_lamp'}"); ++ register(1984, "{Name:'minecraft:redstone_lamp',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_lamp'}"); ++ register(2000, "{Name:'minecraft:oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'oak'}}"); ++ register(2001, "{Name:'minecraft:spruce_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'spruce'}}"); ++ register(2002, "{Name:'minecraft:birch_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'birch'}}"); ++ register(2003, "{Name:'minecraft:jungle_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'jungle'}}"); ++ register(2004, "{Name:'minecraft:acacia_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'acacia'}}"); ++ register(2005, "{Name:'minecraft:dark_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'dark_oak'}}"); ++ register(2016, "{Name:'minecraft:oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'oak'}}"); ++ register(2017, "{Name:'minecraft:spruce_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'spruce'}}"); ++ register(2018, "{Name:'minecraft:birch_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'birch'}}"); ++ register(2019, "{Name:'minecraft:jungle_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'jungle'}}"); ++ register(2020, "{Name:'minecraft:acacia_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'acacia'}}"); ++ register(2021, "{Name:'minecraft:dark_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'dark_oak'}}"); ++ register(2024, "{Name:'minecraft:oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'oak'}}"); ++ register(2025, "{Name:'minecraft:spruce_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'spruce'}}"); ++ register(2026, "{Name:'minecraft:birch_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'birch'}}"); ++ register(2027, "{Name:'minecraft:jungle_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'jungle'}}"); ++ register(2028, "{Name:'minecraft:acacia_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'acacia'}}"); ++ register(2029, "{Name:'minecraft:dark_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'dark_oak'}}"); ++ register(2032, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}"); ++ register(2033, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}"); ++ register(2034, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}"); ++ register(2035, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}"); ++ register(2036, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}"); ++ register(2037, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}"); ++ register(2038, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}"); ++ register(2039, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}"); ++ register(2040, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}"); ++ register(2041, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}"); ++ register(2042, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}"); ++ register(2043, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}"); ++ register(2048, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2049, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2050, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2051, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2052, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2053, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2054, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2055, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2064, "{Name:'minecraft:emerald_ore'}", "{Name:'minecraft:emerald_ore'}"); ++ register(2082, "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}"); ++ register(2083, "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}"); ++ register(2084, "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}"); ++ register(2085, "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}"); ++ register(2096, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}"); ++ register(2097, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}"); ++ register(2098, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}"); ++ register(2099, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}"); ++ register(2100, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}"); ++ register(2101, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}"); ++ register(2102, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}"); ++ register(2103, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}"); ++ register(2104, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}"); ++ register(2105, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}"); ++ register(2106, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}"); ++ register(2107, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}"); ++ register(2108, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}"); ++ register(2109, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}"); ++ register(2110, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}"); ++ register(2111, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}"); ++ register(2112, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2113, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2114, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2115, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2116, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2117, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2118, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2119, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2120, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2121, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2122, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2123, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); ++ register(2124, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); ++ register(2125, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); ++ register(2126, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); ++ register(2128, "{Name:'minecraft:emerald_block'}", "{Name:'minecraft:emerald_block'}"); ++ register(2144, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2145, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2146, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2147, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2148, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2149, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2150, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2151, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2160, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2161, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2162, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2163, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2164, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2165, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2166, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2167, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2176, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2177, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2178, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2179, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2180, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2181, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2182, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2183, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2192, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(2193, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(2194, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(2195, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(2196, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(2197, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(2200, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(2201, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(2202, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(2203, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(2204, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(2205, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(2208, "{Name:'minecraft:beacon'}", "{Name:'minecraft:beacon'}"); ++ register(2224, "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}"); ++ register(2225, "{Name:'minecraft:mossy_cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}"); ++ // There are a few changes made to flower pot here, notably handling how legacy data is handled. ++ // The TE itself should contain the target item and from there the proper state can be determined. However, there are ++ // blocks that do not contain a TE. So we need to make sure there is a default to fall on. ++ // I simply followed the legacy handling from BlockFlowerPot from 1.8.8 to find what legacy data mapped to what. ++ // It's better than defaulting everything to a cactus. ++ register(2240, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'0'}}"); ++ register(2241, "{Name:'minecraft:potted_poppy'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'1'}}"); ++ register(2242, "{Name:'minecraft:potted_dandelion'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'2'}}"); ++ register(2243, "{Name:'minecraft:potted_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'3'}}"); ++ register(2244, "{Name:'minecraft:potted_spruce_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'4'}}"); ++ register(2245, "{Name:'minecraft:potted_birch_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'5'}}"); ++ register(2246, "{Name:'minecraft:potted_jungle_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'6'}}"); ++ register(2247, "{Name:'minecraft:potted_red_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'7'}}"); ++ register(2248, "{Name:'minecraft:potted_brown_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'8'}}"); ++ register(2249, "{Name:'minecraft:potted_cactus'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'9'}}"); ++ register(2250, "{Name:'minecraft:potted_dead_bush'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'10'}}"); ++ register(2251, "{Name:'minecraft:potted_fern'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'11'}}"); ++ register(2252, "{Name:'minecraft:potted_acacia_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'12'}}"); ++ register(2253, "{Name:'minecraft:potted_dark_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'13'}}"); ++ register(2254, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'14'}}"); ++ register(2255, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'15'}}"); ++ register(2256, "{Name:'minecraft:carrots',Properties:{age:'0'}}", "{Name:'minecraft:carrots',Properties:{age:'0'}}"); ++ register(2257, "{Name:'minecraft:carrots',Properties:{age:'1'}}", "{Name:'minecraft:carrots',Properties:{age:'1'}}"); ++ register(2258, "{Name:'minecraft:carrots',Properties:{age:'2'}}", "{Name:'minecraft:carrots',Properties:{age:'2'}}"); ++ register(2259, "{Name:'minecraft:carrots',Properties:{age:'3'}}", "{Name:'minecraft:carrots',Properties:{age:'3'}}"); ++ register(2260, "{Name:'minecraft:carrots',Properties:{age:'4'}}", "{Name:'minecraft:carrots',Properties:{age:'4'}}"); ++ register(2261, "{Name:'minecraft:carrots',Properties:{age:'5'}}", "{Name:'minecraft:carrots',Properties:{age:'5'}}"); ++ register(2262, "{Name:'minecraft:carrots',Properties:{age:'6'}}", "{Name:'minecraft:carrots',Properties:{age:'6'}}"); ++ register(2263, "{Name:'minecraft:carrots',Properties:{age:'7'}}", "{Name:'minecraft:carrots',Properties:{age:'7'}}"); ++ register(2272, "{Name:'minecraft:potatoes',Properties:{age:'0'}}", "{Name:'minecraft:potatoes',Properties:{age:'0'}}"); ++ register(2273, "{Name:'minecraft:potatoes',Properties:{age:'1'}}", "{Name:'minecraft:potatoes',Properties:{age:'1'}}"); ++ register(2274, "{Name:'minecraft:potatoes',Properties:{age:'2'}}", "{Name:'minecraft:potatoes',Properties:{age:'2'}}"); ++ register(2275, "{Name:'minecraft:potatoes',Properties:{age:'3'}}", "{Name:'minecraft:potatoes',Properties:{age:'3'}}"); ++ register(2276, "{Name:'minecraft:potatoes',Properties:{age:'4'}}", "{Name:'minecraft:potatoes',Properties:{age:'4'}}"); ++ register(2277, "{Name:'minecraft:potatoes',Properties:{age:'5'}}", "{Name:'minecraft:potatoes',Properties:{age:'5'}}"); ++ register(2278, "{Name:'minecraft:potatoes',Properties:{age:'6'}}", "{Name:'minecraft:potatoes',Properties:{age:'6'}}"); ++ register(2279, "{Name:'minecraft:potatoes',Properties:{age:'7'}}", "{Name:'minecraft:potatoes',Properties:{age:'7'}}"); ++ register(2288, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'false'}}"); ++ register(2289, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'false'}}"); ++ register(2290, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'false'}}"); ++ register(2291, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'false'}}"); ++ register(2292, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'false'}}"); ++ register(2293, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'false'}}"); ++ register(2296, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'true'}}"); ++ register(2297, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'true'}}"); ++ register(2298, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'true'}}"); ++ register(2299, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'true'}}"); ++ register(2300, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'true'}}"); ++ register(2301, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'true'}}"); ++ register(2304, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'false'}}"); ++ register(2305, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'false'}}"); ++ register(2306, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'false'}}"); ++ register(2307, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'false'}}"); ++ register(2308, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'false'}}"); ++ register(2309, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'false'}}"); ++ register(2312, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'true'}}"); ++ register(2313, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'true'}}"); ++ register(2314, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'true'}}"); ++ register(2315, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'true'}}"); ++ register(2316, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'true'}}"); ++ register(2317, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'true'}}"); ++ register(2320, "{Name:'minecraft:anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'south'}}"); ++ register(2321, "{Name:'minecraft:anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'west'}}"); ++ register(2322, "{Name:'minecraft:anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'north'}}"); ++ register(2323, "{Name:'minecraft:anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'east'}}"); ++ register(2324, "{Name:'minecraft:chipped_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'south'}}"); ++ register(2325, "{Name:'minecraft:chipped_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'west'}}"); ++ register(2326, "{Name:'minecraft:chipped_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'north'}}"); ++ register(2327, "{Name:'minecraft:chipped_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'east'}}"); ++ register(2328, "{Name:'minecraft:damaged_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'south'}}"); ++ register(2329, "{Name:'minecraft:damaged_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'west'}}"); ++ register(2330, "{Name:'minecraft:damaged_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'north'}}"); ++ register(2331, "{Name:'minecraft:damaged_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'east'}}"); ++ register(2338, "{Name:'minecraft:trapped_chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'north'}}"); ++ register(2339, "{Name:'minecraft:trapped_chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'south'}}"); ++ register(2340, "{Name:'minecraft:trapped_chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'west'}}"); ++ register(2341, "{Name:'minecraft:trapped_chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'east'}}"); ++ register(2352, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}"); ++ register(2353, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}"); ++ register(2354, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}"); ++ register(2355, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}"); ++ register(2356, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}"); ++ register(2357, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}"); ++ register(2358, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}"); ++ register(2359, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}"); ++ register(2360, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}"); ++ register(2361, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}"); ++ register(2362, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}"); ++ register(2363, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}"); ++ register(2364, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}"); ++ register(2365, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}"); ++ register(2366, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}"); ++ register(2367, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}"); ++ register(2368, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}"); ++ register(2369, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}"); ++ register(2370, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}"); ++ register(2371, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}"); ++ register(2372, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}"); ++ register(2373, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}"); ++ register(2374, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}"); ++ register(2375, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}"); ++ register(2376, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}"); ++ register(2377, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}"); ++ register(2378, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}"); ++ register(2379, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}"); ++ register(2380, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}"); ++ register(2381, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}"); ++ register(2382, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}"); ++ register(2383, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}"); ++ register(2384, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); ++ register(2385, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); ++ register(2386, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); ++ register(2387, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); ++ register(2388, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); ++ register(2389, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); ++ register(2390, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); ++ register(2391, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); ++ register(2392, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); ++ register(2393, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); ++ register(2394, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); ++ register(2395, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); ++ register(2396, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); ++ register(2397, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); ++ register(2398, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); ++ register(2399, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); ++ register(2400, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); ++ register(2401, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); ++ register(2402, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); ++ register(2403, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); ++ register(2404, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); ++ register(2405, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); ++ register(2406, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); ++ register(2407, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); ++ register(2408, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); ++ register(2409, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); ++ register(2410, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); ++ register(2411, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); ++ register(2412, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); ++ register(2413, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); ++ register(2414, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); ++ register(2415, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); ++ register(2416, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'0'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'0'}}"); ++ register(2417, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'1'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'1'}}"); ++ register(2418, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'2'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'2'}}"); ++ register(2419, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'3'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'3'}}"); ++ register(2420, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'4'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'4'}}"); ++ register(2421, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'5'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'5'}}"); ++ register(2422, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'6'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'6'}}"); ++ register(2423, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'7'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'7'}}"); ++ register(2424, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'8'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'8'}}"); ++ register(2425, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'9'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'9'}}"); ++ register(2426, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'10'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'10'}}"); ++ register(2427, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'11'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'11'}}"); ++ register(2428, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'12'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'12'}}"); ++ register(2429, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'13'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'13'}}"); ++ register(2430, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'14'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'14'}}"); ++ register(2431, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'15'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'15'}}"); ++ register(2432, "{Name:'minecraft:redstone_block'}", "{Name:'minecraft:redstone_block'}"); ++ register(2448, "{Name:'minecraft:nether_quartz_ore'}", "{Name:'minecraft:quartz_ore'}"); ++ register(2464, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}"); ++ register(2466, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}"); ++ register(2467, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}"); ++ register(2468, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}"); ++ register(2469, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}"); ++ register(2472, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}"); ++ register(2474, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}"); ++ register(2475, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}"); ++ register(2476, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}"); ++ register(2477, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}"); ++ register(2480, "{Name:'minecraft:quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'default'}}"); ++ register(2481, "{Name:'minecraft:chiseled_quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'chiseled'}}"); ++ register(2482, "{Name:'minecraft:quartz_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_y'}}"); ++ register(2483, "{Name:'minecraft:quartz_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_x'}}"); ++ register(2484, "{Name:'minecraft:quartz_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_z'}}"); ++ register(2496, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2497, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2498, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2499, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2500, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2501, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2502, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2503, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2512, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}"); ++ register(2513, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}"); ++ register(2514, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}"); ++ register(2515, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}"); ++ register(2516, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}"); ++ register(2517, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}"); ++ register(2520, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}"); ++ register(2521, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}"); ++ register(2522, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}"); ++ register(2523, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}"); ++ register(2524, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}"); ++ register(2525, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}"); ++ register(2528, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}"); ++ register(2529, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}"); ++ register(2530, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}"); ++ register(2531, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}"); ++ register(2532, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}"); ++ register(2533, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}"); ++ register(2536, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}"); ++ register(2537, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}"); ++ register(2538, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}"); ++ register(2539, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}"); ++ register(2540, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}"); ++ register(2541, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}"); ++ register(2544, "{Name:'minecraft:white_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'white'}}"); ++ register(2545, "{Name:'minecraft:orange_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'orange'}}"); ++ register(2546, "{Name:'minecraft:magenta_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'magenta'}}"); ++ register(2547, "{Name:'minecraft:light_blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'light_blue'}}"); ++ register(2548, "{Name:'minecraft:yellow_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'yellow'}}"); ++ register(2549, "{Name:'minecraft:lime_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'lime'}}"); ++ register(2550, "{Name:'minecraft:pink_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'pink'}}"); ++ register(2551, "{Name:'minecraft:gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'gray'}}"); ++ register(2552, "{Name:'minecraft:light_gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'silver'}}"); ++ register(2553, "{Name:'minecraft:cyan_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'cyan'}}"); ++ register(2554, "{Name:'minecraft:purple_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'purple'}}"); ++ register(2555, "{Name:'minecraft:blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'blue'}}"); ++ register(2556, "{Name:'minecraft:brown_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'brown'}}"); ++ register(2557, "{Name:'minecraft:green_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'green'}}"); ++ register(2558, "{Name:'minecraft:red_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'red'}}"); ++ register(2559, "{Name:'minecraft:black_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'black'}}"); ++ register(2560, "{Name:'minecraft:white_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2561, "{Name:'minecraft:orange_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2562, "{Name:'minecraft:magenta_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2563, "{Name:'minecraft:light_blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2564, "{Name:'minecraft:yellow_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2565, "{Name:'minecraft:lime_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2566, "{Name:'minecraft:pink_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2567, "{Name:'minecraft:gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2568, "{Name:'minecraft:light_gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2569, "{Name:'minecraft:cyan_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2570, "{Name:'minecraft:purple_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2571, "{Name:'minecraft:blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2572, "{Name:'minecraft:brown_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2573, "{Name:'minecraft:green_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2574, "{Name:'minecraft:red_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2575, "{Name:'minecraft:black_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'true'}}"); ++ register(2576, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'acacia'}}"); ++ register(2577, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'dark_oak'}}"); ++ register(2580, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'acacia'}}"); ++ register(2581, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'dark_oak'}}"); ++ register(2584, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'acacia'}}"); ++ register(2585, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'dark_oak'}}"); ++ register(2588, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'acacia'}}"); ++ register(2589, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'dark_oak'}}"); ++ register(2592, "{Name:'minecraft:acacia_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'acacia'}}"); ++ register(2593, "{Name:'minecraft:dark_oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'dark_oak'}}"); ++ register(2596, "{Name:'minecraft:acacia_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'acacia'}}"); ++ register(2597, "{Name:'minecraft:dark_oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'dark_oak'}}"); ++ register(2600, "{Name:'minecraft:acacia_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'acacia'}}"); ++ register(2601, "{Name:'minecraft:dark_oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'dark_oak'}}"); ++ register(2604, "{Name:'minecraft:acacia_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'acacia'}}"); ++ register(2605, "{Name:'minecraft:dark_oak_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'dark_oak'}}"); ++ register(2608, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2609, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2610, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2611, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2612, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2613, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2614, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2615, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2624, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2625, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2626, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2627, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2628, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2629, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2630, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2631, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2640, "{Name:'minecraft:slime_block'}", "{Name:'minecraft:slime'}"); ++ register(2656, "{Name:'minecraft:barrier'}", "{Name:'minecraft:barrier'}"); ++ register(2672, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); ++ register(2673, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); ++ register(2674, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); ++ register(2675, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); ++ register(2676, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); ++ register(2677, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); ++ register(2678, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); ++ register(2679, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); ++ register(2680, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); ++ register(2681, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); ++ register(2682, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); ++ register(2683, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); ++ register(2684, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); ++ register(2685, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); ++ register(2686, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); ++ register(2687, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); ++ register(2688, "{Name:'minecraft:prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine'}}"); ++ register(2689, "{Name:'minecraft:prismarine_bricks'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine_bricks'}}"); ++ register(2690, "{Name:'minecraft:dark_prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'dark_prismarine'}}"); ++ register(2704, "{Name:'minecraft:sea_lantern'}", "{Name:'minecraft:sea_lantern'}"); ++ register(2720, "{Name:'minecraft:hay_block',Properties:{axis:'y'}}", "{Name:'minecraft:hay_block',Properties:{axis:'y'}}"); ++ register(2724, "{Name:'minecraft:hay_block',Properties:{axis:'x'}}", "{Name:'minecraft:hay_block',Properties:{axis:'x'}}"); ++ register(2728, "{Name:'minecraft:hay_block',Properties:{axis:'z'}}", "{Name:'minecraft:hay_block',Properties:{axis:'z'}}"); ++ register(2736, "{Name:'minecraft:white_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'white'}}"); ++ register(2737, "{Name:'minecraft:orange_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'orange'}}"); ++ register(2738, "{Name:'minecraft:magenta_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'magenta'}}"); ++ register(2739, "{Name:'minecraft:light_blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'light_blue'}}"); ++ register(2740, "{Name:'minecraft:yellow_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'yellow'}}"); ++ register(2741, "{Name:'minecraft:lime_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'lime'}}"); ++ register(2742, "{Name:'minecraft:pink_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'pink'}}"); ++ register(2743, "{Name:'minecraft:gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'gray'}}"); ++ register(2744, "{Name:'minecraft:light_gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'silver'}}"); ++ register(2745, "{Name:'minecraft:cyan_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'cyan'}}"); ++ register(2746, "{Name:'minecraft:purple_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'purple'}}"); ++ register(2747, "{Name:'minecraft:blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'blue'}}"); ++ register(2748, "{Name:'minecraft:brown_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'brown'}}"); ++ register(2749, "{Name:'minecraft:green_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'green'}}"); ++ register(2750, "{Name:'minecraft:red_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'red'}}"); ++ register(2751, "{Name:'minecraft:black_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'black'}}"); ++ register(2752, "{Name:'minecraft:terracotta'}", "{Name:'minecraft:hardened_clay'}"); ++ register(2768, "{Name:'minecraft:coal_block'}", "{Name:'minecraft:coal_block'}"); ++ register(2784, "{Name:'minecraft:packed_ice'}", "{Name:'minecraft:packed_ice'}"); ++ register(2800, "{Name:'minecraft:sunflower',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'sunflower'}}"); ++ register(2801, "{Name:'minecraft:lilac',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'syringa'}}"); ++ register(2802, "{Name:'minecraft:tall_grass',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_grass'}}"); ++ register(2803, "{Name:'minecraft:large_fern',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_fern'}}"); ++ register(2804, "{Name:'minecraft:rose_bush',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_rose'}}"); ++ register(2805, "{Name:'minecraft:peony',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'paeonia'}}"); ++ register(2808, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'syringa'}}"); ++ register(2809, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'syringa'}}"); ++ register(2810, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'syringa'}}"); ++ register(2811, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'syringa'}}"); ++ register(2816, "{Name:'minecraft:white_banner',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'0'}}"); ++ register(2817, "{Name:'minecraft:white_banner',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'1'}}"); ++ register(2818, "{Name:'minecraft:white_banner',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'2'}}"); ++ register(2819, "{Name:'minecraft:white_banner',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'3'}}"); ++ register(2820, "{Name:'minecraft:white_banner',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'4'}}"); ++ register(2821, "{Name:'minecraft:white_banner',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'5'}}"); ++ register(2822, "{Name:'minecraft:white_banner',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'6'}}"); ++ register(2823, "{Name:'minecraft:white_banner',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'7'}}"); ++ register(2824, "{Name:'minecraft:white_banner',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'8'}}"); ++ register(2825, "{Name:'minecraft:white_banner',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'9'}}"); ++ register(2826, "{Name:'minecraft:white_banner',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'10'}}"); ++ register(2827, "{Name:'minecraft:white_banner',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'11'}}"); ++ register(2828, "{Name:'minecraft:white_banner',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'12'}}"); ++ register(2829, "{Name:'minecraft:white_banner',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'13'}}"); ++ register(2830, "{Name:'minecraft:white_banner',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'14'}}"); ++ register(2831, "{Name:'minecraft:white_banner',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'15'}}"); ++ register(2834, "{Name:'minecraft:white_wall_banner',Properties:{facing:'north'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'north'}}"); ++ register(2835, "{Name:'minecraft:white_wall_banner',Properties:{facing:'south'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'south'}}"); ++ register(2836, "{Name:'minecraft:white_wall_banner',Properties:{facing:'west'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'west'}}"); ++ register(2837, "{Name:'minecraft:white_wall_banner',Properties:{facing:'east'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'east'}}"); ++ register(2848, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'0'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'0'}}"); ++ register(2849, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'1'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'1'}}"); ++ register(2850, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'2'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'2'}}"); ++ register(2851, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'3'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'3'}}"); ++ register(2852, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'4'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'4'}}"); ++ register(2853, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'5'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'5'}}"); ++ register(2854, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'6'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'6'}}"); ++ register(2855, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'7'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'7'}}"); ++ register(2856, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'8'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'8'}}"); ++ register(2857, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'9'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'9'}}"); ++ register(2858, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'10'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'10'}}"); ++ register(2859, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'11'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'11'}}"); ++ register(2860, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'12'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'12'}}"); ++ register(2861, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'13'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'13'}}"); ++ register(2862, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'14'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'14'}}"); ++ register(2863, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'15'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'15'}}"); ++ register(2864, "{Name:'minecraft:red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'red_sandstone'}}"); ++ register(2865, "{Name:'minecraft:chiseled_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'chiseled_red_sandstone'}}"); ++ register(2866, "{Name:'minecraft:cut_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'smooth_red_sandstone'}}"); ++ register(2880, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(2881, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(2882, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(2883, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(2884, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(2885, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(2886, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(2887, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(2896, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'false',variant:'red_sandstone'}}"); ++ register(2904, "{Name:'minecraft:smooth_red_sandstone'}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'true',variant:'red_sandstone'}}"); ++ register(2912, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'bottom',variant:'red_sandstone'}}"); ++ register(2920, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'top',variant:'red_sandstone'}}"); ++ register(2928, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2929, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2930, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2931, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2932, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2933, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2934, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2935, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2936, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2937, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2938, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2939, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2940, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2941, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2942, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2943, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2944, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2945, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2946, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2947, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2948, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2949, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2950, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2951, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2952, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2953, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2954, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2955, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2956, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2957, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2958, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2959, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2960, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2961, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2962, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2963, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2964, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2965, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2966, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2967, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2968, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2969, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2970, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2971, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2972, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2973, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2974, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2975, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2976, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2977, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2978, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2979, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2980, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2981, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2982, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2983, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2984, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2985, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2986, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2987, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(2988, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2989, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2990, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2991, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(2992, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2993, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2994, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2995, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); ++ register(2996, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2997, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2998, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); ++ register(2999, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); ++ register(3000, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3001, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3002, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3003, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); ++ register(3004, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3005, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3006, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3007, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); ++ register(3008, "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3024, "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3040, "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3056, "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3072, "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); ++ register(3088, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3089, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3090, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3091, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3092, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3093, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3094, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3095, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3096, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3097, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3098, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3099, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3104, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3105, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3106, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3107, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3108, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3109, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3110, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3111, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3112, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3113, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3114, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3115, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3120, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3121, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3122, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3123, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3124, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3125, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3126, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3127, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3128, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3129, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3130, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3131, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3136, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3137, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3138, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3139, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3140, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3141, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3142, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3143, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3144, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3145, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3146, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3147, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3152, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3153, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3154, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3155, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); ++ register(3156, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3157, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3158, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3159, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); ++ register(3160, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); ++ register(3161, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); ++ register(3162, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); ++ register(3163, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); ++ register(3168, "{Name:'minecraft:end_rod',Properties:{facing:'down'}}", "{Name:'minecraft:end_rod',Properties:{facing:'down'}}"); ++ register(3169, "{Name:'minecraft:end_rod',Properties:{facing:'up'}}", "{Name:'minecraft:end_rod',Properties:{facing:'up'}}"); ++ register(3170, "{Name:'minecraft:end_rod',Properties:{facing:'north'}}", "{Name:'minecraft:end_rod',Properties:{facing:'north'}}"); ++ register(3171, "{Name:'minecraft:end_rod',Properties:{facing:'south'}}", "{Name:'minecraft:end_rod',Properties:{facing:'south'}}"); ++ register(3172, "{Name:'minecraft:end_rod',Properties:{facing:'west'}}", "{Name:'minecraft:end_rod',Properties:{facing:'west'}}"); ++ register(3173, "{Name:'minecraft:end_rod',Properties:{facing:'east'}}", "{Name:'minecraft:end_rod',Properties:{facing:'east'}}"); ++ register(3184, "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); ++ register(3200, "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}"); ++ register(3201, "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}"); ++ register(3202, "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}"); ++ register(3203, "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}"); ++ register(3204, "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}"); ++ register(3205, "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}"); ++ register(3216, "{Name:'minecraft:purpur_block'}", "{Name:'minecraft:purpur_block'}"); ++ register(3232, "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}"); ++ register(3236, "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}"); ++ register(3240, "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}"); ++ register(3248, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); ++ register(3249, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); ++ register(3250, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); ++ register(3251, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); ++ register(3252, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); ++ register(3253, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); ++ register(3254, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); ++ register(3255, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); ++ register(3264, "{Name:'minecraft:purpur_slab',Properties:{type:'double'}}", "{Name:'minecraft:purpur_double_slab',Properties:{variant:'default'}}"); ++ register(3280, "{Name:'minecraft:purpur_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'bottom',variant:'default'}}"); ++ register(3288, "{Name:'minecraft:purpur_slab',Properties:{type:'top'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'top',variant:'default'}}"); ++ register(3296, "{Name:'minecraft:end_stone_bricks'}", "{Name:'minecraft:end_bricks'}"); ++ register(3312, "{Name:'minecraft:beetroots',Properties:{age:'0'}}", "{Name:'minecraft:beetroots',Properties:{age:'0'}}"); ++ register(3313, "{Name:'minecraft:beetroots',Properties:{age:'1'}}", "{Name:'minecraft:beetroots',Properties:{age:'1'}}"); ++ register(3314, "{Name:'minecraft:beetroots',Properties:{age:'2'}}", "{Name:'minecraft:beetroots',Properties:{age:'2'}}"); ++ register(3315, "{Name:'minecraft:beetroots',Properties:{age:'3'}}", "{Name:'minecraft:beetroots',Properties:{age:'3'}}"); ++ register(3328, "{Name:'minecraft:grass_path'}", "{Name:'minecraft:grass_path'}"); ++ register(3344, "{Name:'minecraft:end_gateway'}", "{Name:'minecraft:end_gateway'}"); ++ register(3360, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(3361, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(3362, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(3363, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(3364, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(3365, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(3368, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(3369, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(3370, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(3371, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(3372, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(3373, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(3376, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}"); ++ register(3377, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}"); ++ register(3378, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}"); ++ register(3379, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}"); ++ register(3380, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}"); ++ register(3381, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}"); ++ register(3384, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}"); ++ register(3385, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}"); ++ register(3386, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}"); ++ register(3387, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}"); ++ register(3388, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}"); ++ register(3389, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}"); ++ register(3392, "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}"); ++ register(3393, "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}"); ++ register(3394, "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}"); ++ register(3395, "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}"); ++ register(3408, "{Name:'minecraft:magma_block'}", "{Name:'minecraft:magma'}"); ++ register(3424, "{Name:'minecraft:nether_wart_block'}", "{Name:'minecraft:nether_wart_block'}"); ++ register(3440, "{Name:'minecraft:red_nether_bricks'}", "{Name:'minecraft:red_nether_brick'}"); ++ register(3456, "{Name:'minecraft:bone_block',Properties:{axis:'y'}}", "{Name:'minecraft:bone_block',Properties:{axis:'y'}}"); ++ register(3460, "{Name:'minecraft:bone_block',Properties:{axis:'x'}}", "{Name:'minecraft:bone_block',Properties:{axis:'x'}}"); ++ register(3464, "{Name:'minecraft:bone_block',Properties:{axis:'z'}}", "{Name:'minecraft:bone_block',Properties:{axis:'z'}}"); ++ register(3472, "{Name:'minecraft:structure_void'}", "{Name:'minecraft:structure_void'}"); ++ register(3488, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}"); ++ register(3489, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}"); ++ register(3490, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}"); ++ register(3491, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}"); ++ register(3492, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}"); ++ register(3493, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}"); ++ register(3496, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}"); ++ register(3497, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}"); ++ register(3498, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}"); ++ register(3499, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}"); ++ register(3500, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}"); ++ register(3501, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}"); ++ register(3504, "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}"); ++ register(3505, "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}"); ++ register(3506, "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}"); ++ register(3507, "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}"); ++ register(3508, "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}"); ++ register(3509, "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}"); ++ register(3520, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}"); ++ register(3521, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}"); ++ register(3522, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}"); ++ register(3523, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}"); ++ register(3524, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}"); ++ register(3525, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}"); ++ register(3536, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}"); ++ register(3537, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}"); ++ register(3538, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}"); ++ register(3539, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}"); ++ register(3540, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}"); ++ register(3541, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}"); ++ register(3552, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}"); ++ register(3553, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}"); ++ register(3554, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}"); ++ register(3555, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}"); ++ register(3556, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}"); ++ register(3557, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}"); ++ register(3568, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}"); ++ register(3569, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}"); ++ register(3570, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}"); ++ register(3571, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}"); ++ register(3572, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}"); ++ register(3573, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}"); ++ register(3584, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}"); ++ register(3585, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}"); ++ register(3586, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}"); ++ register(3587, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}"); ++ register(3588, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}"); ++ register(3589, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}"); ++ register(3600, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}"); ++ register(3601, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}"); ++ register(3602, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}"); ++ register(3603, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}"); ++ register(3604, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}"); ++ register(3605, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}"); ++ register(3616, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}"); ++ register(3617, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}"); ++ register(3618, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}"); ++ register(3619, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}"); ++ register(3620, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}"); ++ register(3621, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}"); ++ register(3632, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'down'}}"); ++ register(3633, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'up'}}"); ++ register(3634, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'north'}}"); ++ register(3635, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'south'}}"); ++ register(3636, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'west'}}"); ++ register(3637, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'east'}}"); ++ register(3648, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}"); ++ register(3649, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}"); ++ register(3650, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}"); ++ register(3651, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}"); ++ register(3652, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}"); ++ register(3653, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}"); ++ register(3664, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}"); ++ register(3665, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}"); ++ register(3666, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}"); ++ register(3667, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}"); ++ register(3668, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}"); ++ register(3669, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}"); ++ register(3680, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}"); ++ register(3681, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}"); ++ register(3682, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}"); ++ register(3683, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}"); ++ register(3684, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}"); ++ register(3685, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}"); ++ register(3696, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}"); ++ register(3697, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}"); ++ register(3698, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}"); ++ register(3699, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}"); ++ register(3700, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}"); ++ register(3701, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}"); ++ register(3712, "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}"); ++ register(3713, "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}"); ++ register(3714, "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}"); ++ register(3715, "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}"); ++ register(3716, "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}"); ++ register(3717, "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}"); ++ register(3728, "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}"); ++ register(3729, "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}"); ++ register(3730, "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}"); ++ register(3731, "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}"); ++ register(3732, "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}"); ++ register(3733, "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}"); ++ register(3744, "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}"); ++ register(3745, "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}"); ++ register(3746, "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}"); ++ register(3747, "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}"); ++ register(3748, "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}"); ++ register(3749, "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}"); ++ register(3760, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3761, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3762, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3763, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3776, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3777, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3778, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3779, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3792, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3793, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3794, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3795, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3808, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3809, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3810, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3811, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3824, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3825, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3826, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3827, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3840, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3841, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3842, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3843, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3856, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3857, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3858, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3859, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3872, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3873, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3874, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3875, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3888, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3889, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3890, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3891, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3904, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3905, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3906, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3907, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3920, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3921, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3922, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3923, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3936, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3937, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3938, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3939, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3952, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3953, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3954, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3955, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3968, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3969, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3970, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3971, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(3984, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(3985, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(3986, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(3987, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(4000, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}"); ++ register(4001, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}"); ++ register(4002, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}"); ++ register(4003, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}"); ++ register(4016, "{Name:'minecraft:white_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'white'}}"); ++ register(4017, "{Name:'minecraft:orange_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'orange'}}"); ++ register(4018, "{Name:'minecraft:magenta_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'magenta'}}"); ++ register(4019, "{Name:'minecraft:light_blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'light_blue'}}"); ++ register(4020, "{Name:'minecraft:yellow_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'yellow'}}"); ++ register(4021, "{Name:'minecraft:lime_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'lime'}}"); ++ register(4022, "{Name:'minecraft:pink_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'pink'}}"); ++ register(4023, "{Name:'minecraft:gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'gray'}}"); ++ register(4024, "{Name:'minecraft:light_gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'silver'}}"); ++ register(4025, "{Name:'minecraft:cyan_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'cyan'}}"); ++ register(4026, "{Name:'minecraft:purple_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'purple'}}"); ++ register(4027, "{Name:'minecraft:blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'blue'}}"); ++ register(4028, "{Name:'minecraft:brown_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'brown'}}"); ++ register(4029, "{Name:'minecraft:green_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'green'}}"); ++ register(4030, "{Name:'minecraft:red_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'red'}}"); ++ register(4031, "{Name:'minecraft:black_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'black'}}"); ++ register(4032, "{Name:'minecraft:white_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'white'}}"); ++ register(4033, "{Name:'minecraft:orange_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'orange'}}"); ++ register(4034, "{Name:'minecraft:magenta_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'magenta'}}"); ++ register(4035, "{Name:'minecraft:light_blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'light_blue'}}"); ++ register(4036, "{Name:'minecraft:yellow_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'yellow'}}"); ++ register(4037, "{Name:'minecraft:lime_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'lime'}}"); ++ register(4038, "{Name:'minecraft:pink_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'pink'}}"); ++ register(4039, "{Name:'minecraft:gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'gray'}}"); ++ register(4040, "{Name:'minecraft:light_gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'silver'}}"); ++ register(4041, "{Name:'minecraft:cyan_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'cyan'}}"); ++ register(4042, "{Name:'minecraft:purple_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'purple'}}"); ++ register(4043, "{Name:'minecraft:blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'blue'}}"); ++ register(4044, "{Name:'minecraft:brown_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'brown'}}"); ++ register(4045, "{Name:'minecraft:green_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'green'}}"); ++ register(4046, "{Name:'minecraft:red_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'red'}}"); ++ register(4047, "{Name:'minecraft:black_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'black'}}"); ++ register(4080, "{Name:'minecraft:structure_block',Properties:{mode:'save'}}", "{Name:'minecraft:structure_block',Properties:{mode:'save'}}"); ++ register(4081, "{Name:'minecraft:structure_block',Properties:{mode:'load'}}", "{Name:'minecraft:structure_block',Properties:{mode:'load'}}"); ++ register(4082, "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}", "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}"); ++ register(4083, "{Name:'minecraft:structure_block',Properties:{mode:'data'}}", "{Name:'minecraft:structure_block',Properties:{mode:'data'}}"); ++ finalizeMaps(); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7ce9cf645ccb2dc796b87858915dba1c3efc3d5b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java +@@ -0,0 +1,566 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class HelperItemNameV102 { ++ ++ // This class is responsible for mapping the id -> string update in itemstacks and potions ++ ++ private static final Int2ObjectOpenHashMap ITEM_NAMES = new Int2ObjectOpenHashMap() { ++ @Override ++ public String put(final int k, final String o) { ++ final String ret = super.put(k, o); ++ ++ if (ret != null) { ++ throw new IllegalStateException("Mapping already exists for " + k + ": prev: " + ret + ", new: " + o); ++ } ++ ++ return ret; ++ } ++ }; ++ ++ static { ++ ITEM_NAMES.put(0, "minecraft:air"); ++ ITEM_NAMES.put(1, "minecraft:stone"); ++ ITEM_NAMES.put(2, "minecraft:grass"); ++ ITEM_NAMES.put(3, "minecraft:dirt"); ++ ITEM_NAMES.put(4, "minecraft:cobblestone"); ++ ITEM_NAMES.put(5, "minecraft:planks"); ++ ITEM_NAMES.put(6, "minecraft:sapling"); ++ ITEM_NAMES.put(7, "minecraft:bedrock"); ++ ITEM_NAMES.put(8, "minecraft:flowing_water"); ++ ITEM_NAMES.put(9, "minecraft:water"); ++ ITEM_NAMES.put(10, "minecraft:flowing_lava"); ++ ITEM_NAMES.put(11, "minecraft:lava"); ++ ITEM_NAMES.put(12, "minecraft:sand"); ++ ITEM_NAMES.put(13, "minecraft:gravel"); ++ ITEM_NAMES.put(14, "minecraft:gold_ore"); ++ ITEM_NAMES.put(15, "minecraft:iron_ore"); ++ ITEM_NAMES.put(16, "minecraft:coal_ore"); ++ ITEM_NAMES.put(17, "minecraft:log"); ++ ITEM_NAMES.put(18, "minecraft:leaves"); ++ ITEM_NAMES.put(19, "minecraft:sponge"); ++ ITEM_NAMES.put(20, "minecraft:glass"); ++ ITEM_NAMES.put(21, "minecraft:lapis_ore"); ++ ITEM_NAMES.put(22, "minecraft:lapis_block"); ++ ITEM_NAMES.put(23, "minecraft:dispenser"); ++ ITEM_NAMES.put(24, "minecraft:sandstone"); ++ ITEM_NAMES.put(25, "minecraft:noteblock"); ++ ITEM_NAMES.put(27, "minecraft:golden_rail"); ++ ITEM_NAMES.put(28, "minecraft:detector_rail"); ++ ITEM_NAMES.put(29, "minecraft:sticky_piston"); ++ ITEM_NAMES.put(30, "minecraft:web"); ++ ITEM_NAMES.put(31, "minecraft:tallgrass"); ++ ITEM_NAMES.put(32, "minecraft:deadbush"); ++ ITEM_NAMES.put(33, "minecraft:piston"); ++ ITEM_NAMES.put(35, "minecraft:wool"); ++ ITEM_NAMES.put(37, "minecraft:yellow_flower"); ++ ITEM_NAMES.put(38, "minecraft:red_flower"); ++ ITEM_NAMES.put(39, "minecraft:brown_mushroom"); ++ ITEM_NAMES.put(40, "minecraft:red_mushroom"); ++ ITEM_NAMES.put(41, "minecraft:gold_block"); ++ ITEM_NAMES.put(42, "minecraft:iron_block"); ++ ITEM_NAMES.put(43, "minecraft:double_stone_slab"); ++ ITEM_NAMES.put(44, "minecraft:stone_slab"); ++ ITEM_NAMES.put(45, "minecraft:brick_block"); ++ ITEM_NAMES.put(46, "minecraft:tnt"); ++ ITEM_NAMES.put(47, "minecraft:bookshelf"); ++ ITEM_NAMES.put(48, "minecraft:mossy_cobblestone"); ++ ITEM_NAMES.put(49, "minecraft:obsidian"); ++ ITEM_NAMES.put(50, "minecraft:torch"); ++ ITEM_NAMES.put(51, "minecraft:fire"); ++ ITEM_NAMES.put(52, "minecraft:mob_spawner"); ++ ITEM_NAMES.put(53, "minecraft:oak_stairs"); ++ ITEM_NAMES.put(54, "minecraft:chest"); ++ ITEM_NAMES.put(56, "minecraft:diamond_ore"); ++ ITEM_NAMES.put(57, "minecraft:diamond_block"); ++ ITEM_NAMES.put(58, "minecraft:crafting_table"); ++ ITEM_NAMES.put(60, "minecraft:farmland"); ++ ITEM_NAMES.put(61, "minecraft:furnace"); ++ ITEM_NAMES.put(62, "minecraft:lit_furnace"); ++ ITEM_NAMES.put(65, "minecraft:ladder"); ++ ITEM_NAMES.put(66, "minecraft:rail"); ++ ITEM_NAMES.put(67, "minecraft:stone_stairs"); ++ ITEM_NAMES.put(69, "minecraft:lever"); ++ ITEM_NAMES.put(70, "minecraft:stone_pressure_plate"); ++ ITEM_NAMES.put(72, "minecraft:wooden_pressure_plate"); ++ ITEM_NAMES.put(73, "minecraft:redstone_ore"); ++ ITEM_NAMES.put(76, "minecraft:redstone_torch"); ++ ITEM_NAMES.put(77, "minecraft:stone_button"); ++ ITEM_NAMES.put(78, "minecraft:snow_layer"); ++ ITEM_NAMES.put(79, "minecraft:ice"); ++ ITEM_NAMES.put(80, "minecraft:snow"); ++ ITEM_NAMES.put(81, "minecraft:cactus"); ++ ITEM_NAMES.put(82, "minecraft:clay"); ++ ITEM_NAMES.put(84, "minecraft:jukebox"); ++ ITEM_NAMES.put(85, "minecraft:fence"); ++ ITEM_NAMES.put(86, "minecraft:pumpkin"); ++ ITEM_NAMES.put(87, "minecraft:netherrack"); ++ ITEM_NAMES.put(88, "minecraft:soul_sand"); ++ ITEM_NAMES.put(89, "minecraft:glowstone"); ++ ITEM_NAMES.put(90, "minecraft:portal"); ++ ITEM_NAMES.put(91, "minecraft:lit_pumpkin"); ++ ITEM_NAMES.put(95, "minecraft:stained_glass"); ++ ITEM_NAMES.put(96, "minecraft:trapdoor"); ++ ITEM_NAMES.put(97, "minecraft:monster_egg"); ++ ITEM_NAMES.put(98, "minecraft:stonebrick"); ++ ITEM_NAMES.put(99, "minecraft:brown_mushroom_block"); ++ ITEM_NAMES.put(100, "minecraft:red_mushroom_block"); ++ ITEM_NAMES.put(101, "minecraft:iron_bars"); ++ ITEM_NAMES.put(102, "minecraft:glass_pane"); ++ ITEM_NAMES.put(103, "minecraft:melon_block"); ++ ITEM_NAMES.put(106, "minecraft:vine"); ++ ITEM_NAMES.put(107, "minecraft:fence_gate"); ++ ITEM_NAMES.put(108, "minecraft:brick_stairs"); ++ ITEM_NAMES.put(109, "minecraft:stone_brick_stairs"); ++ ITEM_NAMES.put(110, "minecraft:mycelium"); ++ ITEM_NAMES.put(111, "minecraft:waterlily"); ++ ITEM_NAMES.put(112, "minecraft:nether_brick"); ++ ITEM_NAMES.put(113, "minecraft:nether_brick_fence"); ++ ITEM_NAMES.put(114, "minecraft:nether_brick_stairs"); ++ ITEM_NAMES.put(116, "minecraft:enchanting_table"); ++ ITEM_NAMES.put(119, "minecraft:end_portal"); ++ ITEM_NAMES.put(120, "minecraft:end_portal_frame"); ++ ITEM_NAMES.put(121, "minecraft:end_stone"); ++ ITEM_NAMES.put(122, "minecraft:dragon_egg"); ++ ITEM_NAMES.put(123, "minecraft:redstone_lamp"); ++ ITEM_NAMES.put(125, "minecraft:double_wooden_slab"); ++ ITEM_NAMES.put(126, "minecraft:wooden_slab"); ++ ITEM_NAMES.put(127, "minecraft:cocoa"); ++ ITEM_NAMES.put(128, "minecraft:sandstone_stairs"); ++ ITEM_NAMES.put(129, "minecraft:emerald_ore"); ++ ITEM_NAMES.put(130, "minecraft:ender_chest"); ++ ITEM_NAMES.put(131, "minecraft:tripwire_hook"); ++ ITEM_NAMES.put(133, "minecraft:emerald_block"); ++ ITEM_NAMES.put(134, "minecraft:spruce_stairs"); ++ ITEM_NAMES.put(135, "minecraft:birch_stairs"); ++ ITEM_NAMES.put(136, "minecraft:jungle_stairs"); ++ ITEM_NAMES.put(137, "minecraft:command_block"); ++ ITEM_NAMES.put(138, "minecraft:beacon"); ++ ITEM_NAMES.put(139, "minecraft:cobblestone_wall"); ++ ITEM_NAMES.put(141, "minecraft:carrots"); ++ ITEM_NAMES.put(142, "minecraft:potatoes"); ++ ITEM_NAMES.put(143, "minecraft:wooden_button"); ++ ITEM_NAMES.put(145, "minecraft:anvil"); ++ ITEM_NAMES.put(146, "minecraft:trapped_chest"); ++ ITEM_NAMES.put(147, "minecraft:light_weighted_pressure_plate"); ++ ITEM_NAMES.put(148, "minecraft:heavy_weighted_pressure_plate"); ++ ITEM_NAMES.put(151, "minecraft:daylight_detector"); ++ ITEM_NAMES.put(152, "minecraft:redstone_block"); ++ ITEM_NAMES.put(153, "minecraft:quartz_ore"); ++ ITEM_NAMES.put(154, "minecraft:hopper"); ++ ITEM_NAMES.put(155, "minecraft:quartz_block"); ++ ITEM_NAMES.put(156, "minecraft:quartz_stairs"); ++ ITEM_NAMES.put(157, "minecraft:activator_rail"); ++ ITEM_NAMES.put(158, "minecraft:dropper"); ++ ITEM_NAMES.put(159, "minecraft:stained_hardened_clay"); ++ ITEM_NAMES.put(160, "minecraft:stained_glass_pane"); ++ ITEM_NAMES.put(161, "minecraft:leaves2"); ++ ITEM_NAMES.put(162, "minecraft:log2"); ++ ITEM_NAMES.put(163, "minecraft:acacia_stairs"); ++ ITEM_NAMES.put(164, "minecraft:dark_oak_stairs"); ++ ITEM_NAMES.put(170, "minecraft:hay_block"); ++ ITEM_NAMES.put(171, "minecraft:carpet"); ++ ITEM_NAMES.put(172, "minecraft:hardened_clay"); ++ ITEM_NAMES.put(173, "minecraft:coal_block"); ++ ITEM_NAMES.put(174, "minecraft:packed_ice"); ++ ITEM_NAMES.put(175, "minecraft:double_plant"); ++ ITEM_NAMES.put(256, "minecraft:iron_shovel"); ++ ITEM_NAMES.put(257, "minecraft:iron_pickaxe"); ++ ITEM_NAMES.put(258, "minecraft:iron_axe"); ++ ITEM_NAMES.put(259, "minecraft:flint_and_steel"); ++ ITEM_NAMES.put(260, "minecraft:apple"); ++ ITEM_NAMES.put(261, "minecraft:bow"); ++ ITEM_NAMES.put(262, "minecraft:arrow"); ++ ITEM_NAMES.put(263, "minecraft:coal"); ++ ITEM_NAMES.put(264, "minecraft:diamond"); ++ ITEM_NAMES.put(265, "minecraft:iron_ingot"); ++ ITEM_NAMES.put(266, "minecraft:gold_ingot"); ++ ITEM_NAMES.put(267, "minecraft:iron_sword"); ++ ITEM_NAMES.put(268, "minecraft:wooden_sword"); ++ ITEM_NAMES.put(269, "minecraft:wooden_shovel"); ++ ITEM_NAMES.put(270, "minecraft:wooden_pickaxe"); ++ ITEM_NAMES.put(271, "minecraft:wooden_axe"); ++ ITEM_NAMES.put(272, "minecraft:stone_sword"); ++ ITEM_NAMES.put(273, "minecraft:stone_shovel"); ++ ITEM_NAMES.put(274, "minecraft:stone_pickaxe"); ++ ITEM_NAMES.put(275, "minecraft:stone_axe"); ++ ITEM_NAMES.put(276, "minecraft:diamond_sword"); ++ ITEM_NAMES.put(277, "minecraft:diamond_shovel"); ++ ITEM_NAMES.put(278, "minecraft:diamond_pickaxe"); ++ ITEM_NAMES.put(279, "minecraft:diamond_axe"); ++ ITEM_NAMES.put(280, "minecraft:stick"); ++ ITEM_NAMES.put(281, "minecraft:bowl"); ++ ITEM_NAMES.put(282, "minecraft:mushroom_stew"); ++ ITEM_NAMES.put(283, "minecraft:golden_sword"); ++ ITEM_NAMES.put(284, "minecraft:golden_shovel"); ++ ITEM_NAMES.put(285, "minecraft:golden_pickaxe"); ++ ITEM_NAMES.put(286, "minecraft:golden_axe"); ++ ITEM_NAMES.put(287, "minecraft:string"); ++ ITEM_NAMES.put(288, "minecraft:feather"); ++ ITEM_NAMES.put(289, "minecraft:gunpowder"); ++ ITEM_NAMES.put(290, "minecraft:wooden_hoe"); ++ ITEM_NAMES.put(291, "minecraft:stone_hoe"); ++ ITEM_NAMES.put(292, "minecraft:iron_hoe"); ++ ITEM_NAMES.put(293, "minecraft:diamond_hoe"); ++ ITEM_NAMES.put(294, "minecraft:golden_hoe"); ++ ITEM_NAMES.put(295, "minecraft:wheat_seeds"); ++ ITEM_NAMES.put(296, "minecraft:wheat"); ++ ITEM_NAMES.put(297, "minecraft:bread"); ++ ITEM_NAMES.put(298, "minecraft:leather_helmet"); ++ ITEM_NAMES.put(299, "minecraft:leather_chestplate"); ++ ITEM_NAMES.put(300, "minecraft:leather_leggings"); ++ ITEM_NAMES.put(301, "minecraft:leather_boots"); ++ ITEM_NAMES.put(302, "minecraft:chainmail_helmet"); ++ ITEM_NAMES.put(303, "minecraft:chainmail_chestplate"); ++ ITEM_NAMES.put(304, "minecraft:chainmail_leggings"); ++ ITEM_NAMES.put(305, "minecraft:chainmail_boots"); ++ ITEM_NAMES.put(306, "minecraft:iron_helmet"); ++ ITEM_NAMES.put(307, "minecraft:iron_chestplate"); ++ ITEM_NAMES.put(308, "minecraft:iron_leggings"); ++ ITEM_NAMES.put(309, "minecraft:iron_boots"); ++ ITEM_NAMES.put(310, "minecraft:diamond_helmet"); ++ ITEM_NAMES.put(311, "minecraft:diamond_chestplate"); ++ ITEM_NAMES.put(312, "minecraft:diamond_leggings"); ++ ITEM_NAMES.put(313, "minecraft:diamond_boots"); ++ ITEM_NAMES.put(314, "minecraft:golden_helmet"); ++ ITEM_NAMES.put(315, "minecraft:golden_chestplate"); ++ ITEM_NAMES.put(316, "minecraft:golden_leggings"); ++ ITEM_NAMES.put(317, "minecraft:golden_boots"); ++ ITEM_NAMES.put(318, "minecraft:flint"); ++ ITEM_NAMES.put(319, "minecraft:porkchop"); ++ ITEM_NAMES.put(320, "minecraft:cooked_porkchop"); ++ ITEM_NAMES.put(321, "minecraft:painting"); ++ ITEM_NAMES.put(322, "minecraft:golden_apple"); ++ ITEM_NAMES.put(323, "minecraft:sign"); ++ ITEM_NAMES.put(324, "minecraft:wooden_door"); ++ ITEM_NAMES.put(325, "minecraft:bucket"); ++ ITEM_NAMES.put(326, "minecraft:water_bucket"); ++ ITEM_NAMES.put(327, "minecraft:lava_bucket"); ++ ITEM_NAMES.put(328, "minecraft:minecart"); ++ ITEM_NAMES.put(329, "minecraft:saddle"); ++ ITEM_NAMES.put(330, "minecraft:iron_door"); ++ ITEM_NAMES.put(331, "minecraft:redstone"); ++ ITEM_NAMES.put(332, "minecraft:snowball"); ++ ITEM_NAMES.put(333, "minecraft:boat"); ++ ITEM_NAMES.put(334, "minecraft:leather"); ++ ITEM_NAMES.put(335, "minecraft:milk_bucket"); ++ ITEM_NAMES.put(336, "minecraft:brick"); ++ ITEM_NAMES.put(337, "minecraft:clay_ball"); ++ ITEM_NAMES.put(338, "minecraft:reeds"); ++ ITEM_NAMES.put(339, "minecraft:paper"); ++ ITEM_NAMES.put(340, "minecraft:book"); ++ ITEM_NAMES.put(341, "minecraft:slime_ball"); ++ ITEM_NAMES.put(342, "minecraft:chest_minecart"); ++ ITEM_NAMES.put(343, "minecraft:furnace_minecart"); ++ ITEM_NAMES.put(344, "minecraft:egg"); ++ ITEM_NAMES.put(345, "minecraft:compass"); ++ ITEM_NAMES.put(346, "minecraft:fishing_rod"); ++ ITEM_NAMES.put(347, "minecraft:clock"); ++ ITEM_NAMES.put(348, "minecraft:glowstone_dust"); ++ ITEM_NAMES.put(349, "minecraft:fish"); ++ ITEM_NAMES.put(350, "minecraft:cooked_fish"); // Fix typo, the game never recognized cooked_fished ++ ITEM_NAMES.put(351, "minecraft:dye"); ++ ITEM_NAMES.put(352, "minecraft:bone"); ++ ITEM_NAMES.put(353, "minecraft:sugar"); ++ ITEM_NAMES.put(354, "minecraft:cake"); ++ ITEM_NAMES.put(355, "minecraft:bed"); ++ ITEM_NAMES.put(356, "minecraft:repeater"); ++ ITEM_NAMES.put(357, "minecraft:cookie"); ++ ITEM_NAMES.put(358, "minecraft:filled_map"); ++ ITEM_NAMES.put(359, "minecraft:shears"); ++ ITEM_NAMES.put(360, "minecraft:melon"); ++ ITEM_NAMES.put(361, "minecraft:pumpkin_seeds"); ++ ITEM_NAMES.put(362, "minecraft:melon_seeds"); ++ ITEM_NAMES.put(363, "minecraft:beef"); ++ ITEM_NAMES.put(364, "minecraft:cooked_beef"); ++ ITEM_NAMES.put(365, "minecraft:chicken"); ++ ITEM_NAMES.put(366, "minecraft:cooked_chicken"); ++ ITEM_NAMES.put(367, "minecraft:rotten_flesh"); ++ ITEM_NAMES.put(368, "minecraft:ender_pearl"); ++ ITEM_NAMES.put(369, "minecraft:blaze_rod"); ++ ITEM_NAMES.put(370, "minecraft:ghast_tear"); ++ ITEM_NAMES.put(371, "minecraft:gold_nugget"); ++ ITEM_NAMES.put(372, "minecraft:nether_wart"); ++ ITEM_NAMES.put(373, "minecraft:potion"); ++ ITEM_NAMES.put(374, "minecraft:glass_bottle"); ++ ITEM_NAMES.put(375, "minecraft:spider_eye"); ++ ITEM_NAMES.put(376, "minecraft:fermented_spider_eye"); ++ ITEM_NAMES.put(377, "minecraft:blaze_powder"); ++ ITEM_NAMES.put(378, "minecraft:magma_cream"); ++ ITEM_NAMES.put(379, "minecraft:brewing_stand"); ++ ITEM_NAMES.put(380, "minecraft:cauldron"); ++ ITEM_NAMES.put(381, "minecraft:ender_eye"); ++ ITEM_NAMES.put(382, "minecraft:speckled_melon"); ++ ITEM_NAMES.put(383, "minecraft:spawn_egg"); ++ ITEM_NAMES.put(384, "minecraft:experience_bottle"); ++ ITEM_NAMES.put(385, "minecraft:fire_charge"); ++ ITEM_NAMES.put(386, "minecraft:writable_book"); ++ ITEM_NAMES.put(387, "minecraft:written_book"); ++ ITEM_NAMES.put(388, "minecraft:emerald"); ++ ITEM_NAMES.put(389, "minecraft:item_frame"); ++ ITEM_NAMES.put(390, "minecraft:flower_pot"); ++ ITEM_NAMES.put(391, "minecraft:carrot"); ++ ITEM_NAMES.put(392, "minecraft:potato"); ++ ITEM_NAMES.put(393, "minecraft:baked_potato"); ++ ITEM_NAMES.put(394, "minecraft:poisonous_potato"); ++ ITEM_NAMES.put(395, "minecraft:map"); ++ ITEM_NAMES.put(396, "minecraft:golden_carrot"); ++ ITEM_NAMES.put(397, "minecraft:skull"); ++ ITEM_NAMES.put(398, "minecraft:carrot_on_a_stick"); ++ ITEM_NAMES.put(399, "minecraft:nether_star"); ++ ITEM_NAMES.put(400, "minecraft:pumpkin_pie"); ++ ITEM_NAMES.put(401, "minecraft:fireworks"); ++ ITEM_NAMES.put(402, "minecraft:firework_charge"); ++ ITEM_NAMES.put(403, "minecraft:enchanted_book"); ++ ITEM_NAMES.put(404, "minecraft:comparator"); ++ ITEM_NAMES.put(405, "minecraft:netherbrick"); ++ ITEM_NAMES.put(406, "minecraft:quartz"); ++ ITEM_NAMES.put(407, "minecraft:tnt_minecart"); ++ ITEM_NAMES.put(408, "minecraft:hopper_minecart"); ++ ITEM_NAMES.put(417, "minecraft:iron_horse_armor"); ++ ITEM_NAMES.put(418, "minecraft:golden_horse_armor"); ++ ITEM_NAMES.put(419, "minecraft:diamond_horse_armor"); ++ ITEM_NAMES.put(420, "minecraft:lead"); ++ ITEM_NAMES.put(421, "minecraft:name_tag"); ++ ITEM_NAMES.put(422, "minecraft:command_block_minecart"); ++ ITEM_NAMES.put(2256, "minecraft:record_13"); ++ ITEM_NAMES.put(2257, "minecraft:record_cat"); ++ ITEM_NAMES.put(2258, "minecraft:record_blocks"); ++ ITEM_NAMES.put(2259, "minecraft:record_chirp"); ++ ITEM_NAMES.put(2260, "minecraft:record_far"); ++ ITEM_NAMES.put(2261, "minecraft:record_mall"); ++ ITEM_NAMES.put(2262, "minecraft:record_mellohi"); ++ ITEM_NAMES.put(2263, "minecraft:record_stal"); ++ ITEM_NAMES.put(2264, "minecraft:record_strad"); ++ ITEM_NAMES.put(2265, "minecraft:record_ward"); ++ ITEM_NAMES.put(2266, "minecraft:record_11"); ++ ITEM_NAMES.put(2267, "minecraft:record_wait"); ++ // https://github.com/starlis/empirecraft/commit/2da59d1901407fc0c135ef0458c0fe9b016570b3 ++ // It's likely that this is a result of old CB/Spigot behavior still writing ids into items as ints. ++ // These ids do not appear to be used by regular MC anyways, so I do not see the harm of porting it here. ++ // Extras can be added if needed ++ String[] extra = new String[4_000]; ++ // EMC start ++ extra[409] = "minecraft:prismarine_shard"; ++ extra[410] = "minecraft:prismarine_crystals"; ++ extra[411] = "minecraft:rabbit"; ++ extra[412] = "minecraft:cooked_rabbit"; ++ extra[413] = "minecraft:rabbit_stew"; ++ extra[414] = "minecraft:rabbit_foot"; ++ extra[415] = "minecraft:rabbit_hide"; ++ extra[416] = "minecraft:armor_stand"; ++ extra[423] = "minecraft:mutton"; ++ extra[424] = "minecraft:cooked_mutton"; ++ extra[425] = "minecraft:banner"; ++ extra[426] = "minecraft:end_crystal"; ++ extra[427] = "minecraft:spruce_door"; ++ extra[428] = "minecraft:birch_door"; ++ extra[429] = "minecraft:jungle_door"; ++ extra[430] = "minecraft:acacia_door"; ++ extra[431] = "minecraft:dark_oak_door"; ++ extra[432] = "minecraft:chorus_fruit"; ++ extra[433] = "minecraft:chorus_fruit_popped"; ++ extra[434] = "minecraft:beetroot"; ++ extra[435] = "minecraft:beetroot_seeds"; ++ extra[436] = "minecraft:beetroot_soup"; ++ extra[437] = "minecraft:dragon_breath"; ++ extra[438] = "minecraft:splash_potion"; ++ extra[439] = "minecraft:spectral_arrow"; ++ extra[440] = "minecraft:tipped_arrow"; ++ extra[441] = "minecraft:lingering_potion"; ++ extra[442] = "minecraft:shield"; ++ extra[443] = "minecraft:elytra"; ++ extra[444] = "minecraft:spruce_boat"; ++ extra[445] = "minecraft:birch_boat"; ++ extra[446] = "minecraft:jungle_boat"; ++ extra[447] = "minecraft:acacia_boat"; ++ extra[448] = "minecraft:dark_oak_boat"; ++ extra[449] = "minecraft:totem_of_undying"; ++ extra[450] = "minecraft:shulker_shell"; ++ extra[452] = "minecraft:iron_nugget"; ++ extra[453] = "minecraft:knowledge_book"; ++ // EMC end ++ ++ // dump extra into map ++ for (int i = 0; i < extra.length; ++i) { ++ if (extra[i] != null) { ++ ITEM_NAMES.put(i, extra[i]); ++ } ++ } ++ ++ // Add block ids into conversion as well ++ // Very old versions of the game handled them, but it seems 1.8.8 did not parse them at all, so no conversion ++ // was written. ++ // block ids are only skipped (set to AIR) if there is no 1-1 replacement item. ++ ITEM_NAMES.put(26, "minecraft:bed"); // bed block ++ ITEM_NAMES.put(34, ITEM_NAMES.get(0)); // skip (piston head block) ++ ITEM_NAMES.put(55, "minecraft:redstone"); // redstone wire block ++ ITEM_NAMES.put(59, ITEM_NAMES.get(0)); // skip (wheat crop block) ++ ITEM_NAMES.put(63, "minecraft:sign"); // standing sign ++ ITEM_NAMES.put(64, "minecraft:wooden_door"); // wooden door block ++ ITEM_NAMES.put(68, "minecraft:sign"); // wall sign ++ ITEM_NAMES.put(71, "minecraft:iron_door"); // iron door block ++ ITEM_NAMES.put(74, "minecraft:redstone_ore"); // lit redstone ore block ++ ITEM_NAMES.put(75, "minecraft:redstone_torch"); // unlit redstone torch ++ ITEM_NAMES.put(83, "minecraft:reeds"); // sugar cane block ++ ITEM_NAMES.put(92, "minecraft:cake"); // cake block ++ ITEM_NAMES.put(93, "minecraft:repeater"); // unpowered repeater block ++ ITEM_NAMES.put(94, "minecraft:repeater"); // powered repeater block ++ ITEM_NAMES.put(104, ITEM_NAMES.get(0)); // skip (pumpkin stem) ++ ITEM_NAMES.put(105, ITEM_NAMES.get(0)); // skip (melon stem) ++ ITEM_NAMES.put(115, "minecraft:nether_wart"); // nether wart block ++ ITEM_NAMES.put(117, "minecraft:brewing_stand"); // brewing stand block ++ ITEM_NAMES.put(118, "minecraft:cauldron"); // cauldron block ++ ITEM_NAMES.put(124, "minecraft:redstone_lamp"); // lit redstone lamp block ++ ITEM_NAMES.put(132, ITEM_NAMES.get(0)); // skip (tripwire wire block) ++ ITEM_NAMES.put(140, "minecraft:flower_pot"); // flower pot block ++ ITEM_NAMES.put(144, "minecraft:skull"); // skull block ++ ITEM_NAMES.put(149, "minecraft:comparator"); // unpowered comparator block ++ ITEM_NAMES.put(150, "minecraft:comparator"); // powered comparator block ++ // there are technically more, but at some point even older versions pre id -> name conversion didn't even load them. ++ // (all I know is 1.7.10 does not load them) ++ // and so given even the vanilla game wouldn't load them, there's no conversion path for them - they were never valid. ++ } ++ ++ private static final String[] POTION_NAMES = new String[128]; ++ static { ++ POTION_NAMES[0] = "minecraft:water"; ++ POTION_NAMES[1] = "minecraft:regeneration"; ++ POTION_NAMES[2] = "minecraft:swiftness"; ++ POTION_NAMES[3] = "minecraft:fire_resistance"; ++ POTION_NAMES[4] = "minecraft:poison"; ++ POTION_NAMES[5] = "minecraft:healing"; ++ POTION_NAMES[6] = "minecraft:night_vision"; ++ POTION_NAMES[7] = null; ++ POTION_NAMES[8] = "minecraft:weakness"; ++ POTION_NAMES[9] = "minecraft:strength"; ++ POTION_NAMES[10] = "minecraft:slowness"; ++ POTION_NAMES[11] = "minecraft:leaping"; ++ POTION_NAMES[12] = "minecraft:harming"; ++ POTION_NAMES[13] = "minecraft:water_breathing"; ++ POTION_NAMES[14] = "minecraft:invisibility"; ++ POTION_NAMES[15] = null; ++ POTION_NAMES[16] = "minecraft:awkward"; ++ POTION_NAMES[17] = "minecraft:regeneration"; ++ POTION_NAMES[18] = "minecraft:swiftness"; ++ POTION_NAMES[19] = "minecraft:fire_resistance"; ++ POTION_NAMES[20] = "minecraft:poison"; ++ POTION_NAMES[21] = "minecraft:healing"; ++ POTION_NAMES[22] = "minecraft:night_vision"; ++ POTION_NAMES[23] = null; ++ POTION_NAMES[24] = "minecraft:weakness"; ++ POTION_NAMES[25] = "minecraft:strength"; ++ POTION_NAMES[26] = "minecraft:slowness"; ++ POTION_NAMES[27] = "minecraft:leaping"; ++ POTION_NAMES[28] = "minecraft:harming"; ++ POTION_NAMES[29] = "minecraft:water_breathing"; ++ POTION_NAMES[30] = "minecraft:invisibility"; ++ POTION_NAMES[31] = null; ++ POTION_NAMES[32] = "minecraft:thick"; ++ POTION_NAMES[33] = "minecraft:strong_regeneration"; ++ POTION_NAMES[34] = "minecraft:strong_swiftness"; ++ POTION_NAMES[35] = "minecraft:fire_resistance"; ++ POTION_NAMES[36] = "minecraft:strong_poison"; ++ POTION_NAMES[37] = "minecraft:strong_healing"; ++ POTION_NAMES[38] = "minecraft:night_vision"; ++ POTION_NAMES[39] = null; ++ POTION_NAMES[40] = "minecraft:weakness"; ++ POTION_NAMES[41] = "minecraft:strong_strength"; ++ POTION_NAMES[42] = "minecraft:slowness"; ++ POTION_NAMES[43] = "minecraft:strong_leaping"; ++ POTION_NAMES[44] = "minecraft:strong_harming"; ++ POTION_NAMES[45] = "minecraft:water_breathing"; ++ POTION_NAMES[46] = "minecraft:invisibility"; ++ POTION_NAMES[47] = null; ++ POTION_NAMES[48] = null; ++ POTION_NAMES[49] = "minecraft:strong_regeneration"; ++ POTION_NAMES[50] = "minecraft:strong_swiftness"; ++ POTION_NAMES[51] = "minecraft:fire_resistance"; ++ POTION_NAMES[52] = "minecraft:strong_poison"; ++ POTION_NAMES[53] = "minecraft:strong_healing"; ++ POTION_NAMES[54] = "minecraft:night_vision"; ++ POTION_NAMES[55] = null; ++ POTION_NAMES[56] = "minecraft:weakness"; ++ POTION_NAMES[57] = "minecraft:strong_strength"; ++ POTION_NAMES[58] = "minecraft:slowness"; ++ POTION_NAMES[59] = "minecraft:strong_leaping"; ++ POTION_NAMES[60] = "minecraft:strong_harming"; ++ POTION_NAMES[61] = "minecraft:water_breathing"; ++ POTION_NAMES[62] = "minecraft:invisibility"; ++ POTION_NAMES[63] = null; ++ POTION_NAMES[64] = "minecraft:mundane"; ++ POTION_NAMES[65] = "minecraft:long_regeneration"; ++ POTION_NAMES[66] = "minecraft:long_swiftness"; ++ POTION_NAMES[67] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[68] = "minecraft:long_poison"; ++ POTION_NAMES[69] = "minecraft:healing"; ++ POTION_NAMES[70] = "minecraft:long_night_vision"; ++ POTION_NAMES[71] = null; ++ POTION_NAMES[72] = "minecraft:long_weakness"; ++ POTION_NAMES[73] = "minecraft:long_strength"; ++ POTION_NAMES[74] = "minecraft:long_slowness"; ++ POTION_NAMES[75] = "minecraft:long_leaping"; ++ POTION_NAMES[76] = "minecraft:harming"; ++ POTION_NAMES[77] = "minecraft:long_water_breathing"; ++ POTION_NAMES[78] = "minecraft:long_invisibility"; ++ POTION_NAMES[79] = null; ++ POTION_NAMES[80] = "minecraft:awkward"; ++ POTION_NAMES[81] = "minecraft:long_regeneration"; ++ POTION_NAMES[82] = "minecraft:long_swiftness"; ++ POTION_NAMES[83] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[84] = "minecraft:long_poison"; ++ POTION_NAMES[85] = "minecraft:healing"; ++ POTION_NAMES[86] = "minecraft:long_night_vision"; ++ POTION_NAMES[87] = null; ++ POTION_NAMES[88] = "minecraft:long_weakness"; ++ POTION_NAMES[89] = "minecraft:long_strength"; ++ POTION_NAMES[90] = "minecraft:long_slowness"; ++ POTION_NAMES[91] = "minecraft:long_leaping"; ++ POTION_NAMES[92] = "minecraft:harming"; ++ POTION_NAMES[93] = "minecraft:long_water_breathing"; ++ POTION_NAMES[94] = "minecraft:long_invisibility"; ++ POTION_NAMES[95] = null; ++ POTION_NAMES[96] = "minecraft:thick"; ++ POTION_NAMES[97] = "minecraft:regeneration"; ++ POTION_NAMES[98] = "minecraft:swiftness"; ++ POTION_NAMES[99] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[100] = "minecraft:poison"; ++ POTION_NAMES[101] = "minecraft:strong_healing"; ++ POTION_NAMES[102] = "minecraft:long_night_vision"; ++ POTION_NAMES[103] = null; ++ POTION_NAMES[104] = "minecraft:long_weakness"; ++ POTION_NAMES[105] = "minecraft:strength"; ++ POTION_NAMES[106] = "minecraft:long_slowness"; ++ POTION_NAMES[107] = "minecraft:leaping"; ++ POTION_NAMES[108] = "minecraft:strong_harming"; ++ POTION_NAMES[109] = "minecraft:long_water_breathing"; ++ POTION_NAMES[110] = "minecraft:long_invisibility"; ++ POTION_NAMES[111] = null; ++ POTION_NAMES[112] = null; ++ POTION_NAMES[113] = "minecraft:regeneration"; ++ POTION_NAMES[114] = "minecraft:swiftness"; ++ POTION_NAMES[115] = "minecraft:long_fire_resistance"; ++ POTION_NAMES[116] = "minecraft:poison"; ++ POTION_NAMES[117] = "minecraft:strong_healing"; ++ POTION_NAMES[118] = "minecraft:long_night_vision"; ++ POTION_NAMES[119] = null; ++ POTION_NAMES[120] = "minecraft:long_weakness"; ++ POTION_NAMES[121] = "minecraft:strength"; ++ POTION_NAMES[122] = "minecraft:long_slowness"; ++ POTION_NAMES[123] = "minecraft:leaping"; ++ POTION_NAMES[124] = "minecraft:strong_harming"; ++ POTION_NAMES[125] = "minecraft:long_water_breathing"; ++ POTION_NAMES[126] = "minecraft:long_invisibility"; ++ POTION_NAMES[127] = null; ++ } ++ ++ // ret is nullable, you are supposed to log when it does not exist, NOT HIDE IT! ++ public static String getNameFromId(final int id) { ++ return ITEM_NAMES.get(id); ++ } ++ ++ public static String getPotionNameFromId(final short id) { ++ return POTION_NAMES[id & 127]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5008c6d28b7f9b730bfaf257a264edcb45c78487 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++public final class HelperSpawnEggNameV105 { ++ ++ private static final String[] ID_TO_STRING = new String[256]; ++ static { ++ ID_TO_STRING[1] = "Item"; ++ ID_TO_STRING[2] = "XPOrb"; ++ ID_TO_STRING[7] = "ThrownEgg"; ++ ID_TO_STRING[8] = "LeashKnot"; ++ ID_TO_STRING[9] = "Painting"; ++ ID_TO_STRING[10] = "Arrow"; ++ ID_TO_STRING[11] = "Snowball"; ++ ID_TO_STRING[12] = "Fireball"; ++ ID_TO_STRING[13] = "SmallFireball"; ++ ID_TO_STRING[14] = "ThrownEnderpearl"; ++ ID_TO_STRING[15] = "EyeOfEnderSignal"; ++ ID_TO_STRING[16] = "ThrownPotion"; ++ ID_TO_STRING[17] = "ThrownExpBottle"; ++ ID_TO_STRING[18] = "ItemFrame"; ++ ID_TO_STRING[19] = "WitherSkull"; ++ ID_TO_STRING[20] = "PrimedTnt"; ++ ID_TO_STRING[21] = "FallingSand"; ++ ID_TO_STRING[22] = "FireworksRocketEntity"; ++ ID_TO_STRING[23] = "TippedArrow"; ++ ID_TO_STRING[24] = "SpectralArrow"; ++ ID_TO_STRING[25] = "ShulkerBullet"; ++ ID_TO_STRING[26] = "DragonFireball"; ++ ID_TO_STRING[30] = "ArmorStand"; ++ ID_TO_STRING[41] = "Boat"; ++ ID_TO_STRING[42] = "MinecartRideable"; ++ ID_TO_STRING[43] = "MinecartChest"; ++ ID_TO_STRING[44] = "MinecartFurnace"; ++ ID_TO_STRING[45] = "MinecartTNT"; ++ ID_TO_STRING[46] = "MinecartHopper"; ++ ID_TO_STRING[47] = "MinecartSpawner"; ++ ID_TO_STRING[40] = "MinecartCommandBlock"; ++ ID_TO_STRING[48] = "Mob"; ++ ID_TO_STRING[49] = "Monster"; ++ ID_TO_STRING[50] = "Creeper"; ++ ID_TO_STRING[51] = "Skeleton"; ++ ID_TO_STRING[52] = "Spider"; ++ ID_TO_STRING[53] = "Giant"; ++ ID_TO_STRING[54] = "Zombie"; ++ ID_TO_STRING[55] = "Slime"; ++ ID_TO_STRING[56] = "Ghast"; ++ ID_TO_STRING[57] = "PigZombie"; ++ ID_TO_STRING[58] = "Enderman"; ++ ID_TO_STRING[59] = "CaveSpider"; ++ ID_TO_STRING[60] = "Silverfish"; ++ ID_TO_STRING[61] = "Blaze"; ++ ID_TO_STRING[62] = "LavaSlime"; ++ ID_TO_STRING[63] = "EnderDragon"; ++ ID_TO_STRING[64] = "WitherBoss"; ++ ID_TO_STRING[65] = "Bat"; ++ ID_TO_STRING[66] = "Witch"; ++ ID_TO_STRING[67] = "Endermite"; ++ ID_TO_STRING[68] = "Guardian"; ++ ID_TO_STRING[69] = "Shulker"; ++ ID_TO_STRING[90] = "Pig"; ++ ID_TO_STRING[91] = "Sheep"; ++ ID_TO_STRING[92] = "Cow"; ++ ID_TO_STRING[93] = "Chicken"; ++ ID_TO_STRING[94] = "Squid"; ++ ID_TO_STRING[95] = "Wolf"; ++ ID_TO_STRING[96] = "MushroomCow"; ++ ID_TO_STRING[97] = "SnowMan"; ++ ID_TO_STRING[98] = "Ozelot"; ++ ID_TO_STRING[99] = "VillagerGolem"; ++ ID_TO_STRING[100] = "EntityHorse"; ++ ID_TO_STRING[101] = "Rabbit"; ++ ID_TO_STRING[120] = "Villager"; ++ ID_TO_STRING[200] = "EnderCrystal"; ++ } ++ ++ public static String getSpawnNameFromId(final short id) { ++ return ID_TO_STRING[id & 255]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d1d99bec73595d49eadf0fdeb8d3999ced38762a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java +@@ -0,0 +1,63 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.helpers; ++ ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.function.Function; ++ ++public final class RenameHelper { ++ ++ // assumes no two or more entries are renamed to a single value, otherwise result will be only one of them will win ++ // and there is no defined winner in such a case ++ public static void renameKeys(final MapType data, final Function renamer) { ++ List newKeys = null; ++ List newValues = null; ++ boolean needsRename = false; ++ for (final String key : data.keys()) { ++ final String renamed = renamer.apply(key); ++ if (renamed != null) { ++ newKeys = new ArrayList<>(); ++ newValues = new ArrayList<>(); ++ newValues.add(data.getGeneric(key)); ++ newKeys.add(renamed); ++ data.remove(key); ++ needsRename = true; ++ break; ++ } ++ } ++ ++ if (!needsRename) { ++ return; ++ } ++ ++ for (final String key : new ArrayList<>(data.keys())) { ++ final String renamed = renamer.apply(key); ++ ++ if (renamed != null) { ++ newValues.add(data.getGeneric(key)); ++ newKeys.add(renamed); ++ data.remove(key); ++ } ++ } ++ ++ // insert new keys ++ for (int i = 0, len = newKeys.size(); i < len; ++i) { ++ final String key = newKeys.get(i); ++ final Object value = newValues.get(i); ++ ++ data.setGeneric(key, value); ++ } ++ } ++ ++ // Clobbers anything in toKey if fromKey exists ++ public static void renameSingle(final MapType data, final String fromKey, final String toKey) { ++ final Object value = data.getGeneric(fromKey); ++ if (value != null) { ++ data.remove(fromKey); ++ data.setGeneric(toKey, value); ++ } ++ } ++ ++ private RenameHelper() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemname; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import java.util.function.Function; ++ ++public final class ConverterAbstractItemRename { ++ ++ private ConverterAbstractItemRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ITEM_NAME, renamer); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java +new file mode 100644 +index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java +@@ -0,0 +1,460 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class ConverterFlattenItemStack extends DataConverter, MapType> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ // Map of "id.damage" -> "flattened id" ++ private static final Map FLATTEN_MAP = new HashMap<>(); ++ static { ++ FLATTEN_MAP.put("minecraft:stone.0", "minecraft:stone"); ++ FLATTEN_MAP.put("minecraft:stone.1", "minecraft:granite"); ++ FLATTEN_MAP.put("minecraft:stone.2", "minecraft:polished_granite"); ++ FLATTEN_MAP.put("minecraft:stone.3", "minecraft:diorite"); ++ FLATTEN_MAP.put("minecraft:stone.4", "minecraft:polished_diorite"); ++ FLATTEN_MAP.put("minecraft:stone.5", "minecraft:andesite"); ++ FLATTEN_MAP.put("minecraft:stone.6", "minecraft:polished_andesite"); ++ FLATTEN_MAP.put("minecraft:dirt.0", "minecraft:dirt"); ++ FLATTEN_MAP.put("minecraft:dirt.1", "minecraft:coarse_dirt"); ++ FLATTEN_MAP.put("minecraft:dirt.2", "minecraft:podzol"); ++ FLATTEN_MAP.put("minecraft:leaves.0", "minecraft:oak_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.1", "minecraft:spruce_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.2", "minecraft:birch_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves.3", "minecraft:jungle_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves2.0", "minecraft:acacia_leaves"); ++ FLATTEN_MAP.put("minecraft:leaves2.1", "minecraft:dark_oak_leaves"); ++ FLATTEN_MAP.put("minecraft:log.0", "minecraft:oak_log"); ++ FLATTEN_MAP.put("minecraft:log.1", "minecraft:spruce_log"); ++ FLATTEN_MAP.put("minecraft:log.2", "minecraft:birch_log"); ++ FLATTEN_MAP.put("minecraft:log.3", "minecraft:jungle_log"); ++ FLATTEN_MAP.put("minecraft:log2.0", "minecraft:acacia_log"); ++ FLATTEN_MAP.put("minecraft:log2.1", "minecraft:dark_oak_log"); ++ FLATTEN_MAP.put("minecraft:sapling.0", "minecraft:oak_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.1", "minecraft:spruce_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.2", "minecraft:birch_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.3", "minecraft:jungle_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.4", "minecraft:acacia_sapling"); ++ FLATTEN_MAP.put("minecraft:sapling.5", "minecraft:dark_oak_sapling"); ++ FLATTEN_MAP.put("minecraft:planks.0", "minecraft:oak_planks"); ++ FLATTEN_MAP.put("minecraft:planks.1", "minecraft:spruce_planks"); ++ FLATTEN_MAP.put("minecraft:planks.2", "minecraft:birch_planks"); ++ FLATTEN_MAP.put("minecraft:planks.3", "minecraft:jungle_planks"); ++ FLATTEN_MAP.put("minecraft:planks.4", "minecraft:acacia_planks"); ++ FLATTEN_MAP.put("minecraft:planks.5", "minecraft:dark_oak_planks"); ++ FLATTEN_MAP.put("minecraft:sand.0", "minecraft:sand"); ++ FLATTEN_MAP.put("minecraft:sand.1", "minecraft:red_sand"); ++ FLATTEN_MAP.put("minecraft:quartz_block.0", "minecraft:quartz_block"); ++ FLATTEN_MAP.put("minecraft:quartz_block.1", "minecraft:chiseled_quartz_block"); ++ FLATTEN_MAP.put("minecraft:quartz_block.2", "minecraft:quartz_pillar"); ++ FLATTEN_MAP.put("minecraft:anvil.0", "minecraft:anvil"); ++ FLATTEN_MAP.put("minecraft:anvil.1", "minecraft:chipped_anvil"); ++ FLATTEN_MAP.put("minecraft:anvil.2", "minecraft:damaged_anvil"); ++ FLATTEN_MAP.put("minecraft:wool.0", "minecraft:white_wool"); ++ FLATTEN_MAP.put("minecraft:wool.1", "minecraft:orange_wool"); ++ FLATTEN_MAP.put("minecraft:wool.2", "minecraft:magenta_wool"); ++ FLATTEN_MAP.put("minecraft:wool.3", "minecraft:light_blue_wool"); ++ FLATTEN_MAP.put("minecraft:wool.4", "minecraft:yellow_wool"); ++ FLATTEN_MAP.put("minecraft:wool.5", "minecraft:lime_wool"); ++ FLATTEN_MAP.put("minecraft:wool.6", "minecraft:pink_wool"); ++ FLATTEN_MAP.put("minecraft:wool.7", "minecraft:gray_wool"); ++ FLATTEN_MAP.put("minecraft:wool.8", "minecraft:light_gray_wool"); ++ FLATTEN_MAP.put("minecraft:wool.9", "minecraft:cyan_wool"); ++ FLATTEN_MAP.put("minecraft:wool.10", "minecraft:purple_wool"); ++ FLATTEN_MAP.put("minecraft:wool.11", "minecraft:blue_wool"); ++ FLATTEN_MAP.put("minecraft:wool.12", "minecraft:brown_wool"); ++ FLATTEN_MAP.put("minecraft:wool.13", "minecraft:green_wool"); ++ FLATTEN_MAP.put("minecraft:wool.14", "minecraft:red_wool"); ++ FLATTEN_MAP.put("minecraft:wool.15", "minecraft:black_wool"); ++ FLATTEN_MAP.put("minecraft:carpet.0", "minecraft:white_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.1", "minecraft:orange_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.2", "minecraft:magenta_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.3", "minecraft:light_blue_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.4", "minecraft:yellow_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.5", "minecraft:lime_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.6", "minecraft:pink_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.7", "minecraft:gray_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.8", "minecraft:light_gray_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.9", "minecraft:cyan_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.10", "minecraft:purple_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.11", "minecraft:blue_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.12", "minecraft:brown_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.13", "minecraft:green_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.14", "minecraft:red_carpet"); ++ FLATTEN_MAP.put("minecraft:carpet.15", "minecraft:black_carpet"); ++ FLATTEN_MAP.put("minecraft:hardened_clay.0", "minecraft:terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.0", "minecraft:white_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.1", "minecraft:orange_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.2", "minecraft:magenta_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.3", "minecraft:light_blue_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.4", "minecraft:yellow_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.5", "minecraft:lime_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.6", "minecraft:pink_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.7", "minecraft:gray_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.8", "minecraft:light_gray_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.9", "minecraft:cyan_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.10", "minecraft:purple_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.11", "minecraft:blue_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.12", "minecraft:brown_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.13", "minecraft:green_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.14", "minecraft:red_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_hardened_clay.15", "minecraft:black_terracotta"); ++ FLATTEN_MAP.put("minecraft:silver_glazed_terracotta.0", "minecraft:light_gray_glazed_terracotta"); ++ FLATTEN_MAP.put("minecraft:stained_glass.0", "minecraft:white_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.1", "minecraft:orange_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.2", "minecraft:magenta_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.3", "minecraft:light_blue_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.4", "minecraft:yellow_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.5", "minecraft:lime_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.6", "minecraft:pink_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.7", "minecraft:gray_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.8", "minecraft:light_gray_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.9", "minecraft:cyan_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.10", "minecraft:purple_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.11", "minecraft:blue_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.12", "minecraft:brown_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.13", "minecraft:green_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.14", "minecraft:red_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass.15", "minecraft:black_stained_glass"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.0", "minecraft:white_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.1", "minecraft:orange_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.2", "minecraft:magenta_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.3", "minecraft:light_blue_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.4", "minecraft:yellow_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.5", "minecraft:lime_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.6", "minecraft:pink_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.7", "minecraft:gray_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.8", "minecraft:light_gray_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.9", "minecraft:cyan_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.10", "minecraft:purple_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.11", "minecraft:blue_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.12", "minecraft:brown_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.13", "minecraft:green_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.14", "minecraft:red_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:stained_glass_pane.15", "minecraft:black_stained_glass_pane"); ++ FLATTEN_MAP.put("minecraft:prismarine.0", "minecraft:prismarine"); ++ FLATTEN_MAP.put("minecraft:prismarine.1", "minecraft:prismarine_bricks"); ++ FLATTEN_MAP.put("minecraft:prismarine.2", "minecraft:dark_prismarine"); ++ FLATTEN_MAP.put("minecraft:concrete.0", "minecraft:white_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.1", "minecraft:orange_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.2", "minecraft:magenta_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.3", "minecraft:light_blue_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.4", "minecraft:yellow_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.5", "minecraft:lime_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.6", "minecraft:pink_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.7", "minecraft:gray_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.8", "minecraft:light_gray_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.9", "minecraft:cyan_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.10", "minecraft:purple_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.11", "minecraft:blue_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.12", "minecraft:brown_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.13", "minecraft:green_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.14", "minecraft:red_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete.15", "minecraft:black_concrete"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.0", "minecraft:white_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.1", "minecraft:orange_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.2", "minecraft:magenta_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.3", "minecraft:light_blue_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.4", "minecraft:yellow_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.5", "minecraft:lime_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.6", "minecraft:pink_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.7", "minecraft:gray_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.8", "minecraft:light_gray_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.9", "minecraft:cyan_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.10", "minecraft:purple_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.11", "minecraft:blue_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.12", "minecraft:brown_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.13", "minecraft:green_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.14", "minecraft:red_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:concrete_powder.15", "minecraft:black_concrete_powder"); ++ FLATTEN_MAP.put("minecraft:cobblestone_wall.0", "minecraft:cobblestone_wall"); ++ FLATTEN_MAP.put("minecraft:cobblestone_wall.1", "minecraft:mossy_cobblestone_wall"); ++ FLATTEN_MAP.put("minecraft:sandstone.0", "minecraft:sandstone"); ++ FLATTEN_MAP.put("minecraft:sandstone.1", "minecraft:chiseled_sandstone"); ++ FLATTEN_MAP.put("minecraft:sandstone.2", "minecraft:cut_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.0", "minecraft:red_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.1", "minecraft:chiseled_red_sandstone"); ++ FLATTEN_MAP.put("minecraft:red_sandstone.2", "minecraft:cut_red_sandstone"); ++ FLATTEN_MAP.put("minecraft:stonebrick.0", "minecraft:stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.1", "minecraft:mossy_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.2", "minecraft:cracked_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:stonebrick.3", "minecraft:chiseled_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.0", "minecraft:infested_stone"); ++ FLATTEN_MAP.put("minecraft:monster_egg.1", "minecraft:infested_cobblestone"); ++ FLATTEN_MAP.put("minecraft:monster_egg.2", "minecraft:infested_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.3", "minecraft:infested_mossy_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.4", "minecraft:infested_cracked_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:monster_egg.5", "minecraft:infested_chiseled_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:yellow_flower.0", "minecraft:dandelion"); ++ FLATTEN_MAP.put("minecraft:red_flower.0", "minecraft:poppy"); ++ FLATTEN_MAP.put("minecraft:red_flower.1", "minecraft:blue_orchid"); ++ FLATTEN_MAP.put("minecraft:red_flower.2", "minecraft:allium"); ++ FLATTEN_MAP.put("minecraft:red_flower.3", "minecraft:azure_bluet"); ++ FLATTEN_MAP.put("minecraft:red_flower.4", "minecraft:red_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.5", "minecraft:orange_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.6", "minecraft:white_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.7", "minecraft:pink_tulip"); ++ FLATTEN_MAP.put("minecraft:red_flower.8", "minecraft:oxeye_daisy"); ++ FLATTEN_MAP.put("minecraft:double_plant.0", "minecraft:sunflower"); ++ FLATTEN_MAP.put("minecraft:double_plant.1", "minecraft:lilac"); ++ FLATTEN_MAP.put("minecraft:double_plant.2", "minecraft:tall_grass"); ++ FLATTEN_MAP.put("minecraft:double_plant.3", "minecraft:large_fern"); ++ FLATTEN_MAP.put("minecraft:double_plant.4", "minecraft:rose_bush"); ++ FLATTEN_MAP.put("minecraft:double_plant.5", "minecraft:peony"); ++ FLATTEN_MAP.put("minecraft:deadbush.0", "minecraft:dead_bush"); ++ FLATTEN_MAP.put("minecraft:tallgrass.0", "minecraft:dead_bush"); ++ FLATTEN_MAP.put("minecraft:tallgrass.1", "minecraft:grass"); ++ FLATTEN_MAP.put("minecraft:tallgrass.2", "minecraft:fern"); ++ FLATTEN_MAP.put("minecraft:sponge.0", "minecraft:sponge"); ++ FLATTEN_MAP.put("minecraft:sponge.1", "minecraft:wet_sponge"); ++ FLATTEN_MAP.put("minecraft:purpur_slab.0", "minecraft:purpur_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.0", "minecraft:stone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.1", "minecraft:sandstone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.2", "minecraft:petrified_oak_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.3", "minecraft:cobblestone_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.4", "minecraft:brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.5", "minecraft:stone_brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.6", "minecraft:nether_brick_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab.7", "minecraft:quartz_slab"); ++ FLATTEN_MAP.put("minecraft:stone_slab2.0", "minecraft:red_sandstone_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.0", "minecraft:oak_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.1", "minecraft:spruce_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.2", "minecraft:birch_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.3", "minecraft:jungle_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.4", "minecraft:acacia_slab"); ++ FLATTEN_MAP.put("minecraft:wooden_slab.5", "minecraft:dark_oak_slab"); ++ FLATTEN_MAP.put("minecraft:coal.0", "minecraft:coal"); ++ FLATTEN_MAP.put("minecraft:coal.1", "minecraft:charcoal"); ++ FLATTEN_MAP.put("minecraft:fish.0", "minecraft:cod"); ++ FLATTEN_MAP.put("minecraft:fish.1", "minecraft:salmon"); ++ FLATTEN_MAP.put("minecraft:fish.2", "minecraft:clownfish"); ++ FLATTEN_MAP.put("minecraft:fish.3", "minecraft:pufferfish"); ++ FLATTEN_MAP.put("minecraft:cooked_fish.0", "minecraft:cooked_cod"); ++ FLATTEN_MAP.put("minecraft:cooked_fish.1", "minecraft:cooked_salmon"); ++ FLATTEN_MAP.put("minecraft:skull.0", "minecraft:skeleton_skull"); ++ FLATTEN_MAP.put("minecraft:skull.1", "minecraft:wither_skeleton_skull"); ++ FLATTEN_MAP.put("minecraft:skull.2", "minecraft:zombie_head"); ++ FLATTEN_MAP.put("minecraft:skull.3", "minecraft:player_head"); ++ FLATTEN_MAP.put("minecraft:skull.4", "minecraft:creeper_head"); ++ FLATTEN_MAP.put("minecraft:skull.5", "minecraft:dragon_head"); ++ FLATTEN_MAP.put("minecraft:golden_apple.0", "minecraft:golden_apple"); ++ FLATTEN_MAP.put("minecraft:golden_apple.1", "minecraft:enchanted_golden_apple"); ++ FLATTEN_MAP.put("minecraft:fireworks.0", "minecraft:firework_rocket"); ++ FLATTEN_MAP.put("minecraft:firework_charge.0", "minecraft:firework_star"); ++ FLATTEN_MAP.put("minecraft:dye.0", "minecraft:ink_sac"); ++ FLATTEN_MAP.put("minecraft:dye.1", "minecraft:rose_red"); ++ FLATTEN_MAP.put("minecraft:dye.2", "minecraft:cactus_green"); ++ FLATTEN_MAP.put("minecraft:dye.3", "minecraft:cocoa_beans"); ++ FLATTEN_MAP.put("minecraft:dye.4", "minecraft:lapis_lazuli"); ++ FLATTEN_MAP.put("minecraft:dye.5", "minecraft:purple_dye"); ++ FLATTEN_MAP.put("minecraft:dye.6", "minecraft:cyan_dye"); ++ FLATTEN_MAP.put("minecraft:dye.7", "minecraft:light_gray_dye"); ++ FLATTEN_MAP.put("minecraft:dye.8", "minecraft:gray_dye"); ++ FLATTEN_MAP.put("minecraft:dye.9", "minecraft:pink_dye"); ++ FLATTEN_MAP.put("minecraft:dye.10", "minecraft:lime_dye"); ++ FLATTEN_MAP.put("minecraft:dye.11", "minecraft:dandelion_yellow"); ++ FLATTEN_MAP.put("minecraft:dye.12", "minecraft:light_blue_dye"); ++ FLATTEN_MAP.put("minecraft:dye.13", "minecraft:magenta_dye"); ++ FLATTEN_MAP.put("minecraft:dye.14", "minecraft:orange_dye"); ++ FLATTEN_MAP.put("minecraft:dye.15", "minecraft:bone_meal"); ++ FLATTEN_MAP.put("minecraft:silver_shulker_box.0", "minecraft:light_gray_shulker_box"); ++ FLATTEN_MAP.put("minecraft:fence.0", "minecraft:oak_fence"); ++ FLATTEN_MAP.put("minecraft:fence_gate.0", "minecraft:oak_fence_gate"); ++ FLATTEN_MAP.put("minecraft:wooden_door.0", "minecraft:oak_door"); ++ FLATTEN_MAP.put("minecraft:boat.0", "minecraft:oak_boat"); ++ FLATTEN_MAP.put("minecraft:lit_pumpkin.0", "minecraft:jack_o_lantern"); ++ FLATTEN_MAP.put("minecraft:pumpkin.0", "minecraft:carved_pumpkin"); ++ FLATTEN_MAP.put("minecraft:trapdoor.0", "minecraft:oak_trapdoor"); ++ FLATTEN_MAP.put("minecraft:nether_brick.0", "minecraft:nether_bricks"); ++ FLATTEN_MAP.put("minecraft:red_nether_brick.0", "minecraft:red_nether_bricks"); ++ FLATTEN_MAP.put("minecraft:netherbrick.0", "minecraft:nether_brick"); ++ FLATTEN_MAP.put("minecraft:wooden_button.0", "minecraft:oak_button"); ++ FLATTEN_MAP.put("minecraft:wooden_pressure_plate.0", "minecraft:oak_pressure_plate"); ++ FLATTEN_MAP.put("minecraft:noteblock.0", "minecraft:note_block"); ++ FLATTEN_MAP.put("minecraft:bed.0", "minecraft:white_bed"); ++ FLATTEN_MAP.put("minecraft:bed.1", "minecraft:orange_bed"); ++ FLATTEN_MAP.put("minecraft:bed.2", "minecraft:magenta_bed"); ++ FLATTEN_MAP.put("minecraft:bed.3", "minecraft:light_blue_bed"); ++ FLATTEN_MAP.put("minecraft:bed.4", "minecraft:yellow_bed"); ++ FLATTEN_MAP.put("minecraft:bed.5", "minecraft:lime_bed"); ++ FLATTEN_MAP.put("minecraft:bed.6", "minecraft:pink_bed"); ++ FLATTEN_MAP.put("minecraft:bed.7", "minecraft:gray_bed"); ++ FLATTEN_MAP.put("minecraft:bed.8", "minecraft:light_gray_bed"); ++ FLATTEN_MAP.put("minecraft:bed.9", "minecraft:cyan_bed"); ++ FLATTEN_MAP.put("minecraft:bed.10", "minecraft:purple_bed"); ++ FLATTEN_MAP.put("minecraft:bed.11", "minecraft:blue_bed"); ++ FLATTEN_MAP.put("minecraft:bed.12", "minecraft:brown_bed"); ++ FLATTEN_MAP.put("minecraft:bed.13", "minecraft:green_bed"); ++ FLATTEN_MAP.put("minecraft:bed.14", "minecraft:red_bed"); ++ FLATTEN_MAP.put("minecraft:bed.15", "minecraft:black_bed"); ++ FLATTEN_MAP.put("minecraft:banner.15", "minecraft:white_banner"); ++ FLATTEN_MAP.put("minecraft:banner.14", "minecraft:orange_banner"); ++ FLATTEN_MAP.put("minecraft:banner.13", "minecraft:magenta_banner"); ++ FLATTEN_MAP.put("minecraft:banner.12", "minecraft:light_blue_banner"); ++ FLATTEN_MAP.put("minecraft:banner.11", "minecraft:yellow_banner"); ++ FLATTEN_MAP.put("minecraft:banner.10", "minecraft:lime_banner"); ++ FLATTEN_MAP.put("minecraft:banner.9", "minecraft:pink_banner"); ++ FLATTEN_MAP.put("minecraft:banner.8", "minecraft:gray_banner"); ++ FLATTEN_MAP.put("minecraft:banner.7", "minecraft:light_gray_banner"); ++ FLATTEN_MAP.put("minecraft:banner.6", "minecraft:cyan_banner"); ++ FLATTEN_MAP.put("minecraft:banner.5", "minecraft:purple_banner"); ++ FLATTEN_MAP.put("minecraft:banner.4", "minecraft:blue_banner"); ++ FLATTEN_MAP.put("minecraft:banner.3", "minecraft:brown_banner"); ++ FLATTEN_MAP.put("minecraft:banner.2", "minecraft:green_banner"); ++ FLATTEN_MAP.put("minecraft:banner.1", "minecraft:red_banner"); ++ FLATTEN_MAP.put("minecraft:banner.0", "minecraft:black_banner"); ++ FLATTEN_MAP.put("minecraft:grass.0", "minecraft:grass_block"); ++ FLATTEN_MAP.put("minecraft:brick_block.0", "minecraft:bricks"); ++ FLATTEN_MAP.put("minecraft:end_bricks.0", "minecraft:end_stone_bricks"); ++ FLATTEN_MAP.put("minecraft:golden_rail.0", "minecraft:powered_rail"); ++ FLATTEN_MAP.put("minecraft:magma.0", "minecraft:magma_block"); ++ FLATTEN_MAP.put("minecraft:quartz_ore.0", "minecraft:nether_quartz_ore"); ++ FLATTEN_MAP.put("minecraft:reeds.0", "minecraft:sugar_cane"); ++ FLATTEN_MAP.put("minecraft:slime.0", "minecraft:slime_block"); ++ FLATTEN_MAP.put("minecraft:stone_stairs.0", "minecraft:cobblestone_stairs"); ++ FLATTEN_MAP.put("minecraft:waterlily.0", "minecraft:lily_pad"); ++ FLATTEN_MAP.put("minecraft:web.0", "minecraft:cobweb"); ++ FLATTEN_MAP.put("minecraft:snow.0", "minecraft:snow_block"); ++ FLATTEN_MAP.put("minecraft:snow_layer.0", "minecraft:snow"); ++ FLATTEN_MAP.put("minecraft:record_11.0", "minecraft:music_disc_11"); ++ FLATTEN_MAP.put("minecraft:record_13.0", "minecraft:music_disc_13"); ++ FLATTEN_MAP.put("minecraft:record_blocks.0", "minecraft:music_disc_blocks"); ++ FLATTEN_MAP.put("minecraft:record_cat.0", "minecraft:music_disc_cat"); ++ FLATTEN_MAP.put("minecraft:record_chirp.0", "minecraft:music_disc_chirp"); ++ FLATTEN_MAP.put("minecraft:record_far.0", "minecraft:music_disc_far"); ++ FLATTEN_MAP.put("minecraft:record_mall.0", "minecraft:music_disc_mall"); ++ FLATTEN_MAP.put("minecraft:record_mellohi.0", "minecraft:music_disc_mellohi"); ++ FLATTEN_MAP.put("minecraft:record_stal.0", "minecraft:music_disc_stal"); ++ FLATTEN_MAP.put("minecraft:record_strad.0", "minecraft:music_disc_strad"); ++ FLATTEN_MAP.put("minecraft:record_wait.0", "minecraft:music_disc_wait"); ++ FLATTEN_MAP.put("minecraft:record_ward.0", "minecraft:music_disc_ward"); ++ } ++ ++ // maps out ids requiring flattening ++ private static final Set IDS_REQUIRING_FLATTENING = new HashSet<>(); ++ static { ++ for (final String key : FLATTEN_MAP.keySet()) { ++ IDS_REQUIRING_FLATTENING.add(key.substring(0, key.indexOf('.'))); ++ } ++ } ++ ++ // Damage tag is moved from the ItemStack base tag to the ItemStack tag, and we only want to migrate that ++ // for items that actually require it for damage purposes (Remember, old damage was used to differentiate item types) ++ // It should be noted that this ID set should not be included in the flattening map, because damage for these items ++ // is actual damage and not a subtype specifier ++ private static final Set ITEMS_WITH_DAMAGE = new HashSet<>(Arrays.asList( ++ "minecraft:bow", ++ "minecraft:carrot_on_a_stick", ++ "minecraft:chainmail_boots", ++ "minecraft:chainmail_chestplate", ++ "minecraft:chainmail_helmet", ++ "minecraft:chainmail_leggings", ++ "minecraft:diamond_axe", ++ "minecraft:diamond_boots", ++ "minecraft:diamond_chestplate", ++ "minecraft:diamond_helmet", ++ "minecraft:diamond_hoe", ++ "minecraft:diamond_leggings", ++ "minecraft:diamond_pickaxe", ++ "minecraft:diamond_shovel", ++ "minecraft:diamond_sword", ++ "minecraft:elytra", ++ "minecraft:fishing_rod", ++ "minecraft:flint_and_steel", ++ "minecraft:golden_axe", ++ "minecraft:golden_boots", ++ "minecraft:golden_chestplate", ++ "minecraft:golden_helmet", ++ "minecraft:golden_hoe", ++ "minecraft:golden_leggings", ++ "minecraft:golden_pickaxe", ++ "minecraft:golden_shovel", ++ "minecraft:golden_sword", ++ "minecraft:iron_axe", ++ "minecraft:iron_boots", ++ "minecraft:iron_chestplate", ++ "minecraft:iron_helmet", ++ "minecraft:iron_hoe", ++ "minecraft:iron_leggings", ++ "minecraft:iron_pickaxe", ++ "minecraft:iron_shovel", ++ "minecraft:iron_sword", ++ "minecraft:leather_boots", ++ "minecraft:leather_chestplate", ++ "minecraft:leather_helmet", ++ "minecraft:leather_leggings", ++ "minecraft:shears", ++ "minecraft:shield", ++ "minecraft:stone_axe", ++ "minecraft:stone_hoe", ++ "minecraft:stone_pickaxe", ++ "minecraft:stone_shovel", ++ "minecraft:stone_sword", ++ "minecraft:wooden_axe", ++ "minecraft:wooden_hoe", ++ "minecraft:wooden_pickaxe", ++ "minecraft:wooden_shovel", ++ "minecraft:wooden_sword" ++ )); ++ ++ public ConverterFlattenItemStack() { ++ super(MCVersions.V17W47A, 4); ++ } ++ ++ public static String flattenItem(final String oldName, final int data) { ++ if (IDS_REQUIRING_FLATTENING.contains(oldName)) { ++ final String flattened = FLATTEN_MAP.get(oldName + '.' + data); ++ return flattened == null ? FLATTEN_MAP.get(oldName.concat(".0")) : flattened; ++ } else { ++ return null; ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ ++ if (id == null) { ++ return null; ++ } ++ ++ final int damage = data.getInt("Damage"); ++ data.remove("Damage"); ++ ++ if (IDS_REQUIRING_FLATTENING.contains(id)) { ++ String remap = FLATTEN_MAP.get(id + '.' + damage); ++ if (remap == null) { ++ remap = FLATTEN_MAP.get(id.concat(".0")); ++ // this shouldn't be null ++ } ++ if (remap != null) { ++ data.setString("id", remap); ++ } else { ++ LOGGER.warn("Item '" + id + "' requires flattening but found no mapping for it! (ConverterFlattenItemStack)"); ++ } ++ } ++ ++ if (damage != 0 && ITEMS_WITH_DAMAGE.contains(id)) { ++ // migrate damage ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ tag.setInt("Damage", damage); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d88b12e6b9e381ba614dc04599a44e472a37ca03 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java +@@ -0,0 +1,83 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class ConverterFlattenSpawnEgg extends DataConverter, MapType> { ++ ++ private static final Map ENTITY_ID_TO_NEW_EGG_ID = new HashMap<>(); ++ static { ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:bat", "minecraft:bat_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:blaze", "minecraft:blaze_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cave_spider", "minecraft:cave_spider_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:chicken", "minecraft:chicken_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cow", "minecraft:cow_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:creeper", "minecraft:creeper_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:donkey", "minecraft:donkey_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:elder_guardian", "minecraft:elder_guardian_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:enderman", "minecraft:enderman_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:endermite", "minecraft:endermite_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:evocation_illager", "minecraft:evocation_illager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ghast", "minecraft:ghast_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:guardian", "minecraft:guardian_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:horse", "minecraft:horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:husk", "minecraft:husk_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:llama", "minecraft:llama_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:magma_cube", "minecraft:magma_cube_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mooshroom", "minecraft:mooshroom_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mule", "minecraft:mule_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ocelot", "minecraft:ocelot_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pufferfish", "minecraft:pufferfish_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:parrot", "minecraft:parrot_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pig", "minecraft:pig_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:polar_bear", "minecraft:polar_bear_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:rabbit", "minecraft:rabbit_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:sheep", "minecraft:sheep_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:shulker", "minecraft:shulker_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:silverfish", "minecraft:silverfish_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton", "minecraft:skeleton_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton_horse", "minecraft:skeleton_horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:slime", "minecraft:slime_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:spider", "minecraft:spider_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:squid", "minecraft:squid_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:stray", "minecraft:stray_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:turtle", "minecraft:turtle_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vex", "minecraft:vex_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:villager", "minecraft:villager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vindication_illager", "minecraft:vindication_illager_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:witch", "minecraft:witch_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither_skeleton", "minecraft:wither_skeleton_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wolf", "minecraft:wolf_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie", "minecraft:zombie_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_horse", "minecraft:zombie_horse_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_pigman", "minecraft:zombie_pigman_spawn_egg"); ++ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_villager", "minecraft:zombie_villager_spawn_egg"); ++ } ++ ++ public ConverterFlattenSpawnEgg() { ++ super(MCVersions.V17W47A,5); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag == null) { ++ return null; ++ } ++ ++ final String id = entityTag.getString("id"); ++ if (id != null) { ++ data.setString("id", ENTITY_ID_TO_NEW_EGG_ID.getOrDefault(id, "minecraft:pig_spawn_egg")); ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.options; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractOptionsRename { ++ ++ private ConverterAbstractOptionsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameKeys(data, renamer); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.poi; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.function.Function; ++ ++public final class ConverterAbstractPOIRename { ++ ++ private ConverterAbstractPOIRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ final ListType records = section.getList("Records", ObjectType.MAP); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ for (int i = 0, len = records.size(); i < len; ++i) { ++ final MapType record = records.getMap(i); ++ ++ final String type = record.getString("type"); ++ if (type != null) { ++ final String converted = renamer.apply(type); ++ if (converted != null) { ++ record.setString("type", converted); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java +new file mode 100644 +index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.poi; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import java.util.function.Predicate; ++ ++public final class ConverterPoiDelete extends DataConverter, MapType> { ++ ++ private final Predicate delete; ++ ++ public ConverterPoiDelete(final int toVersion, final Predicate delete) { ++ super(toVersion); ++ this.delete = delete; ++ } ++ ++ public ConverterPoiDelete(final int toVersion, final int versionStep, final Predicate delete) { ++ super(toVersion, versionStep); ++ this.delete = delete; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ final ListType records = section.getList("Records", ObjectType.MAP); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ for (int i = 0; i < records.size();) { ++ final MapType record = records.getMap(i); ++ ++ final String type = record.getString("type"); ++ if (type != null && this.delete.test(type)) { ++ records.remove(i); ++ continue; ++ } ++ ++i; ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.recipe; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import java.util.function.Function; ++ ++public final class ConverterAbstractRecipeRename { ++ ++ private ConverterAbstractRecipeRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.RECIPE, renamer); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java +@@ -0,0 +1,66 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.stats; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++import java.util.function.Function; ++ ++public final class ConverterAbstractStatsRename { ++ ++ private ConverterAbstractStatsRename() {} ++ ++ public static void register(final int version, final Function renamer) { ++ register(version, 0, renamer); ++ } ++ ++ public static void register(final int version, final int subVersion, final Function renamer) { ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ if (criteriaType == null) { ++ return null; ++ } ++ ++ final String type = criteriaType.getString("type"); ++ if (!"minecraft:custom".equals(type)) { ++ return null; ++ } ++ ++ final String id = criteriaType.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ final String rename = renamer.apply(id); ++ if (rename != null) { ++ criteriaType.setString("id", rename); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STATS.addStructureConverter(new DataConverter<>(version, subVersion) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType stats = data.getMap("stats"); ++ ++ if (stats == null) { ++ return null; ++ } ++ ++ final MapType custom = stats.getMap("minecraft:custom"); ++ if (custom == null) { ++ return null; ++ } ++ ++ RenameHelper.renameKeys(custom, renamer); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java +new file mode 100644 +index 0000000000000000000000000000000000000000..99d2c2c84820295be1f8bb0b43784e58f51a46dd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java +@@ -0,0 +1,94 @@ ++package ca.spottedleaf.dataconverter.minecraft.converters.stats; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; ++import org.apache.commons.lang3.StringUtils; ++import java.util.Map; ++import java.util.Set; ++ ++public final class ConverterFlattenStats extends DataConverter, MapType> { ++ ++ private static final Set SKIP = ImmutableSet.builder().add("stat.craftItem.minecraft.spawn_egg").add("stat.useItem.minecraft.spawn_egg").add("stat.breakItem.minecraft.spawn_egg").add("stat.pickup.minecraft.spawn_egg").add("stat.drop.minecraft.spawn_egg").build(); ++ private static final Map CUSTOM_MAP = ImmutableMap.builder().put("stat.leaveGame", "minecraft:leave_game").put("stat.playOneMinute", "minecraft:play_one_minute").put("stat.timeSinceDeath", "minecraft:time_since_death").put("stat.sneakTime", "minecraft:sneak_time").put("stat.walkOneCm", "minecraft:walk_one_cm").put("stat.crouchOneCm", "minecraft:crouch_one_cm").put("stat.sprintOneCm", "minecraft:sprint_one_cm").put("stat.swimOneCm", "minecraft:swim_one_cm").put("stat.fallOneCm", "minecraft:fall_one_cm").put("stat.climbOneCm", "minecraft:climb_one_cm").put("stat.flyOneCm", "minecraft:fly_one_cm").put("stat.diveOneCm", "minecraft:dive_one_cm").put("stat.minecartOneCm", "minecraft:minecart_one_cm").put("stat.boatOneCm", "minecraft:boat_one_cm").put("stat.pigOneCm", "minecraft:pig_one_cm").put("stat.horseOneCm", "minecraft:horse_one_cm").put("stat.aviateOneCm", "minecraft:aviate_one_cm").put("stat.jump", "minecraft:jump").put("stat.drop", "minecraft:drop").put("stat.damageDealt", "minecraft:damage_dealt").put("stat.damageTaken", "minecraft:damage_taken").put("stat.deaths", "minecraft:deaths").put("stat.mobKills", "minecraft:mob_kills").put("stat.animalsBred", "minecraft:animals_bred").put("stat.playerKills", "minecraft:player_kills").put("stat.fishCaught", "minecraft:fish_caught").put("stat.talkedToVillager", "minecraft:talked_to_villager").put("stat.tradedWithVillager", "minecraft:traded_with_villager").put("stat.cakeSlicesEaten", "minecraft:eat_cake_slice").put("stat.cauldronFilled", "minecraft:fill_cauldron").put("stat.cauldronUsed", "minecraft:use_cauldron").put("stat.armorCleaned", "minecraft:clean_armor").put("stat.bannerCleaned", "minecraft:clean_banner").put("stat.brewingstandInteraction", "minecraft:interact_with_brewingstand").put("stat.beaconInteraction", "minecraft:interact_with_beacon").put("stat.dropperInspected", "minecraft:inspect_dropper").put("stat.hopperInspected", "minecraft:inspect_hopper").put("stat.dispenserInspected", "minecraft:inspect_dispenser").put("stat.noteblockPlayed", "minecraft:play_noteblock").put("stat.noteblockTuned", "minecraft:tune_noteblock").put("stat.flowerPotted", "minecraft:pot_flower").put("stat.trappedChestTriggered", "minecraft:trigger_trapped_chest").put("stat.enderchestOpened", "minecraft:open_enderchest").put("stat.itemEnchanted", "minecraft:enchant_item").put("stat.recordPlayed", "minecraft:play_record").put("stat.furnaceInteraction", "minecraft:interact_with_furnace").put("stat.craftingTableInteraction", "minecraft:interact_with_crafting_table").put("stat.chestOpened", "minecraft:open_chest").put("stat.sleepInBed", "minecraft:sleep_in_bed").put("stat.shulkerBoxOpened", "minecraft:open_shulker_box").build(); ++ private static final Map ITEM_KEYS = ImmutableMap.builder().put("stat.craftItem", "minecraft:crafted").put("stat.useItem", "minecraft:used").put("stat.breakItem", "minecraft:broken").put("stat.pickup", "minecraft:picked_up").put("stat.drop", "minecraft:dropped").build(); ++ private static final Map ENTITY_KEYS = ImmutableMap.builder().put("stat.entityKilledBy", "minecraft:killed_by").put("stat.killEntity", "minecraft:killed").build(); ++ private static final Map ENTITIES = ImmutableMap.builder().put("Bat", "minecraft:bat").put("Blaze", "minecraft:blaze").put("CaveSpider", "minecraft:cave_spider").put("Chicken", "minecraft:chicken").put("Cow", "minecraft:cow").put("Creeper", "minecraft:creeper").put("Donkey", "minecraft:donkey").put("ElderGuardian", "minecraft:elder_guardian").put("Enderman", "minecraft:enderman").put("Endermite", "minecraft:endermite").put("EvocationIllager", "minecraft:evocation_illager").put("Ghast", "minecraft:ghast").put("Guardian", "minecraft:guardian").put("Horse", "minecraft:horse").put("Husk", "minecraft:husk").put("Llama", "minecraft:llama").put("LavaSlime", "minecraft:magma_cube").put("MushroomCow", "minecraft:mooshroom").put("Mule", "minecraft:mule").put("Ozelot", "minecraft:ocelot").put("Parrot", "minecraft:parrot").put("Pig", "minecraft:pig").put("PolarBear", "minecraft:polar_bear").put("Rabbit", "minecraft:rabbit").put("Sheep", "minecraft:sheep").put("Shulker", "minecraft:shulker").put("Silverfish", "minecraft:silverfish").put("SkeletonHorse", "minecraft:skeleton_horse").put("Skeleton", "minecraft:skeleton").put("Slime", "minecraft:slime").put("Spider", "minecraft:spider").put("Squid", "minecraft:squid").put("Stray", "minecraft:stray").put("Vex", "minecraft:vex").put("Villager", "minecraft:villager").put("VindicationIllager", "minecraft:vindication_illager").put("Witch", "minecraft:witch").put("WitherSkeleton", "minecraft:wither_skeleton").put("Wolf", "minecraft:wolf").put("ZombieHorse", "minecraft:zombie_horse").put("PigZombie", "minecraft:zombie_pigman").put("ZombieVillager", "minecraft:zombie_villager").put("Zombie", "minecraft:zombie").build(); ++ ++ public ConverterFlattenStats() { ++ super(MCVersions.V17W47A, 6); ++ } ++ ++ private static String upgradeItem(final String itemName) { ++ return ConverterFlattenItemStack.flattenItem(itemName, 0); ++ } ++ ++ private static String upgradeBlock(final String block) { ++ return HelperBlockFlatteningV1450.getNewBlockName(block); ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType stats = Types.NBT.createEmptyMap(); ++ ++ for (final String statKey : data.keys()) { ++ final Number value = data.getNumber(statKey); ++ if (value == null) { ++ continue; ++ } ++ ++ if (SKIP.contains(statKey)) { ++ continue; ++ } ++ ++ final String statType; ++ final String newStatKey; ++ ++ if (CUSTOM_MAP.containsKey(statKey)) { ++ statType = "minecraft:custom"; ++ newStatKey = CUSTOM_MAP.get(statKey); ++ } else { ++ final int i = StringUtils.ordinalIndexOf(statKey, ".", 2); ++ if (i < 0) { ++ continue; ++ } ++ ++ final String key = statKey.substring(0, i); ++ ++ if ("stat.mineBlock".equals(key)) { ++ statType = "minecraft:mined"; ++ newStatKey = upgradeBlock(statKey.substring(i + 1).replace('.', ':')); ++ } else if (ITEM_KEYS.containsKey(key)) { ++ statType = ITEM_KEYS.get(key); ++ final String item = statKey.substring(i + 1).replace('.', ':'); ++ final String upgradedItem = upgradeItem(item); ++ newStatKey = upgradedItem == null ? item : upgradedItem; ++ } else if (ENTITY_KEYS.containsKey(key)) { ++ statType = ENTITY_KEYS.get(key); ++ final String entity = statKey.substring(i + 1).replace('.', ':'); ++ newStatKey = ENTITIES.getOrDefault(entity, entity); ++ } else { ++ continue; ++ } ++ } ++ ++ MapType statTypeMap = stats.getMap(statType); ++ if (statTypeMap == null) { ++ stats.setMap(statType, statTypeMap = Types.NBT.createEmptyMap()); ++ } ++ ++ statTypeMap.setGeneric(newStatKey, value); ++ } ++ ++ data.clear(); ++ ++ data.setMap("stats", stats); ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b2c2b4c4ae83f14639fa53e38f2c75ccd284c2d2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java +@@ -0,0 +1,166 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++public class IDDataType extends MCDataType { ++ ++ protected final Map>>> walkersById = new HashMap<>(); ++ ++ public IDDataType(final String name) { ++ super(name); ++ } ++ ++ public void addConverterForId(final String id, final DataConverter, MapType> converter) { ++ this.addStructureConverter(new DataConverter<>(converter.getToVersion(), converter.getVersionStep()) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!id.equals(data.getString("id"))) { ++ return null; ++ } ++ return converter.convert(data, sourceVersion, toVersion); ++ } ++ }); ++ } ++ ++ public void addWalker(final int minVersion, final String id, final DataWalker walker) { ++ this.addWalker(minVersion, 0, id, walker); ++ } ++ ++ public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker walker) { ++ this.walkersById.computeIfAbsent(id, (final String keyInMap) -> { ++ return new Long2ObjectArraySortedMap<>(); ++ }).computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(walker); ++ } ++ ++ public void copyWalkers(final int minVersion, final String fromId, final String toId) { ++ this.copyWalkers(minVersion, 0, fromId, toId); ++ } ++ ++ public void copyWalkers(final int minVersion, final int versionStep, final String fromId, final String toId) { ++ final long version = DataConverter.encodeVersions(minVersion, versionStep); ++ final Long2ObjectArraySortedMap>> walkersForId = this.walkersById.get(fromId); ++ if (walkersForId == null) { ++ return; ++ } ++ ++ final List> nearest = walkersForId.getFloor(version); ++ ++ if (nearest == null) { ++ return; ++ } ++ ++ for (final DataWalker walker : nearest) { ++ this.addWalker(minVersion, versionStep, toId, walker); ++ } ++ } ++ ++ @Override ++ public MapType convert(MapType data, final long fromVersion, final long toVersion) { ++ MapType ret = null; ++ ++ final List, MapType>> converters = this.structureConverters; ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter, MapType> converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final MapType replace = converter.convert(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ } ++ ++ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); ++ ++ // run pre hooks ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ // run all walkers ++ ++ final List> walkers = this.structureWalkers.getFloor(toVersion); ++ if (walkers != null) { ++ for (int i = 0, len = walkers.size(); i < len; ++i) { ++ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final Long2ObjectArraySortedMap>> walkersByVersion = this.walkersById.get(data.getString("id")); ++ if (walkersByVersion != null) { ++ final List> walkersForId = walkersByVersion.getFloor(toVersion); ++ if (walkersForId != null) { ++ for (int i = 0, len = walkersForId.size(); i < len; ++i) { ++ final MapType replace = walkersForId.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ } ++ ++ // run post hooks ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..76a6e3efa5c69150e8f5e0063cb6357bed1bffae +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java +@@ -0,0 +1,129 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class MCDataType extends DataType, MapType> { ++ ++ public final String name; ++ ++ protected final ArrayList, MapType>> structureConverters = new ArrayList<>(); ++ protected final Long2ObjectArraySortedMap>> structureWalkers = new Long2ObjectArraySortedMap<>(); ++ protected final Long2ObjectArraySortedMap, MapType>>> structureHooks = new Long2ObjectArraySortedMap<>(); ++ ++ public MCDataType(final String name) { ++ this.name = name; ++ } ++ ++ public void addStructureConverter(final DataConverter, MapType> converter) { ++ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); ++ this.structureConverters.add(converter); ++ this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); ++ } ++ ++ public void addStructureWalker(final int minVersion, final DataWalker walker) { ++ this.addStructureWalker(minVersion, 0, walker); ++ } ++ ++ public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker walker) { ++ this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(walker); ++ } ++ ++ public void addStructureHook(final int minVersion, final DataHook, MapType> hook) { ++ this.addStructureHook(minVersion, 0, hook); ++ } ++ ++ public void addStructureHook(final int minVersion, final int versionStep, final DataHook, MapType> hook) { ++ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(hook); ++ } ++ ++ @Override ++ public MapType convert(MapType data, final long fromVersion, final long toVersion) { ++ MapType ret = null; ++ ++ final List, MapType>> converters = this.structureConverters; ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter, MapType> converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final MapType replace = converter.convert(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ } ++ ++ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ final List> walkers = this.structureWalkers.getFloor(toVersion); ++ if (walkers != null) { ++ for (int i = 0, len = walkers.size(); i < len; ++i) { ++ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); ++ if (replace != null) { ++ ret = data = replace; ++ } ++ } ++ } ++ ++ if (hooks != null) { ++ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { ++ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); ++ if (postReplace != null) { ++ ret = data = postReplace; ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..602c210df505613482bb4b1bbb782c0d8d6a32c8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java +@@ -0,0 +1,236 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.minecraft.versions.*; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++public final class MCTypeRegistry { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public static final MCDataType LEVEL = new MCDataType("Level"); ++ public static final MCDataType PLAYER = new MCDataType("Player"); ++ public static final MCDataType CHUNK = new MCDataType("Chunk"); ++ public static final MCDataType HOTBAR = new MCDataType("CreativeHotbar"); ++ public static final MCDataType OPTIONS = new MCDataType("Options"); ++ public static final MCDataType STRUCTURE = new MCDataType("Structure"); ++ public static final MCDataType STATS = new MCDataType("Stats"); ++ public static final MCDataType SAVED_DATA = new MCDataType("SavedData"); ++ public static final MCDataType ADVANCEMENTS = new MCDataType("Advancements"); ++ public static final MCDataType POI_CHUNK = new MCDataType("PoiChunk"); ++ public static final MCDataType ENTITY_CHUNK = new MCDataType("EntityChunk"); ++ public static final IDDataType TILE_ENTITY = new IDDataType("TileEntity"); ++ public static final IDDataType ITEM_STACK = new IDDataType("ItemStack"); ++ public static final MCDataType BLOCK_STATE = new MCDataType("BlockState"); ++ public static final MCValueType ENTITY_NAME = new MCValueType("EntityName"); ++ public static final IDDataType ENTITY = new IDDataType("Entity"); ++ public static final MCValueType BLOCK_NAME = new MCValueType("BlockName"); ++ public static final MCValueType ITEM_NAME = new MCValueType("ItemName"); ++ public static final MCDataType UNTAGGED_SPAWNER = new MCDataType("Spawner"); ++ public static final MCDataType STRUCTURE_FEATURE = new MCDataType("StructureFeature"); ++ public static final MCDataType OBJECTIVE = new MCDataType("Objective"); ++ public static final MCDataType TEAM = new MCDataType("Team"); ++ public static final MCValueType RECIPE = new MCValueType("RecipeName"); ++ public static final MCValueType BIOME = new MCValueType("Biome"); ++ public static final MCDataType WORLD_GEN_SETTINGS = new MCDataType("WorldGenSettings"); ++ public static final MCValueType GAME_EVENT_NAME = new MCValueType("GameEventName"); ++ ++ static { ++ try { ++ registerAll(); ++ } catch (final ThreadDeath thr) { ++ throw thr; ++ } catch (final Throwable thr) { ++ LOGGER.error(LogUtils.FATAL_MARKER, "Failed to register data converters", thr); ++ throw new RuntimeException(thr); ++ } ++ } ++ ++ private static void registerAll() { ++ // General notes: ++ // - Structure converters run before everything. ++ // - ID specific converters run after structure converters. ++ // - Structure walkers run after id specific converters. ++ // - ID specific walkers run after structure walkers. ++ ++ V99.register(); // all legacy data before converters existed ++ V100.register(); // first version with version id ++ V101.register(); ++ V102.register(); ++ V105.register(); ++ V106.register(); ++ V107.register(); ++ V108.register(); ++ V109.register(); ++ V110.register(); ++ V111.register(); ++ V113.register(); ++ V135.register(); ++ V143.register(); ++ V147.register(); ++ V165.register(); ++ V501.register(); ++ V502.register(); ++ V505.register(); ++ V700.register(); ++ V701.register(); ++ V702.register(); ++ V703.register(); ++ V704.register(); ++ V705.register(); ++ V804.register(); ++ V806.register(); ++ V808.register(); ++ V813.register(); ++ V816.register(); ++ V820.register(); ++ V1022.register(); ++ V1125.register(); ++ // END OF LEGACY DATA CONVERTERS ++ ++ // V1.13 ++ V1344.register(); ++ V1446.register(); ++ // START THE FLATTENING ++ V1450.register(); ++ V1451.register(); ++ // END THE FLATTENING ++ ++ V1456.register(); ++ V1458.register(); ++ V1460.register(); ++ V1466.register(); ++ V1470.register(); ++ V1474.register(); ++ V1475.register(); ++ V1480.register(); ++ // V1481 is adding simple block entity ++ V1483.register(); ++ V1484.register(); ++ V1486.register(); ++ V1487.register(); ++ V1488.register(); ++ V1490.register(); ++ V1492.register(); ++ V1494.register(); ++ V1496.register(); ++ V1500.register(); ++ V1501.register(); ++ V1502.register(); ++ V1506.register(); ++ V1510.register(); ++ V1514.register(); ++ V1515.register(); ++ V1624.register(); ++ // V1.14 ++ V1800.register(); ++ V1801.register(); ++ V1802.register(); ++ V1803.register(); ++ V1904.register(); ++ V1905.register(); ++ V1906.register(); ++ // V1909 is just adding a simple block entity (jigsaw) ++ V1911.register(); ++ V1914.register(); ++ V1917.register(); ++ V1918.register(); ++ V1920.register(); ++ V1925.register(); ++ V1928.register(); ++ V1929.register(); ++ V1931.register(); ++ V1936.register(); ++ V1946.register(); ++ V1948.register(); ++ V1953.register(); ++ V1955.register(); ++ V1961.register(); ++ V1963.register(); ++ // V1.15 ++ V2100.register(); ++ V2202.register(); ++ V2209.register(); ++ V2211.register(); ++ V2218.register(); ++ // V1.16 ++ V2501.register(); ++ V2502.register(); ++ V2503.register(); ++ V2505.register(); ++ V2508.register(); ++ V2509.register(); ++ V2511.register(); ++ V2514.register(); ++ V2516.register(); ++ V2518.register(); ++ V2519.register(); ++ V2522.register(); ++ V2523.register(); ++ V2527.register(); ++ V2528.register(); ++ V2529.register(); ++ V2531.register(); ++ V2533.register(); ++ V2535.register(); ++ V2550.register(); ++ V2551.register(); ++ V2552.register(); ++ V2553.register(); ++ V2558.register(); ++ V2568.register(); ++ // V1.17 ++ // WARN: Mojang registers V2671 under 2571, but that version predates 1.16.5? So it looks like a typo... ++ // I changed it to 2671, just so that it's after 1.16.5, but even then this looks misplaced... Thankfully this is ++ // the first datafixer, and all it does is add a walker, so I think even if the version here is just wrong it will ++ // work. ++ V2671.register(); ++ V2679.register(); ++ V2680.register(); ++ V2684.register(); ++ V2686.register(); ++ V2688.register(); ++ V2690.register(); ++ V2691.register(); ++ V2693.register(); ++ V2696.register(); ++ V2700.register(); ++ V2701.register(); ++ V2702.register(); ++ // In reference to V2671, why the fuck is goat being registered again? For this obvious reason, V2704 is absent. ++ V2707.register(); ++ V2710.register(); ++ V2717.register(); ++ // V1.18 ++ V2825.register(); ++ V2831.register(); ++ V2832.register(); ++ V2833.register(); ++ V2838.register(); ++ V2841.register(); ++ V2842.register(); ++ V2843.register(); ++ V2846.register(); ++ V2852.register(); ++ V2967.register(); ++ V2970.register(); ++ // V1.19 ++ // V3076 is registering a simple tile entity (sculk_catalyst) ++ V3077.register(); ++ V3078.register(); ++ V3081.register(); ++ V3082.register(); ++ V3083.register(); ++ V3084.register(); ++ V3086.register(); ++ V3087.register(); ++ V3088.register(); ++ V3090.register(); ++ V3093.register(); ++ V3094.register(); ++ V3097.register(); ++ V3108.register(); ++ } ++ ++ private MCTypeRegistry() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java +@@ -0,0 +1,86 @@ ++package ca.spottedleaf.dataconverter.minecraft.datatypes; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; ++import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class MCValueType extends DataType { ++ ++ public final String name; ++ ++ protected final ArrayList> converters = new ArrayList<>(); ++ protected final Long2ObjectArraySortedMap>> structureHooks = new Long2ObjectArraySortedMap<>(); ++ ++ public MCValueType(final String name) { ++ this.name = name; ++ } ++ ++ public void addStructureHook(final int minVersion, final DataHook hook) { ++ this.addStructureHook(minVersion, 0, hook); ++ } ++ ++ public void addStructureHook(final int minVersion, final int versionStep, final DataHook hook) { ++ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { ++ return new ArrayList<>(); ++ }).add(hook); ++ } ++ ++ public void addConverter(final DataConverter converter) { ++ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); ++ this.converters.add(converter); ++ this.converters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); ++ } ++ ++ @Override ++ public Object convert(final Object data, final long fromVersion, final long toVersion) { ++ Object ret = null; ++ final List> converters = this.converters; ++ ++ for (int i = 0, len = converters.size(); i < len; ++i) { ++ final DataConverter converter = converters.get(i); ++ final long converterVersion = converter.getEncodedVersion(); ++ ++ if (converterVersion <= fromVersion) { ++ continue; ++ } ++ ++ if (converterVersion > toVersion) { ++ break; ++ } ++ ++ List> hooks = this.structureHooks.getFloor(converterVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final Object replace = hooks.get(k).preHook(ret == null ? data : ret, fromVersion, toVersion); ++ if (replace != null) { ++ ret = replace; ++ } ++ } ++ } ++ ++ final Object converted = converter.convert(ret == null ? data : ret, fromVersion, toVersion); ++ if (converted != null) { ++ ret = converted; ++ } ++ ++ // possibly new data format, update hooks ++ hooks = this.structureHooks.getFloor(toVersion); ++ ++ if (hooks != null) { ++ for (int k = 0, klen = hooks.size(); k < klen; ++k) { ++ final Object replace = hooks.get(k).postHook(ret == null ? data : ret, fromVersion, toVersion); ++ if (replace != null) { ++ ret = replace; ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java +new file mode 100644 +index 0000000000000000000000000000000000000000..26a03007a4386ff037a1ae50045d0c44dd438235 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java +@@ -0,0 +1,35 @@ ++package ca.spottedleaf.dataconverter.minecraft.hooks; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public class DataHookEnforceNamespacedID implements DataHook, MapType> { ++ ++ private final String path; ++ ++ public DataHookEnforceNamespacedID() { ++ this("id"); ++ } ++ ++ public DataHookEnforceNamespacedID(final String path) { ++ this.path = path; ++ } ++ ++ @Override ++ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { ++ final String id = data.getString(this.path); ++ if (id != null) { ++ final String replace = NamespaceUtil.correctNamespaceOrNull(id); ++ if (replace != null) { ++ data.setString(this.path, replace); ++ } ++ } ++ return null; ++ } ++ ++ @Override ++ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.hooks; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public class DataHookValueTypeEnforceNamespaced implements DataHook { ++ ++ @Override ++ public Object preHook(final Object data, final long fromVersion, final long toVersion) { ++ if (data instanceof String) { ++ return NamespaceUtil.correctNamespaceOrNull((String)data); ++ } ++ return null; ++ } ++ ++ @Override ++ public Object postHook(final Object data, final long fromVersion, final long toVersion) { ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7e8f42eb57c12c885a1c17eafab1c9d9be4d8963 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java +@@ -0,0 +1,159 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V100 { ++ ++ protected static final int VERSION = MCVersions.V15W32A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType equipment = data.getList("Equipment", ObjectType.MAP); ++ data.remove("Equipment"); ++ ++ if (equipment != null) { ++ if (equipment.size() > 0 && data.getListUnchecked("HandItems") == null) { ++ final ListType handItems = Types.NBT.createEmptyList(); ++ data.setList("HandItems", handItems); ++ handItems.addMap(equipment.getMap(0)); ++ handItems.addMap(Types.NBT.createEmptyMap()); ++ } ++ ++ if (equipment.size() > 1 && data.getListUnchecked("ArmorItems") == null) { ++ final ListType armorItems = Types.NBT.createEmptyList(); ++ data.setList("ArmorItems", armorItems); ++ for (int i = 1; i < Math.min(equipment.size(), 5); ++i) { ++ armorItems.addMap(equipment.getMap(i)); ++ } ++ } ++ } ++ ++ final ListType dropChances = data.getList("DropChances", ObjectType.FLOAT); ++ data.remove("DropChances"); ++ ++ if (dropChances != null) { ++ if (data.getListUnchecked("HandDropChances") == null) { ++ final ListType handDropChances = Types.NBT.createEmptyList(); ++ data.setList("HandDropChances", handDropChances); ++ if (0 < dropChances.size()) { ++ handDropChances.addFloat(dropChances.getFloat(0)); ++ } else { ++ handDropChances.addFloat(0.0F); ++ } ++ handDropChances.addFloat(0.0F); ++ } ++ ++ if (data.getListUnchecked("ArmorDropChances") == null) { ++ final ListType armorDropChances = Types.NBT.createEmptyList(); ++ data.setList("ArmorDropChances", armorDropChances); ++ for (int i = 1; i < 5; ++i) { ++ if (i < dropChances.size()) { ++ armorDropChances.addFloat(dropChances.getFloat(i)); ++ } else { ++ armorDropChances.addFloat(0.0F); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("ArmorStand"); ++ registerMob("Creeper"); ++ registerMob("Skeleton"); ++ registerMob("Spider"); ++ registerMob("Giant"); ++ registerMob("Zombie"); ++ registerMob("Slime"); ++ registerMob("Ghast"); ++ registerMob("PigZombie"); ++ registerMob("Enderman"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); ++ registerMob("CaveSpider"); ++ registerMob("Silverfish"); ++ registerMob("Blaze"); ++ registerMob("LavaSlime"); ++ registerMob("EnderDragon"); ++ registerMob("WitherBoss"); ++ registerMob("Bat"); ++ registerMob("Witch"); ++ registerMob("Endermite"); ++ registerMob("Guardian"); ++ registerMob("Pig"); ++ registerMob("Sheep"); ++ registerMob("Cow"); ++ registerMob("Chicken"); ++ registerMob("Squid"); ++ registerMob("Wolf"); ++ registerMob("MushroomCow"); ++ registerMob("SnowMan"); ++ registerMob("Ozelot"); ++ registerMob("VillagerGolem"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ registerMob("Rabbit"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ registerMob("Shulker"); ++ ++ MCTypeRegistry.STRUCTURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType entities = data.getList("entities", ObjectType.MAP); ++ if (entities != null) { ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, entities.getMap(i), "nbt", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType blocks = data.getList("blocks", ObjectType.MAP); ++ if (blocks != null) { ++ for (int i = 0, len = blocks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.TILE_ENTITY, blocks.getMap(i), "nbt", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, data, "palette", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V100() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bcdca74d1ca7265afaa3fc0f6913ddfb07800377 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.gson.JsonParseException; ++import net.minecraft.network.chat.CommonComponents; ++import net.minecraft.network.chat.Component; ++import net.minecraft.util.GsonHelper; ++import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; ++ ++public final class V101 { ++ ++ protected static final int VERSION = MCVersions.V15W32A + 1; ++ ++ protected static void updateLine(final MapType data, final String path) { ++ final String textString = data.getString(path); ++ if (textString == null || textString.isEmpty() || "null".equals(textString)) { ++ data.setString(path, Component.Serializer.toJson(CommonComponents.EMPTY)); ++ return; ++ } ++ ++ Component component = null; ++ ++ if (textString.charAt(0) == '"' && textString.charAt(textString.length() - 1) == '"' ++ || textString.charAt(0) == '{' && textString.charAt(textString.length() - 1) == '}') { ++ try { ++ component = GsonHelper.fromJson(BlockEntitySignTextStrictJsonFix.GSON, textString, Component.class, true); ++ if (component == null) { ++ component = CommonComponents.EMPTY; ++ } ++ } catch (final JsonParseException ignored) {} ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJson(textString); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJsonLenient(textString); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ component = Component.literal(textString); ++ } ++ } else { ++ component = Component.literal(textString); ++ } ++ ++ data.setString(path, Component.Serializer.toJson(component)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("Sign", new DataConverter<>(VERSION) { ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLine(data, "Text1"); ++ updateLine(data, "Text2"); ++ updateLine(data, "Text3"); ++ updateLine(data, "Text4"); ++ return null; ++ } ++ }); ++ } ++ ++ private V101() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java +new file mode 100644 +index 0000000000000000000000000000000000000000..76374ca99efdf898dee0829fd8eb5fac26dc9a22 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java +@@ -0,0 +1,87 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++public final class V102 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32A + 2; ++ ++ public static void register() { ++ // V102 -> V15W32A + 2 ++ // V102 schema only modifies ITEM_STACK to have only a string ID, but our ITEM_NAME is generic (int or String) so we don't ++ // actually need to update the walker ++ ++ MCTypeRegistry.ITEM_NAME.addConverter(new DataConverter<>(VERSION) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ if (!(data instanceof Number)) { ++ return null; ++ } ++ final int id = ((Number)data).intValue(); ++ final String remap = HelperItemNameV102.getNameFromId(id); ++ if (remap == null) { ++ LOGGER.warn("Unknown legacy integer id (V102) " + id); ++ } ++ return remap == null ? HelperItemNameV102.getNameFromId(0) : remap; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("id", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ final int id = data.getInt("id"); ++ ++ String remap = HelperItemNameV102.getNameFromId(id); ++ if (remap == null) { ++ LOGGER.warn("Unknown legacy integer id (V102) " + id); ++ remap = HelperItemNameV102.getNameFromId(0); ++ } ++ ++ data.setString("id", remap); ++ ++ return null; ++ } ++ }); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final short damage = data.getShort("Damage"); ++ if (damage != 0) { ++ data.setShort("Damage", (short)0); ++ } ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("Potion", ObjectType.STRING)) { ++ final String converted = HelperItemNameV102.getPotionNameFromId(damage); ++ tag.setString("Potion", converted == null ? "minecraft:water" : converted); ++ if ((damage & 16384) == 16384) { ++ data.setString("id", "minecraft:splash_potion"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V102() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e251ead28d7d90937ae5871ffac489c1161e6e87 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1022 { ++ ++ protected static final int VERSION = MCVersions.V17W06A; ++ ++ public static void register() { ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle != null) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "EnderItems", fromVersion, toVersion); ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityLeft", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityRight", fromVersion, toVersion); ++ ++ final MapType recipeBook = data.getMap("recipeBook"); ++ if (recipeBook != null) { ++ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "recipes", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "toBeDisplayed", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.HOTBAR.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ for (final String key : data.keys()) { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, key, fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ } ++ ++ private V1022() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java +new file mode 100644 +index 0000000000000000000000000000000000000000..544f6a54041147a8c9ee3ff52c31c480a3696924 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperSpawnEggNameV105; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V105 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ } ++ ++ final short damage = data.getShort("Damage"); ++ if (damage != 0) { ++ data.setShort("Damage", (short)0); ++ } ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag == null) { ++ entityTag = Types.NBT.createEmptyMap(); ++ } ++ ++ if (!entityTag.hasKey("id", ObjectType.STRING)) { ++ final String converted = HelperSpawnEggNameV105.getSpawnNameFromId(damage); ++ if (converted != null) { ++ entityTag.setString("id", converted); ++ tag.setMap("EntityTag", entityTag); ++ data.setMap("tag", tag); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V105() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java +new file mode 100644 +index 0000000000000000000000000000000000000000..951838b0f4f2b4ed82d707706ef15d779f3f41eb +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java +@@ -0,0 +1,84 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V106 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 2; ++ ++ public static void register() { ++ // V106 -> V15W32C + 2 ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // While all converters for spawners check the id for this version, we don't because spawners exist in minecarts. ooops! Loading a chunk ++ // with a minecart spawner from 1.7.10 in 1.16.5 vanilla will fail to convert! Clearly there was a mistake in how they ++ // used and applied spawner converters. In anycase, do not check the id - we are not guaranteed to be a tile ++ // entity. We can be a regular old minecart spawner. And we know we are a spawner because this is only called from data walkers. ++ ++ final String entityId = data.getString("EntityId"); ++ if (entityId != null) { ++ data.remove("EntityId"); ++ MapType spawnData = data.getMap("SpawnData"); ++ if (spawnData == null) { ++ spawnData = Types.NBT.createEmptyMap(); ++ data.setMap("SpawnData", spawnData); ++ } ++ spawnData.setString("id", entityId.isEmpty() ? "Pig" : entityId); ++ } ++ ++ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ // convert to standard entity format (it's not a coincidence a walker for spawners is only added ++ // in this version) ++ final MapType spawn = spawnPotentials.getMap(i); ++ final String spawnType = spawn.getString("Type"); ++ if (spawnType == null) { ++ continue; ++ } ++ spawn.remove("Type"); ++ ++ MapType properties = spawn.getMap("Properties"); ++ if (properties == null) { ++ properties = Types.NBT.createEmptyMap(); ++ } else { ++ spawn.remove("Properties"); ++ } ++ ++ properties.setString("id", spawnType); ++ ++ spawn.setMap("Entity", properties); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential, "Entity", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "SpawnData", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V106() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aa8c8d22ee2a77604d923b62f5a93ede9b3f333f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V107 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 3; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Minecart", new DataConverter<>(VERSION) { ++ protected final String[] MINECART_IDS = new String[] { ++ "MinecartRideable", // 0 ++ "MinecartChest", // 1 ++ "MinecartFurnace", // 2 ++ "MinecartTNT", // 3 ++ "MinecartSpawner", // 4 ++ "MinecartHopper", // 5 ++ "MinecartCommandBlock" // 6 ++ }; ++ // Vanilla does not use all of the IDs here. The legacy (pre DFU) code does, so I'm going to use them. ++ // No harm in catching more cases here. ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ String newId = "MinecartRideable"; // dfl ++ final int type = data.getInt("Type"); ++ data.remove("Type"); ++ ++ if (type >= 0 && type < MINECART_IDS.length) { ++ newId = MINECART_IDS[type]; ++ } ++ data.setString("id", newId); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V107() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6bc4e2939bd26538492a7b94b743957d56ddc575 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java +@@ -0,0 +1,48 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++ ++import java.util.UUID; ++ ++public final class V108 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32C + 4; ++ ++ public static void register() { ++ // Convert String UUID into UUIDMost and UUIDLeast ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String uuidString = data.getString("UUID"); ++ ++ if (uuidString == null) { ++ return null; ++ } ++ data.remove("UUID"); ++ ++ final UUID uuid; ++ try { ++ uuid = UUID.fromString(uuidString); ++ } catch (final Exception ex) { ++ LOGGER.warn("Failed to parse UUID for legacy entity (V108): " + uuidString, ex); ++ return null; ++ } ++ ++ data.setLong("UUIDMost", uuid.getMostSignificantBits()); ++ data.setLong("UUIDLeast", uuid.getLeastSignificantBits()); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V108() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e0e52fa2d8988ca973f8a97b2374a8c3d4ef80c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java +@@ -0,0 +1,83 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.Sets; ++import java.util.Set; ++ ++public final class V109 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 5; ++ ++ // DFU declares this exact field but leaves it unused. Not sure why, legacy conversion system checked if the ID matched. ++ // I'm going to leave it here unused as well, just in case it's needed in the future. ++ protected static final Set ENTITIES = Sets.newHashSet( ++ "ArmorStand", ++ "Bat", ++ "Blaze", ++ "CaveSpider", ++ "Chicken", ++ "Cow", ++ "Creeper", ++ "EnderDragon", ++ "Enderman", ++ "Endermite", ++ "EntityHorse", ++ "Ghast", ++ "Giant", ++ "Guardian", ++ "LavaSlime", ++ "MushroomCow", ++ "Ozelot", ++ "Pig", ++ "PigZombie", ++ "Rabbit", ++ "Sheep", ++ "Shulker", ++ "Silverfish", ++ "Skeleton", ++ "Slime", ++ "SnowMan", ++ "Spider", ++ "Squid", ++ "Villager", ++ "VillagerGolem", ++ "Witch", ++ "WitherBoss", ++ "Wolf", ++ "Zombie" ++ ); ++ ++ public static void register() { ++ // Converts health to be in float, and cleans up whatever the hell was going on with HealF and Health... ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number healF = data.getNumber("HealF"); ++ final Number heal = data.getNumber("Health"); ++ ++ final float newHealth; ++ ++ if (healF != null) { ++ data.remove("HealF"); ++ newHealth = healF.floatValue(); ++ } else { ++ if (heal == null) { ++ return null; ++ } ++ ++ newHealth = heal.floatValue(); ++ } ++ ++ data.setFloat("Health", newHealth); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V109() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9771810a1f1cbf760fd9a8a5fd575f6052f40ea9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V110 { ++ ++ protected static final int VERSION = MCVersions.V15W32C + 6; ++ ++ public static void register() { ++ // Moves the Saddle boolean to be an actual saddle item. Note: The data walker for the SaddleItem exists ++ // in V99, it doesn't need to be added here. ++ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.getBoolean("Saddle") || data.hasKey("SaddleItem", ObjectType.MAP)) { ++ return null; ++ } ++ ++ final MapType saddleItem = Types.NBT.createEmptyMap(); ++ data.remove("Saddle"); ++ data.setMap("SaddleItem", saddleItem); ++ ++ saddleItem.setString("id", "minecraft:saddle"); ++ saddleItem.setByte("Count", (byte)1); ++ saddleItem.setShort("Damage", (short)0); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V110() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5bae7effda7761a3f2a0a2ce550d867cb2c18b99 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java +@@ -0,0 +1,67 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V111 { ++ ++ protected static final int VERSION = MCVersions.V15W33B; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Painting", new EntityRotationFix("Painting")); ++ MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", new EntityRotationFix("ItemFrame")); ++ } ++ ++ private V111() {} ++ ++ protected static final class EntityRotationFix extends DataConverter, MapType> { ++ ++ private static final int[][] DIRECTIONS = new int[][] { ++ {0, 0, 1}, ++ {-1, 0, 0}, ++ {0, 0, -1}, ++ {1, 0, 0} ++ }; ++ ++ protected final String id; ++ ++ public EntityRotationFix(final String id) { ++ super(VERSION); ++ this.id = id; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getNumber("Facing") != null) { ++ return null; ++ } ++ ++ final Number direction = data.getNumber("Direction"); ++ final int facing; ++ if (direction != null) { ++ data.remove("Direction"); ++ facing = direction.intValue() % DIRECTIONS.length; ++ final int[] offsets = DIRECTIONS[facing]; ++ data.setInt("TileX", data.getInt("TileX") + offsets[0]); ++ data.setInt("TileY", data.getInt("TileY") + offsets[1]); ++ data.setInt("TileZ", data.getInt("TileZ") + offsets[2]); ++ if ("ItemFrame".equals(data.getString("id"))) { ++ final Number rotation = data.getNumber("ItemRotation"); ++ if (rotation != null) { ++ data.setByte("ItemRotation", (byte)(rotation.byteValue() * 2)); ++ } ++ } ++ } else { ++ facing = data.getByte("Dir") % DIRECTIONS.length; ++ data.remove("Dir"); ++ } ++ ++ data.setByte("Facing", (byte)facing); ++ ++ return null; ++ } ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a6d3c28e8c97b53f388c03ccad3449390937d3b2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java +@@ -0,0 +1,102 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1125 { ++ ++ protected static final int VERSION = MCVersions.V17W15A; ++ protected static final int BED_BLOCK_ID = 416; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ if (tileEntities == null) { ++ tileEntities = Types.NBT.createEmptyList(); ++ level.setList("TileEntities", tileEntities); ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final byte sectionY = section.getByte("Y"); ++ final byte[] blocks = section.getBytes("Blocks"); ++ ++ if (blocks == null) { ++ continue; ++ } ++ ++ for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) { ++ if (BED_BLOCK_ID != ((blocks[blockIndex] & 255) << 4)) { ++ continue; ++ } ++ ++ final int localX = blockIndex & 15; ++ final int localZ = (blockIndex >> 4) & 15; ++ final int localY = (blockIndex >> 8) & 15; ++ ++ final MapType newTile = Types.NBT.createEmptyMap(); ++ newTile.setString("id", "minecraft:bed"); ++ newTile.setInt("x", localX + (chunkX << 4)); ++ newTile.setInt("y", localY + (sectionY << 4)); ++ newTile.setInt("z", localZ + (chunkZ << 4)); ++ newTile.setShort("color", (short)14); // Red ++ ++ tileEntities.addMap(newTile); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:bed", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getShort("Damage") == 0) { ++ data.setShort("Damage", (short)14); // Red ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ MCTypeRegistry.ADVANCEMENTS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertKeys(MCTypeRegistry.BIOME, data.getMap("minecraft:adventure/adventuring_time"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_a_mob"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_all_mobs"), "criteria", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/bred_all_animals"), "criteria", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ // Enforce namespacing for ids ++ MCTypeRegistry.BIOME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ } ++ ++ private V1125() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8b93bba2b084e20d346461f53d2f7662c3d6238b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java +@@ -0,0 +1,41 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V113 { ++ ++ protected static final int VERSION = MCVersions.V15W33C + 1; ++ ++ protected static void checkList(final MapType data, final String id, final int requiredLength) { ++ final ListType list = data.getList(id, ObjectType.FLOAT); ++ if (list != null && list.size() == requiredLength) { ++ for (int i = 0; i < requiredLength; ++i) { ++ if (list.getFloat(i) != 0.0F) { ++ return; ++ } ++ } ++ } ++ ++ data.remove(id); ++ } ++ ++ public static void register() { ++ // Removes "HandDropChances" and "ArmorDropChances" if they're empty. ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ checkList(data, "HandDropChances", 2); ++ checkList(data, "ArmorDropChances", 4); ++ return null; ++ } ++ }); ++ } ++ ++ private V113() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ac390a6111ba1a4aae3d5726747f60f4929fa254 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java +@@ -0,0 +1,177 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V1344 { ++ ++ protected static final int VERSION = MCVersions.V1_12_2 + 1; ++ ++ private static final Int2ObjectOpenHashMap BUTTON_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); ++ static { ++ BUTTON_ID_TO_NAME.put(0, "key.unknown"); ++ BUTTON_ID_TO_NAME.put(11, "key.0"); ++ BUTTON_ID_TO_NAME.put(2, "key.1"); ++ BUTTON_ID_TO_NAME.put(3, "key.2"); ++ BUTTON_ID_TO_NAME.put(4, "key.3"); ++ BUTTON_ID_TO_NAME.put(5, "key.4"); ++ BUTTON_ID_TO_NAME.put(6, "key.5"); ++ BUTTON_ID_TO_NAME.put(7, "key.6"); ++ BUTTON_ID_TO_NAME.put(8, "key.7"); ++ BUTTON_ID_TO_NAME.put(9, "key.8"); ++ BUTTON_ID_TO_NAME.put(10, "key.9"); ++ BUTTON_ID_TO_NAME.put(30, "key.a"); ++ BUTTON_ID_TO_NAME.put(40, "key.apostrophe"); ++ BUTTON_ID_TO_NAME.put(48, "key.b"); ++ BUTTON_ID_TO_NAME.put(43, "key.backslash"); ++ BUTTON_ID_TO_NAME.put(14, "key.backspace"); ++ BUTTON_ID_TO_NAME.put(46, "key.c"); ++ BUTTON_ID_TO_NAME.put(58, "key.caps.lock"); ++ BUTTON_ID_TO_NAME.put(51, "key.comma"); ++ BUTTON_ID_TO_NAME.put(32, "key.d"); ++ BUTTON_ID_TO_NAME.put(211, "key.delete"); ++ BUTTON_ID_TO_NAME.put(208, "key.down"); ++ BUTTON_ID_TO_NAME.put(18, "key.e"); ++ BUTTON_ID_TO_NAME.put(207, "key.end"); ++ BUTTON_ID_TO_NAME.put(28, "key.enter"); ++ BUTTON_ID_TO_NAME.put(13, "key.equal"); ++ BUTTON_ID_TO_NAME.put(1, "key.escape"); ++ BUTTON_ID_TO_NAME.put(33, "key.f"); ++ BUTTON_ID_TO_NAME.put(59, "key.f1"); ++ BUTTON_ID_TO_NAME.put(68, "key.f10"); ++ BUTTON_ID_TO_NAME.put(87, "key.f11"); ++ BUTTON_ID_TO_NAME.put(88, "key.f12"); ++ BUTTON_ID_TO_NAME.put(100, "key.f13"); ++ BUTTON_ID_TO_NAME.put(101, "key.f14"); ++ BUTTON_ID_TO_NAME.put(102, "key.f15"); ++ BUTTON_ID_TO_NAME.put(103, "key.f16"); ++ BUTTON_ID_TO_NAME.put(104, "key.f17"); ++ BUTTON_ID_TO_NAME.put(105, "key.f18"); ++ BUTTON_ID_TO_NAME.put(113, "key.f19"); ++ BUTTON_ID_TO_NAME.put(60, "key.f2"); ++ BUTTON_ID_TO_NAME.put(61, "key.f3"); ++ BUTTON_ID_TO_NAME.put(62, "key.f4"); ++ BUTTON_ID_TO_NAME.put(63, "key.f5"); ++ BUTTON_ID_TO_NAME.put(64, "key.f6"); ++ BUTTON_ID_TO_NAME.put(65, "key.f7"); ++ BUTTON_ID_TO_NAME.put(66, "key.f8"); ++ BUTTON_ID_TO_NAME.put(67, "key.f9"); ++ BUTTON_ID_TO_NAME.put(34, "key.g"); ++ BUTTON_ID_TO_NAME.put(41, "key.grave.accent"); ++ BUTTON_ID_TO_NAME.put(35, "key.h"); ++ BUTTON_ID_TO_NAME.put(199, "key.home"); ++ BUTTON_ID_TO_NAME.put(23, "key.i"); ++ BUTTON_ID_TO_NAME.put(210, "key.insert"); ++ BUTTON_ID_TO_NAME.put(36, "key.j"); ++ BUTTON_ID_TO_NAME.put(37, "key.k"); ++ BUTTON_ID_TO_NAME.put(82, "key.keypad.0"); ++ BUTTON_ID_TO_NAME.put(79, "key.keypad.1"); ++ BUTTON_ID_TO_NAME.put(80, "key.keypad.2"); ++ BUTTON_ID_TO_NAME.put(81, "key.keypad.3"); ++ BUTTON_ID_TO_NAME.put(75, "key.keypad.4"); ++ BUTTON_ID_TO_NAME.put(76, "key.keypad.5"); ++ BUTTON_ID_TO_NAME.put(77, "key.keypad.6"); ++ BUTTON_ID_TO_NAME.put(71, "key.keypad.7"); ++ BUTTON_ID_TO_NAME.put(72, "key.keypad.8"); ++ BUTTON_ID_TO_NAME.put(73, "key.keypad.9"); ++ BUTTON_ID_TO_NAME.put(78, "key.keypad.add"); ++ BUTTON_ID_TO_NAME.put(83, "key.keypad.decimal"); ++ BUTTON_ID_TO_NAME.put(181, "key.keypad.divide"); ++ BUTTON_ID_TO_NAME.put(156, "key.keypad.enter"); ++ BUTTON_ID_TO_NAME.put(141, "key.keypad.equal"); ++ BUTTON_ID_TO_NAME.put(55, "key.keypad.multiply"); ++ BUTTON_ID_TO_NAME.put(74, "key.keypad.subtract"); ++ BUTTON_ID_TO_NAME.put(38, "key.l"); ++ BUTTON_ID_TO_NAME.put(203, "key.left"); ++ BUTTON_ID_TO_NAME.put(56, "key.left.alt"); ++ BUTTON_ID_TO_NAME.put(26, "key.left.bracket"); ++ BUTTON_ID_TO_NAME.put(29, "key.left.control"); ++ BUTTON_ID_TO_NAME.put(42, "key.left.shift"); ++ BUTTON_ID_TO_NAME.put(219, "key.left.win"); ++ BUTTON_ID_TO_NAME.put(50, "key.m"); ++ BUTTON_ID_TO_NAME.put(12, "key.minus"); ++ BUTTON_ID_TO_NAME.put(49, "key.n"); ++ BUTTON_ID_TO_NAME.put(69, "key.num.lock"); ++ BUTTON_ID_TO_NAME.put(24, "key.o"); ++ BUTTON_ID_TO_NAME.put(25, "key.p"); ++ BUTTON_ID_TO_NAME.put(209, "key.page.down"); ++ BUTTON_ID_TO_NAME.put(201, "key.page.up"); ++ BUTTON_ID_TO_NAME.put(197, "key.pause"); ++ BUTTON_ID_TO_NAME.put(52, "key.period"); ++ BUTTON_ID_TO_NAME.put(183, "key.print.screen"); ++ BUTTON_ID_TO_NAME.put(16, "key.q"); ++ BUTTON_ID_TO_NAME.put(19, "key.r"); ++ BUTTON_ID_TO_NAME.put(205, "key.right"); ++ BUTTON_ID_TO_NAME.put(184, "key.right.alt"); ++ BUTTON_ID_TO_NAME.put(27, "key.right.bracket"); ++ BUTTON_ID_TO_NAME.put(157, "key.right.control"); ++ BUTTON_ID_TO_NAME.put(54, "key.right.shift"); ++ BUTTON_ID_TO_NAME.put(220, "key.right.win"); ++ BUTTON_ID_TO_NAME.put(31, "key.s"); ++ BUTTON_ID_TO_NAME.put(70, "key.scroll.lock"); ++ BUTTON_ID_TO_NAME.put(39, "key.semicolon"); ++ BUTTON_ID_TO_NAME.put(53, "key.slash"); ++ BUTTON_ID_TO_NAME.put(57, "key.space"); ++ BUTTON_ID_TO_NAME.put(20, "key.t"); ++ BUTTON_ID_TO_NAME.put(15, "key.tab"); ++ BUTTON_ID_TO_NAME.put(22, "key.u"); ++ BUTTON_ID_TO_NAME.put(200, "key.up"); ++ BUTTON_ID_TO_NAME.put(47, "key.v"); ++ BUTTON_ID_TO_NAME.put(17, "key.w"); ++ BUTTON_ID_TO_NAME.put(45, "key.x"); ++ BUTTON_ID_TO_NAME.put(21, "key.y"); ++ BUTTON_ID_TO_NAME.put(44, "key.z"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ for (final String key : data.keys()) { ++ if (!key.startsWith("key_")) { ++ continue; ++ } ++ final String value = data.getString(key); ++ final int code; ++ try { ++ code = Integer.parseInt(value); ++ } catch (final NumberFormatException ex) { ++ continue; ++ } ++ ++ final String newEntry; ++ ++ if (code < 0) { ++ final int mouseCode = code + 100; ++ switch (mouseCode) { ++ case 0: ++ newEntry = "key.mouse.left"; ++ break; ++ case 1: ++ newEntry = "key.mouse.right"; ++ break; ++ case 2: ++ newEntry = "key.mouse.middle"; ++ break; ++ default: ++ newEntry = "key.mouse." + (mouseCode + 1); ++ break; ++ } ++ } else { ++ newEntry = BUTTON_ID_TO_NAME.getOrDefault(code, "key.unknown"); ++ } ++ ++ // No CMEs occur for existing entries in maps. ++ data.setString(key, newEntry); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V1344() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java +new file mode 100644 +index 0000000000000000000000000000000000000000..764a56fda5ee909ac47a0c1b3b581c8c26deb591 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java +@@ -0,0 +1,61 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V135 { ++ ++ protected static final int VERSION = MCVersions.V15W40B + 1; ++ ++ public static void register() { ++ // In this update they changed the "Riding" value to be "Passengers", which is now a list. So it added ++ // support for multiple entities riding. Of course, Riding and Passenger are opposites - so it also will ++ // switch the data layout to be from highest rider to lowest rider, in terms of depth. ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(MapType data, final long sourceVersion, final long toVersion) { ++ MapType ret = null; ++ while (data.hasKey("Riding", ObjectType.MAP)) { ++ final MapType riding = data.getMap("Riding"); ++ data.remove("Riding"); ++ ++ final ListType passengers = Types.NBT.createEmptyList(); ++ riding.setList("Passengers", passengers); ++ passengers.addMap(data); ++ ++ ret = data = riding; ++ } ++ ++ return ret; ++ } ++ }); ++ ++ ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle != null) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Passengers", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ } ++ ++ private V135() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java +new file mode 100644 +index 0000000000000000000000000000000000000000..451e837349e10f1e76ac7d9f5d49cbe0ff630f4d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++ ++public final class V143 { ++ ++ protected static final int VERSION = MCVersions.V15W44B; ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, (final String input) -> { ++ return "TippedArrow".equals(input) ? "Arrow" : null; ++ }); ++ } ++ ++ private V143() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fc0ece569baed94bbf3cbbaa21a397fdc37e51e8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1446 { ++ ++ protected static final int VERSION = MCVersions.V17W43B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ for (final String key : data.keys()) { ++ if (!key.startsWith("key_")) { ++ continue; ++ } ++ ++ final String value = data.getString(key); ++ ++ if (value.startsWith("key.mouse") || value.startsWith("scancode.")) { ++ continue; ++ } ++ ++ data.setString(key, "key.keyboard." + value.substring("key.".length())); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V1446() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java +new file mode 100644 +index 0000000000000000000000000000000000000000..711222cd33ee557b7f3d1f6ae73ad45d1caf6768 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1450 { ++ ++ protected static final int VERSION = MCVersions.V17W46A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType ret = HelperBlockFlatteningV1450.flattenNBT(data); ++ return ret == data ? null : ret.copy(); // copy to avoid problems with later state datafixers ++ } ++ }); ++ } ++ ++ private V1450() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a1f270f55617e1608bf7821951fa589e611b6f7d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java +@@ -0,0 +1,512 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterFlattenChunk; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterFlattenStats; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterFlattenEntity; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.base.Splitter; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.util.datafix.fixes.BlockStateData; ++import net.minecraft.util.datafix.fixes.EntityBlockStateFix; ++import org.apache.commons.lang3.math.NumberUtils; ++import java.util.Iterator; ++import java.util.List; ++import java.util.stream.Collectors; ++import java.util.stream.StreamSupport; ++ ++public final class V1451 { ++ ++ protected static final int VERSION = MCVersions.V17W47A; ++ ++ public static void register() { ++ // V0 ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 0, "minecraft:trapped_chest", new DataWalkerItemLists("Items")); ++ ++ // V1 ++ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterFlattenChunk()); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, 1, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ ++ // V2 ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:piston", new DataConverter<>(VERSION, 2) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int blockId = data.getInt("blockId"); ++ final int blockData = data.getInt("blockData") & 15; ++ ++ data.remove("blockId"); ++ data.remove("blockData"); ++ ++ data.setMap("blockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 2, "minecraft:piston", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "blockState")); ++ ++ // V3 ++ ConverterFlattenEntity.register(); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:filled_map", new DataConverter<>(VERSION, 3) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("map", ObjectType.NUMBER)) { // This if is from CB. as usual, no documentation from CB. I'm guessing it just wants to avoid possibly overwriting it. seems fine. ++ tag.setInt("map", data.getInt("Damage")); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:potion", new DataWalkerItems("Potion")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "carriedBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "BlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spectral_arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:commandblock_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:furnace_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:tnt_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); ++ ++ // V4 ++ MCTypeRegistry.BLOCK_NAME.addConverter(new DataConverter<>(VERSION, 4) { ++ @Override ++ public Object convert(final Object data, final long sourceVersion, final long toVersion) { ++ if (data instanceof Number) { ++ return HelperBlockFlatteningV1450.getNameForId(((Number)data).intValue()); ++ } else if (data instanceof String) { ++ return HelperBlockFlatteningV1450.getNewBlockName((String)data); // structure hook ensured data is namespaced ++ } ++ return null; ++ } ++ }); ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new ConverterFlattenItemStack()); ++ ++ // V5 ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new ConverterFlattenSpawnEgg()); ++ /* This datafixer has been disabled because the collar colour handler did not change from 1.12 -> 1.13 at all. ++ // So clearly somebody fucked up. This fixes wolf colours incorrectly converting between versions ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:wolf", new DataConverter<>(VERSION, 5) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number colour = data.getNumber("CollarColor"); ++ ++ if (colour != null) { ++ data.setByte("CollarColor", (byte)(15 - colour.intValue())); ++ } ++ ++ return null; ++ } ++ }); ++ */ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION, 5) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number base = data.getNumber("Base"); ++ if (base != null) { ++ data.setInt("Base", 15 - base.intValue()); ++ } ++ ++ final ListType patterns = data.getList("Patterns", ObjectType.MAP); ++ if (patterns != null) { ++ for (int i = 0, len = patterns.size(); i < len; ++i) { ++ final MapType pattern = patterns.getMap(i); ++ final Number colour = pattern.getNumber("Color"); ++ if (colour != null) { ++ pattern.setInt("Color", 15 - colour.intValue()); ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION, 5) { ++ private final Splitter SPLITTER = Splitter.on(';').limit(5); ++ private final Splitter LAYER_SPLITTER = Splitter.on(','); ++ private final Splitter OLD_AMOUNT_SPLITTER = Splitter.on('x').limit(2); ++ private final Splitter AMOUNT_SPLITTER = Splitter.on('*').limit(2); ++ private final Splitter BLOCK_SPLITTER = Splitter.on(':').limit(3); ++ ++ // idk man i just copy and pasted this one ++ private String fixGeneratorSettings(final String generatorSettings) { ++ if (generatorSettings.isEmpty()) { ++ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; ++ } else { ++ Iterator iterator = SPLITTER.split(generatorSettings).iterator(); ++ String string2 = (String)iterator.next(); ++ int j; ++ String string4; ++ if (iterator.hasNext()) { ++ j = NumberUtils.toInt(string2, 0); ++ string4 = (String)iterator.next(); ++ } else { ++ j = 0; ++ string4 = string2; ++ } ++ ++ if (j >= 0 && j <= 3) { ++ StringBuilder stringBuilder = new StringBuilder(); ++ Splitter splitter = j < 3 ? OLD_AMOUNT_SPLITTER : AMOUNT_SPLITTER; ++ stringBuilder.append((String) StreamSupport.stream(LAYER_SPLITTER.split(string4).spliterator(), false).map((stringx) -> { ++ List list = splitter.splitToList(stringx); ++ int k; ++ String string3; ++ if (list.size() == 2) { ++ k = NumberUtils.toInt((String)list.get(0)); ++ string3 = (String)list.get(1); ++ } else { ++ k = 1; ++ string3 = (String)list.get(0); ++ } ++ ++ List list2 = BLOCK_SPLITTER.splitToList(string3); ++ int l = ((String)list2.get(0)).equals("minecraft") ? 1 : 0; ++ String string5 = (String)list2.get(l); ++ int m = j == 3 ? EntityBlockStateFix.getBlockId("minecraft:" + string5) : NumberUtils.toInt(string5, 0); ++ int n = l + 1; ++ int o = list2.size() > n ? NumberUtils.toInt((String)list2.get(n), 0) : 0; ++ return (k == 1 ? "" : k + "*") + BlockStateData.getTag(m << 4 | o).get("Name").asString(""); ++ }).collect(Collectors.joining(","))); ++ ++ while(iterator.hasNext()) { ++ stringBuilder.append(';').append((String)iterator.next()); ++ } ++ ++ return stringBuilder.toString(); ++ } else { ++ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; ++ } ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"flat".equalsIgnoreCase(data.getString("generatorName"))) { ++ return null; ++ } ++ ++ final String generatorOptions = data.getString("generatorOptions"); ++ if (generatorOptions == null) { ++ return null; ++ } ++ ++ data.setString("generatorOptions", this.fixGeneratorSettings(generatorOptions)); ++ ++ return null; ++ } ++ }); ++ ++ // V6 ++ MCTypeRegistry.STATS.addStructureConverter(new ConverterFlattenStats()); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jukebox", new DataConverter<>(VERSION, 6) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int record = data.getInt("Record"); ++ if (record <= 0) { ++ return null; ++ } ++ ++ data.remove("Record"); ++ ++ final String newItemId = ConverterFlattenItemStack.flattenItem(HelperItemNameV102.getNameFromId(record), 0); ++ if (newItemId == null) { ++ return null; ++ } ++ ++ final MapType recordItem = Types.NBT.createEmptyMap(); ++ data.setMap("RecordItem", recordItem); ++ ++ recordItem.setString("id", newItemId); ++ recordItem.setByte("Count", (byte)1); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STATS.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType stats = data.getMap("stats"); ++ if (stats == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.BLOCK_NAME, stats, "minecraft:mined", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:crafted", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:used", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:broken", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:picked_up", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:dropped", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed", fromVersion, toVersion); ++ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed_by", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureHook(VERSION, 6, new DataHook<>() { ++ private static String packWithDot(final String string) { ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(string); ++ return resourceLocation != null ? resourceLocation.getNamespace() + "." + resourceLocation.getPath() : string; ++ } ++ ++ @Override ++ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { ++ // unpack ++ final String criteriaName = data.getString("CriteriaName"); ++ String type; ++ String id; ++ ++ if (criteriaName != null) { ++ final int index = criteriaName.indexOf(':'); ++ if (index < 0) { ++ type = "_special"; ++ id = criteriaName; ++ } else { ++ try { ++ type = ResourceLocation.of(criteriaName.substring(0, index), '.').toString(); ++ id = ResourceLocation.of(criteriaName.substring(index + 1), '.').toString(); ++ } catch (final Exception ex) { ++ type = "_special"; ++ id = criteriaName; ++ } ++ } ++ } else { ++ type = null; ++ id = null; ++ } ++ ++ if (type != null && id != null) { ++ final MapType criteriaType = Types.NBT.createEmptyMap(); ++ data.setMap("CriteriaType", criteriaType); ++ ++ criteriaType.setString("type", type); ++ criteriaType.setString("id", id); ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { ++ // repack ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ ++ final String newName; ++ if (criteriaType == null) { ++ newName = null; ++ } else { ++ final String type = criteriaType.getString("type"); ++ final String id = criteriaType.getString("id"); ++ if (type != null && id != null) { ++ if ("_special".equals(type)) { ++ newName = id; ++ } else { ++ newName = packWithDot(type) + ":" + packWithDot(id); ++ } ++ } else { ++ newName = null; ++ } ++ } ++ ++ if (newName != null) { ++ data.remove("CriteriaType"); ++ data.setString("CriteriaName", newName); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType criteriaType = data.getMap("CriteriaType"); ++ if (criteriaType == null) { ++ return null; ++ } ++ ++ final String type = criteriaType.getString("type"); ++ ++ if (type == null) { ++ return null; ++ } ++ ++ switch (type) { ++ case "minecraft:mined": { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:crafted": ++ case "minecraft:used": ++ case "minecraft:broken": ++ case "minecraft:picked_up": ++ case "minecraft:dropped": { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:killed": ++ case "minecraft:killed_by": { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY_NAME, criteriaType, "id", fromVersion, toVersion); ++ break; ++ } ++ } ++ ++ return null; ++ }); ++ ++ ++ // V7 ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION, 7) { ++ private static void convertToBlockState(final MapType data, final String path) { ++ final Number number = data.getNumber(path); ++ if (number == null) { ++ return; ++ } ++ ++ data.setMap(path, HelperBlockFlatteningV1450.getNBTForId(number.intValue() << 4).copy()); // copy to avoid problems with later state datafixers ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ if (children == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ final String id = child.getString("id"); ++ ++ switch (id) { ++ case "ViF": ++ convertToBlockState(child, "CA"); ++ convertToBlockState(child, "CB"); ++ break; ++ case "ViDF": ++ convertToBlockState(child, "CA"); ++ convertToBlockState(child, "CB"); ++ convertToBlockState(child, "CC"); ++ convertToBlockState(child, "CD"); ++ break; ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ // convert villagers to trade with pumpkins and not the carved pumpkin ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION, 7) { ++ private static void convertPumpkin(final MapType data, final String path) { ++ final MapType item = data.getMap(path); ++ if (item == null) { ++ return; ++ } ++ ++ final String id = item.getString("id"); ++ ++ if (id.equals("minecraft:carved_pumpkin")) { ++ item.setString("id", "minecraft:pumpkin"); ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ ++ convertPumpkin(recipe, "buy"); ++ convertPumpkin(recipe, "buyB"); ++ convertPumpkin(recipe, "sell"); ++ } ++ } ++ } ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, 7, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType list = data.getList("Children", ObjectType.MAP); ++ if (list == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType child = list.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); ++ } ++ ++ return null; ++ }); ++ } ++ ++ private V1451() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8ca5b9d7292ba9c81f7f0fdfb6ca8fd17f796990 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1456 { ++ ++ protected static final int VERSION = MCVersions.V17W49B + 1; ++ ++ protected static byte direction2dTo3d(final byte old) { ++ switch (old) { ++ case 0: ++ return 3; ++ case 1: ++ return 4; ++ case 2: ++ default: ++ return 2; ++ case 3: ++ return 5; ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item_frame", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setByte("Facing", direction2dTo3d(data.getByte("Facing"))); ++ return null; ++ } ++ }); ++ } ++ ++ private V1456() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1de3a47887f29134e3e0ae6467afb54c8ab7ef68 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java +@@ -0,0 +1,88 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import net.minecraft.network.chat.Component; ++ ++public final class V1458 { ++ ++ protected static final int VERSION = MCVersions.V17W50A + 1; ++ ++ public static MapType updateCustomName(final MapType data) { ++ final String customName = data.getString("CustomName", ""); ++ ++ if (customName.isEmpty()) { ++ data.remove("CustomName"); ++ } else { ++ data.setString("CustomName", Component.Serializer.toJson(Component.literal(customName))); ++ } ++ ++ return null; ++ } ++ ++ public static void register() { ++ // From CB ++ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if ("minecraft:commandblock_minecart".equals(data.getString("id"))) { ++ return null; ++ } ++ ++ return updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ if (display == null) { ++ return null; ++ } ++ ++ final String name = display.getString("Name"); ++ if (name != null) { ++ display.setString("Name", Component.Serializer.toJson(Component.literal(name))); ++ } else { ++ final String localisedName = display.getString("LocName"); ++ if (localisedName != null) { ++ display.setString("Name", Component.Serializer.toJson(Component.translatable(localisedName))); ++ display.remove("LocName"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if ("minecraft:command_block".equals(data.getString("id"))) { ++ return null; ++ } ++ ++ return updateCustomName(data); ++ } ++ }); ++ ++ } ++ ++ private V1458() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f68b561b2bb750d5f632f17e538337fa38108472 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import net.minecraft.resources.ResourceLocation; ++import java.util.HashMap; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V1460 { ++ ++ private static final Map MOTIVE_REMAP = new HashMap<>(); ++ ++ static { ++ MOTIVE_REMAP.put("donkeykong", "donkey_kong"); ++ MOTIVE_REMAP.put("burningskull", "burning_skull"); ++ MOTIVE_REMAP.put("skullandroses", "skull_and_roses"); ++ }; ++ ++ protected static final int VERSION = MCVersions.V18W01A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private static void registerThrowableProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ String motive = data.getString("Motive"); ++ if (motive != null) { ++ motive = motive.toLowerCase(Locale.ROOT); ++ data.setString("Motive", new ResourceLocation(MOTIVE_REMAP.getOrDefault(motive, motive)).toString()); ++ } ++ return null; ++ } ++ }); ++ ++ // No idea why so many type redefines exist here in Vanilla. nothing about the data structure changed, it's literally a copy of ++ // the existing types. ++ } ++ ++ private V1460() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c4fa8e36fb68a610106cee8bae1af243e51fae2e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java +@@ -0,0 +1,143 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1466 { ++ ++ protected static final int VERSION = MCVersions.V18W06A; ++ ++ protected static short packOffsetCoordinates(final int x, final int y, final int z) { ++ return (short)((x & 15) | ((y & 15) << 4) | ((z & 15) << 8)); ++ } ++ ++ public static void register() { ++ // There is a rather critical change I've made to this converter: changing the chunk status determination. ++ // In Vanilla, this is determined by whether the terrain has been populated and whether the chunk is lit. ++ // For reference, here is the full status progression (at the time of 18w06a): ++ // empty -> base -> carved -> decorated -> lighted -> mobs_spawned -> finalized -> fullchunk -> postprocessed ++ // So one of those must be picked. ++ // If the chunk is lit and terrain is populated, the Vanilla converter will set the status to "mobs_spawned." ++ // If it is anything else, it will be "empty" ++ // I've changed it to the following: if terrain is populated, it is set to at least decorated. If it is populated ++ // and lit, it is set to "mobs_spawned" ++ // But what if it is not populated? If it is not populated, ignore the lit field - obviously that's just broken. ++ // It can't be lit and not populated. ++ // Let's take a look at chunk generation logic for a chunk that is not populated, or even near a populated chunk. ++ // It actually will generate a chunk up to the "carved" stage. It generates the base terrain, (i.e using noise ++ // to figure out where stone is, dirt, grass) and it will generate caves. Nothing else though. No populators. ++ // So "carved" is the correct stage to use, not empty. Setting it to empty would clobber chunk data, when we don't ++ // need to. If it is populated, at least set it to decorated. If it is lit and populated, set it to mobs_spawned. Else, ++ // it is carved. ++ // This change also fixes the random light check "bug" (really this is Mojang's fault for fucking up the status conversion here) ++ // caused by spigot, which would not set the lit value for some chunks. Now those chunks will not be regenerated. ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final boolean terrainPopulated = level.getBoolean("TerrainPopulated"); ++ final boolean lightPopulated = level.getBoolean("LightPopulated") || level.getNumber("LightPopulated") == null; ++ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated"); ++ ++ level.setString("Status", newStatus); ++ level.setBoolean("hasLegacyStructureData", true); ++ ++ // convert biome byte[] into int[] ++ final byte[] biomes = level.getBytes("Biomes"); ++ if (biomes != null) { ++ final int[] newBiomes = new int[256]; ++ for (int i = 0, len = Math.min(newBiomes.length, biomes.length); i < len; ++i) { ++ newBiomes[i] = biomes[i] & 255; ++ } ++ level.setInts("Biomes", newBiomes); ++ } ++ ++ // ProtoChunks have their own dedicated tick list, so we must convert the TileTicks to that. ++ final ListType ticks = level.getList("TileTicks", ObjectType.MAP); ++ if (ticks != null) { ++ final ListType sections = Types.NBT.createEmptyList(); ++ final ListType[] sectionAccess = new ListType[16]; ++ for (int i = 0; i < sectionAccess.length; ++i) { ++ sections.addList(sectionAccess[i] = Types.NBT.createEmptyList()); ++ } ++ level.setList("ToBeTicked", sections); ++ ++ for (int i = 0, len = ticks.size(); i < len; ++i) { ++ final MapType tick = ticks.getMap(i); ++ ++ final int x = tick.getInt("x"); ++ final int y = tick.getInt("y"); ++ final int z = tick.getInt("z"); ++ final short coordinate = packOffsetCoordinates(x, y, z); ++ ++ sectionAccess[y >> 4].addShort(coordinate); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final ListType list = data.getList("Children", ObjectType.MAP); ++ if (list != null) { ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType child = list.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, data, "biome", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private V1466() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java +new file mode 100644 +index 0000000000000000000000000000000000000000..68dd3ce7709a998bc50a5080fe9c805b71a88365 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V147 { ++ ++ protected static final int VERSION = MCVersions.V15W46A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("ArmorStand", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("Silent") && !data.getBoolean("Marker")) { ++ data.remove("Silent"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V147() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java +new file mode 100644 +index 0000000000000000000000000000000000000000..669509286b18a173826938bae347c1aefffeed51 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java +@@ -0,0 +1,31 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V1470 { ++ ++ protected static final int VERSION = MCVersions.V18W08A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:turtle"); ++ registerMob("minecraft:cod_mob"); ++ registerMob("minecraft:tropical_fish"); ++ registerMob("minecraft:salmon_mob"); ++ registerMob("minecraft:puffer_fish"); ++ registerMob("minecraft:phantom"); ++ registerMob("minecraft:dolphin"); ++ registerMob("minecraft:drowned"); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); ++ } ++ ++ private V1470() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4cf1085b4392c9b348ebe65590cdbf287a908a38 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1474 { ++ ++ protected static final int VERSION = MCVersions.V18W10B; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getInt("Color") == 10) { ++ data.setByte("Color", (byte)16); ++ } ++ return null; ++ } ++ }); ++ // data hooks ensure the inputs are namespaced ++ ConverterAbstractBlockRename.register(VERSION, (final String old) -> { ++ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; ++ }); ++ ConverterAbstractItemRename.register(VERSION, (final String old) -> { ++ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; ++ }); ++ ++ } ++ ++ private V1474() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java +new file mode 100644 +index 0000000000000000000000000000000000000000..40b64efb8717b2de0ff13af87bcc99119c9f7c9d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1475 { ++ ++ protected static final int VERSION = MCVersions.V18W10B + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ++ ImmutableMap.of( ++ "minecraft:flowing_water", "minecraft:water", ++ "minecraft:flowing_lava", "minecraft:lava" ++ )::get); ++ } ++ ++ private V1475() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e5373d4e6ca027749f634e9a508bd81b9b41ed3e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1480 { ++ ++ protected static final int VERSION = MCVersions.V18W14A + 1; ++ ++ public static final Map RENAMED_IDS = ImmutableMap.builder() ++ .put("minecraft:blue_coral", "minecraft:tube_coral_block") ++ .put("minecraft:pink_coral", "minecraft:brain_coral_block") ++ .put("minecraft:purple_coral", "minecraft:bubble_coral_block") ++ .put("minecraft:red_coral", "minecraft:fire_coral_block") ++ .put("minecraft:yellow_coral", "minecraft:horn_coral_block") ++ .put("minecraft:blue_coral_plant", "minecraft:tube_coral") ++ .put("minecraft:pink_coral_plant", "minecraft:brain_coral") ++ .put("minecraft:purple_coral_plant", "minecraft:bubble_coral") ++ .put("minecraft:red_coral_plant", "minecraft:fire_coral") ++ .put("minecraft:yellow_coral_plant", "minecraft:horn_coral") ++ .put("minecraft:blue_coral_fan", "minecraft:tube_coral_fan") ++ .put("minecraft:pink_coral_fan", "minecraft:brain_coral_fan") ++ .put("minecraft:purple_coral_fan", "minecraft:bubble_coral_fan") ++ .put("minecraft:red_coral_fan", "minecraft:fire_coral_fan") ++ .put("minecraft:yellow_coral_fan", "minecraft:horn_coral_fan") ++ .put("minecraft:blue_dead_coral", "minecraft:dead_tube_coral") ++ .put("minecraft:pink_dead_coral", "minecraft:dead_brain_coral") ++ .put("minecraft:purple_dead_coral", "minecraft:dead_bubble_coral") ++ .put("minecraft:red_dead_coral", "minecraft:dead_fire_coral") ++ .put("minecraft:yellow_dead_coral", "minecraft:dead_horn_coral") ++ .build(); ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_IDS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_IDS::get); ++ } ++ ++ private V1480() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e6fb5d6870514a509f7f1aa5343ed7e762af8a72 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java +@@ -0,0 +1,31 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1483 { ++ ++ protected static final int VERSION = MCVersions.V18W16A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:puffer_fish", "minecraft:pufferfish" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg" ++ )::get); ++ ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:puffer_fish", "minecraft:pufferfish"); ++ } ++ ++ private V1483() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5b9c166304930e095bfc00e8f6b93edb706df48 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java +@@ -0,0 +1,73 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1484 { ++ ++ protected static final int VERSION = MCVersions.V18W19A; ++ ++ public static void register() { ++ final Map renamed = ImmutableMap.of( ++ "minecraft:sea_grass", "minecraft:seagrass", ++ "minecraft:tall_sea_grass", "minecraft:tall_seagrass" ++ ); ++ ++ ConverterAbstractItemRename.register(VERSION, renamed::get); ++ ConverterAbstractBlockRename.register(VERSION, renamed::get); ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType heightmaps = level.getMap("Heightmaps"); ++ ++ if (heightmaps == null) { ++ return null; ++ } ++ ++ final Object liquid = heightmaps.getGeneric("LIQUID"); ++ if (liquid != null) { ++ heightmaps.remove("LIQUID"); ++ heightmaps.setGeneric("WORLD_SURFACE_WG", liquid); ++ } ++ ++ final Object solid = heightmaps.getGeneric("SOLID"); ++ if (solid != null) { ++ heightmaps.remove("SOLID"); ++ heightmaps.setGeneric("OCEAN_FLOOR_WG", solid); ++ heightmaps.setGeneric("OCEAN_FLOOR", solid); ++ } ++ ++ final Object light = heightmaps.getGeneric("LIGHT"); ++ if (light != null) { ++ heightmaps.remove("LIGHT"); ++ heightmaps.setGeneric("LIGHT_BLOCKING", light); ++ } ++ ++ final Object rain = heightmaps.getGeneric("RAIN"); ++ if (rain != null) { ++ heightmaps.remove("RAIN"); ++ heightmaps.setGeneric("MOTION_BLOCKING", rain); ++ heightmaps.setGeneric("MOTION_BLOCKING_NO_LEAVES", rain); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1484() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf7b0a77b30312a32d5fdb10be3fb45d10ba7870 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1486 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static final Map RENAMED_ENTITY_IDS = ImmutableMap.builder() ++ .put("minecraft:salmon_mob", "minecraft:salmon") ++ .put("minecraft:cod_mob", "minecraft:cod") ++ .build(); ++ public static final Map RENAMED_ITEM_IDS = ImmutableMap.builder() ++ .put("minecraft:salmon_mob_spawn_egg", "minecraft:salmon_spawn_egg") ++ .put("minecraft:cod_mob_spawn_egg", "minecraft:cod_spawn_egg") ++ .build(); ++ ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:cod_mob", "minecraft:cod"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:salmon_mob", "minecraft:salmon"); ++ ++ ConverterAbstractEntityRename.register(VERSION, RENAMED_ENTITY_IDS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); ++ } ++ ++ private V1486() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dba4a64f61b27f1eb820e0e0a3fddb81517acc16 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1487 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 2; ++ ++ public static void register() { ++ final Map remap = ImmutableMap.of( ++ "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab", ++ "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs" ++ ); ++ ++ ConverterAbstractItemRename.register(VERSION, remap::get); ++ ConverterAbstractBlockRename.register(VERSION, remap::get); ++ } ++ ++ private V1487() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cf3579524a9ba96f2065d98bca928bf920da081c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java +@@ -0,0 +1,88 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1488 { ++ ++ protected static final int VERSION = MCVersions.V18W19B + 3; ++ ++ protected static boolean isIglooPiece(final MapType piece) { ++ return "Iglu".equals(piece.getString("id")); ++ } ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:kelp_top", "minecraft:kelp", ++ "minecraft:kelp", "minecraft:kelp_plant" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:kelp_top", "minecraft:kelp" ++ )::get); ++ ++ // Don't ask me why in V1458 they wrote the converter to NOT do command blocks and THEN in THIS version ++ // to ONLY do command blocks. I don't know. ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:command_block", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return V1458.updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:commandblock_minecart", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ return V1458.updateCustomName(data); ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ boolean isIgloo; ++ if (children != null) { ++ isIgloo = true; ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ if (!isIglooPiece(children.getMap(i))) { ++ isIgloo = false; ++ break; ++ } ++ } ++ } else { ++ isIgloo = false; ++ } ++ ++ if (isIgloo) { ++ data.remove("Children"); ++ data.setString("id", "Igloo"); ++ return null; ++ } ++ ++ if (children != null) { ++ for (int i = 0; i < children.size();) { ++ final MapType child = children.getMap(i); ++ if (isIglooPiece(child)) { ++ children.remove(i); ++ continue; ++ } ++ ++i; ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1488() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb3afa0634cb47cbd5b324a66d140375b1c23e07 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1490 { ++ ++ protected static final int VERSION = MCVersions.V18W20A + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:melon_block", "minecraft:melon" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:melon_block", "minecraft:melon", ++ "minecraft:melon", "minecraft:melon_slice", ++ "minecraft:speckled_melon", "minecraft:glistering_melon_slice" ++ )::get); ++ } ++ ++ private V1490() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b8732c2035ee0659173a8299cc2b0a5f86ace7b0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java +@@ -0,0 +1,152 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import com.mojang.datafixers.util.Pair; ++ ++public final class V1492 { ++ ++ private static final ImmutableMap>> RENAMES = ImmutableMap.>>builder() ++ .put("EndCity", Pair.of( ++ "ECP", ++ ImmutableMap.builder() ++ .put("second_floor", "second_floor_1") ++ .put("third_floor", "third_floor_1") ++ .put("third_floor_c", "third_floor_2") ++ .build() ++ ) ++ ) ++ ++ .put("Mansion", Pair.of( ++ "WMP", ++ ImmutableMap.builder() ++ .put("carpet_south", "carpet_south_1") ++ .put("carpet_west", "carpet_west_1") ++ .put("indoors_door", "indoors_door_1") ++ .put("indoors_wall", "indoors_wall_1") ++ .build() ++ ) ++ ) ++ ++ .put("Igloo", Pair.of( ++ "Iglu", ++ ImmutableMap.builder() ++ .put("minecraft:igloo/igloo_bottom", "minecraft:igloo/bottom") ++ .put("minecraft:igloo/igloo_middle", "minecraft:igloo/middle") ++ .put("minecraft:igloo/igloo_top", "minecraft:igloo/top") ++ .build() ++ ) ++ ) ++ .put("Ocean_Ruin", Pair.of( ++ "ORP", ++ ImmutableMap.builder() ++ .put("minecraft:ruin/big_ruin1_brick", "minecraft:underwater_ruin/big_brick_1") ++ .put("minecraft:ruin/big_ruin2_brick", "minecraft:underwater_ruin/big_brick_2") ++ .put("minecraft:ruin/big_ruin3_brick", "minecraft:underwater_ruin/big_brick_3") ++ .put("minecraft:ruin/big_ruin8_brick", "minecraft:underwater_ruin/big_brick_8") ++ .put("minecraft:ruin/big_ruin1_cracked", "minecraft:underwater_ruin/big_cracked_1") ++ .put("minecraft:ruin/big_ruin2_cracked", "minecraft:underwater_ruin/big_cracked_2") ++ .put("minecraft:ruin/big_ruin3_cracked", "minecraft:underwater_ruin/big_cracked_3") ++ .put("minecraft:ruin/big_ruin8_cracked", "minecraft:underwater_ruin/big_cracked_8") ++ .put("minecraft:ruin/big_ruin1_mossy", "minecraft:underwater_ruin/big_mossy_1") ++ .put("minecraft:ruin/big_ruin2_mossy", "minecraft:underwater_ruin/big_mossy_2") ++ .put("minecraft:ruin/big_ruin3_mossy", "minecraft:underwater_ruin/big_mossy_3") ++ .put("minecraft:ruin/big_ruin8_mossy", "minecraft:underwater_ruin/big_mossy_8") ++ .put("minecraft:ruin/big_ruin_warm4", "minecraft:underwater_ruin/big_warm_4") ++ .put("minecraft:ruin/big_ruin_warm5", "minecraft:underwater_ruin/big_warm_5") ++ .put("minecraft:ruin/big_ruin_warm6", "minecraft:underwater_ruin/big_warm_6") ++ .put("minecraft:ruin/big_ruin_warm7", "minecraft:underwater_ruin/big_warm_7") ++ .put("minecraft:ruin/ruin1_brick", "minecraft:underwater_ruin/brick_1") ++ .put("minecraft:ruin/ruin2_brick", "minecraft:underwater_ruin/brick_2") ++ .put("minecraft:ruin/ruin3_brick", "minecraft:underwater_ruin/brick_3") ++ .put("minecraft:ruin/ruin4_brick", "minecraft:underwater_ruin/brick_4") ++ .put("minecraft:ruin/ruin5_brick", "minecraft:underwater_ruin/brick_5") ++ .put("minecraft:ruin/ruin6_brick", "minecraft:underwater_ruin/brick_6") ++ .put("minecraft:ruin/ruin7_brick", "minecraft:underwater_ruin/brick_7") ++ .put("minecraft:ruin/ruin8_brick", "minecraft:underwater_ruin/brick_8") ++ .put("minecraft:ruin/ruin1_cracked", "minecraft:underwater_ruin/cracked_1") ++ .put("minecraft:ruin/ruin2_cracked", "minecraft:underwater_ruin/cracked_2") ++ .put("minecraft:ruin/ruin3_cracked", "minecraft:underwater_ruin/cracked_3") ++ .put("minecraft:ruin/ruin4_cracked", "minecraft:underwater_ruin/cracked_4") ++ .put("minecraft:ruin/ruin5_cracked", "minecraft:underwater_ruin/cracked_5") ++ .put("minecraft:ruin/ruin6_cracked", "minecraft:underwater_ruin/cracked_6") ++ .put("minecraft:ruin/ruin7_cracked", "minecraft:underwater_ruin/cracked_7") ++ .put("minecraft:ruin/ruin8_cracked", "minecraft:underwater_ruin/cracked_8") ++ .put("minecraft:ruin/ruin1_mossy", "minecraft:underwater_ruin/mossy_1") ++ .put("minecraft:ruin/ruin2_mossy", "minecraft:underwater_ruin/mossy_2") ++ .put("minecraft:ruin/ruin3_mossy", "minecraft:underwater_ruin/mossy_3") ++ .put("minecraft:ruin/ruin4_mossy", "minecraft:underwater_ruin/mossy_4") ++ .put("minecraft:ruin/ruin5_mossy", "minecraft:underwater_ruin/mossy_5") ++ .put("minecraft:ruin/ruin6_mossy", "minecraft:underwater_ruin/mossy_6") ++ .put("minecraft:ruin/ruin7_mossy", "minecraft:underwater_ruin/mossy_7") ++ .put("minecraft:ruin/ruin8_mossy", "minecraft:underwater_ruin/mossy_8") ++ .put("minecraft:ruin/ruin_warm1", "minecraft:underwater_ruin/warm_1") ++ .put("minecraft:ruin/ruin_warm2", "minecraft:underwater_ruin/warm_2") ++ .put("minecraft:ruin/ruin_warm3", "minecraft:underwater_ruin/warm_3") ++ .put("minecraft:ruin/ruin_warm4", "minecraft:underwater_ruin/warm_4") ++ .put("minecraft:ruin/ruin_warm5", "minecraft:underwater_ruin/warm_5") ++ .put("minecraft:ruin/ruin_warm6", "minecraft:underwater_ruin/warm_6") ++ .put("minecraft:ruin/ruin_warm7", "minecraft:underwater_ruin/warm_7") ++ .put("minecraft:ruin/ruin_warm8", "minecraft:underwater_ruin/warm_8") ++ .put("minecraft:ruin/big_brick_1", "minecraft:underwater_ruin/big_brick_1") ++ .put("minecraft:ruin/big_brick_2", "minecraft:underwater_ruin/big_brick_2") ++ .put("minecraft:ruin/big_brick_3", "minecraft:underwater_ruin/big_brick_3") ++ .put("minecraft:ruin/big_brick_8", "minecraft:underwater_ruin/big_brick_8") ++ .put("minecraft:ruin/big_mossy_1", "minecraft:underwater_ruin/big_mossy_1") ++ .put("minecraft:ruin/big_mossy_2", "minecraft:underwater_ruin/big_mossy_2") ++ .put("minecraft:ruin/big_mossy_3", "minecraft:underwater_ruin/big_mossy_3") ++ .put("minecraft:ruin/big_mossy_8", "minecraft:underwater_ruin/big_mossy_8") ++ .put("minecraft:ruin/big_cracked_1", "minecraft:underwater_ruin/big_cracked_1") ++ .put("minecraft:ruin/big_cracked_2", "minecraft:underwater_ruin/big_cracked_2") ++ .put("minecraft:ruin/big_cracked_3", "minecraft:underwater_ruin/big_cracked_3") ++ .put("minecraft:ruin/big_cracked_8", "minecraft:underwater_ruin/big_cracked_8") ++ .put("minecraft:ruin/big_warm_4", "minecraft:underwater_ruin/big_warm_4") ++ .put("minecraft:ruin/big_warm_5", "minecraft:underwater_ruin/big_warm_5") ++ .put("minecraft:ruin/big_warm_6", "minecraft:underwater_ruin/big_warm_6") ++ .put("minecraft:ruin/big_warm_7", "minecraft:underwater_ruin/big_warm_7") ++ .build() ++ ) ++ ) ++ ++ .build(); ++ ++ protected static final int VERSION = MCVersions.V18W20B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ if (children == null) { ++ return null; ++ } ++ ++ final String id = data.getString("id"); ++ ++ final Pair> renames = RENAMES.get(id); ++ if (renames == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ if (renames.getFirst().equals(child.getString("id"))) { ++ final String template = child.getString("Template", ""); ++ child.setString("Template", renames.getSecond().getOrDefault(template, template)); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1492() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java +new file mode 100644 +index 0000000000000000000000000000000000000000..50411042a83d58c4c36768a8f5196b4b41b4d095 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java +@@ -0,0 +1,89 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V1494 { ++ ++ protected static final int VERSION = MCVersions.V18W20C + 1; ++ ++ private static final Int2ObjectOpenHashMap ENCH_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); ++ static { ++ ENCH_ID_TO_NAME.put(0, "minecraft:protection"); ++ ENCH_ID_TO_NAME.put(1, "minecraft:fire_protection"); ++ ENCH_ID_TO_NAME.put(2, "minecraft:feather_falling"); ++ ENCH_ID_TO_NAME.put(3, "minecraft:blast_protection"); ++ ENCH_ID_TO_NAME.put(4, "minecraft:projectile_protection"); ++ ENCH_ID_TO_NAME.put(5, "minecraft:respiration"); ++ ENCH_ID_TO_NAME.put(6, "minecraft:aqua_affinity"); ++ ENCH_ID_TO_NAME.put(7, "minecraft:thorns"); ++ ENCH_ID_TO_NAME.put(8, "minecraft:depth_strider"); ++ ENCH_ID_TO_NAME.put(9, "minecraft:frost_walker"); ++ ENCH_ID_TO_NAME.put(10, "minecraft:binding_curse"); ++ ENCH_ID_TO_NAME.put(16, "minecraft:sharpness"); ++ ENCH_ID_TO_NAME.put(17, "minecraft:smite"); ++ ENCH_ID_TO_NAME.put(18, "minecraft:bane_of_arthropods"); ++ ENCH_ID_TO_NAME.put(19, "minecraft:knockback"); ++ ENCH_ID_TO_NAME.put(20, "minecraft:fire_aspect"); ++ ENCH_ID_TO_NAME.put(21, "minecraft:looting"); ++ ENCH_ID_TO_NAME.put(22, "minecraft:sweeping"); ++ ENCH_ID_TO_NAME.put(32, "minecraft:efficiency"); ++ ENCH_ID_TO_NAME.put(33, "minecraft:silk_touch"); ++ ENCH_ID_TO_NAME.put(34, "minecraft:unbreaking"); ++ ENCH_ID_TO_NAME.put(35, "minecraft:fortune"); ++ ENCH_ID_TO_NAME.put(48, "minecraft:power"); ++ ENCH_ID_TO_NAME.put(49, "minecraft:punch"); ++ ENCH_ID_TO_NAME.put(50, "minecraft:flame"); ++ ENCH_ID_TO_NAME.put(51, "minecraft:infinity"); ++ ENCH_ID_TO_NAME.put(61, "minecraft:luck_of_the_sea"); ++ ENCH_ID_TO_NAME.put(62, "minecraft:lure"); ++ ENCH_ID_TO_NAME.put(65, "minecraft:loyalty"); ++ ENCH_ID_TO_NAME.put(66, "minecraft:impaling"); ++ ENCH_ID_TO_NAME.put(67, "minecraft:riptide"); ++ ENCH_ID_TO_NAME.put(68, "minecraft:channeling"); ++ ENCH_ID_TO_NAME.put(70, "minecraft:mending"); ++ ENCH_ID_TO_NAME.put(71, "minecraft:vanishing_curse"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final ListType enchants = tag.getList("ench", ObjectType.MAP); ++ if (enchants != null) { ++ tag.remove("ench"); ++ tag.setList("Enchantments", enchants); ++ ++ for (int i = 0, len = enchants.size(); i < len; ++i) { ++ final MapType enchant = enchants.getMap(i); ++ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); ++ } ++ } ++ ++ final ListType storedEnchants = tag.getList("StoredEnchantments", ObjectType.MAP); ++ if (storedEnchants != null) { ++ for (int i = 0, len = storedEnchants.size(); i < len; ++i) { ++ final MapType enchant = storedEnchants.getMap(i); ++ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); ++ } ++ } ++ ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V1494() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c56d50c552d4609474f5b3b6b0b8be8b575764ea +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java +@@ -0,0 +1,370 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.datafixers.DataFixUtils; ++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import net.minecraft.util.datafix.PackedBitStorage; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V1496 { ++ ++ private V1496() {} ++ ++ protected static final int VERSION = MCVersions.V18W21B; ++ ++ private static final int[][] DIRECTIONS = new int[][] { ++ new int[] {-1, 0, 0}, ++ new int[] {1, 0, 0}, ++ new int[] {0, -1, 0}, ++ new int[] {0, 1, 0}, ++ new int[] {0, 0, -1}, ++ new int[] {0, 0, 1} ++ }; ++ ++ private static final Object2IntOpenHashMap LEAVES_TO_ID = new Object2IntOpenHashMap<>(); ++ static { ++ LEAVES_TO_ID.put("minecraft:acacia_leaves", 0); ++ LEAVES_TO_ID.put("minecraft:birch_leaves", 1); ++ LEAVES_TO_ID.put("minecraft:dark_oak_leaves", 2); ++ LEAVES_TO_ID.put("minecraft:jungle_leaves", 3); ++ LEAVES_TO_ID.put("minecraft:oak_leaves", 4); ++ LEAVES_TO_ID.put("minecraft:spruce_leaves", 5); ++ } ++ ++ private static final Set LOGS = new HashSet<>( ++ Arrays.asList( ++ "minecraft:acacia_bark", ++ "minecraft:birch_bark", ++ "minecraft:dark_oak_bark", ++ "minecraft:jungle_bark", ++ "minecraft:oak_bark", ++ "minecraft:spruce_bark", ++ "minecraft:acacia_log", ++ "minecraft:birch_log", ++ "minecraft:dark_oak_log", ++ "minecraft:jungle_log", ++ "minecraft:oak_log", ++ "minecraft:spruce_log", ++ "minecraft:stripped_acacia_log", ++ "minecraft:stripped_birch_log", ++ "minecraft:stripped_dark_oak_log", ++ "minecraft:stripped_jungle_log", ++ "minecraft:stripped_oak_log", ++ "minecraft:stripped_spruce_log" ++ ) ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sectionsNBT = level.getList("Sections", ObjectType.MAP); ++ if (sectionsNBT == null) { ++ return null; ++ } ++ ++ int newSides = 0; ++ ++ final LeavesSection[] sections = new LeavesSection[16]; ++ boolean skippable = true; ++ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { ++ final LeavesSection section = new LeavesSection(sectionsNBT.getMap(i)); ++ sections[section.sectionY] = section; ++ ++ skippable &= section.isSkippable(); ++ } ++ ++ if (skippable) { ++ return null; ++ } ++ ++ final IntOpenHashSet[] positionsByDistance = new IntOpenHashSet[7]; ++ for (int i = 0; i < positionsByDistance.length; ++i) { ++ positionsByDistance[i] = new IntOpenHashSet(); ++ } ++ ++ for (final LeavesSection section : sections) { ++ if (section == null || section.isSkippable()) { ++ continue; ++ } ++ ++ for (int index = 0; index < 4096; ++index) { ++ final int block = section.getBlock(index); ++ if (section.isLog(block)) { ++ positionsByDistance[0].add(section.getSectionY() << 12 | index); ++ } else if (section.isLeaf(block)) { ++ int x = getX(index); ++ int z = getZ(index); ++ newSides |= getSideMask(x == 0, x == 15, z == 0, z == 15); ++ } ++ } ++ } ++ ++ // this is basically supposed to recalculate the distances, because a higher cap was added ++ for (int distance = 1; distance < 7; ++distance) { ++ final IntOpenHashSet positionsLess = positionsByDistance[distance - 1]; ++ final IntOpenHashSet positionsEqual = positionsByDistance[distance]; ++ ++ for (final IntIterator iterator = positionsLess.iterator(); iterator.hasNext();) { ++ final int position = iterator.nextInt(); ++ final int fromX = getX(position); ++ final int fromY = getY(position); ++ final int fromZ = getZ(position); ++ ++ for (final int[] direction : DIRECTIONS) { ++ final int toX = fromX + direction[0]; ++ final int toY = fromY + direction[1]; ++ final int toZ = fromZ + direction[2]; ++ ++ if (!(toX >= 0 && toX <= 15 && toZ >= 0 && toZ <= 15 && toY >= 0 && toY <= 255)) { ++ continue; ++ } ++ ++ final LeavesSection toSection = sections[toY >> 4]; ++ if (toSection == null || toSection.isSkippable()) { ++ continue; ++ } ++ ++ final int sectionLocalIndex = getIndex(toX, toY & 15, toZ); ++ final int toBlock = toSection.getBlock(sectionLocalIndex); ++ ++ if (toSection.isLeaf(toBlock)) { ++ final int newDistance = toSection.getDistance(toBlock); ++ if (newDistance > distance) { ++ toSection.setDistance(sectionLocalIndex, toBlock, distance); ++ positionsEqual.add(getIndex(toX, toY, toZ)); ++ } ++ } ++ } ++ } ++ } ++ ++ // done updating blocks, now just update the blockstates and palette ++ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { ++ final MapType sectionNBT = sectionsNBT.getMap(i); ++ final int y = sectionNBT.getInt("Y"); ++ final LeavesSection section = sections[y]; ++ ++ section.writeInto(sectionNBT); ++ } ++ ++ // if sides changed during process, update it now ++ if (newSides != 0) { ++ MapType upgradeData = level.getMap("UpgradeData"); ++ if (upgradeData == null) { ++ level.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); ++ } ++ ++ upgradeData.setByte("Sides", (byte)(upgradeData.getByte("Sides") | newSides)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static int getIndex(final int x, final int y, final int z) { ++ return y << 8 | z << 4 | x; ++ } ++ ++ public static int getX(final int index) { ++ return index & 15; ++ } ++ ++ public static int getY(final int index) { ++ return index >> 8 & 255; ++ } ++ ++ public static int getZ(final int index) { ++ return index >> 4 & 15; ++ } ++ ++ public static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { ++ final int ret; ++ ++ if (noBack) { ++ if (noRight) { ++ ret = 2; ++ } else if (noLeft) { ++ ret = 128; ++ } else { ++ ret = 1; ++ } ++ } else if (noForward) { ++ if (noLeft) { ++ ret = 32; ++ } else if (noRight) { ++ ret = 8; ++ } else { ++ ret = 16; ++ } ++ } else if (noRight) { ++ ret = 4; ++ } else if (noLeft) { ++ ret = 64; ++ } else { ++ ret = 0; ++ } ++ ++ return ret; ++ } ++ ++ public abstract static class Section { ++ protected final ListType palette; ++ protected final int sectionY; ++ protected PackedBitStorage storage; ++ ++ public Section(final MapType section) { ++ this.palette = section.getList("Palette", ObjectType.MAP); ++ this.sectionY = section.getInt("Y"); ++ this.readStorage(section); ++ } ++ ++ protected void readStorage(final MapType section) { ++ if (this.initSkippable()) { ++ this.storage = null; ++ } else { ++ final long[] states = section.getLongs("BlockStates"); ++ final int bits = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); ++ this.storage = new PackedBitStorage(bits, 4096, states); ++ } ++ } ++ ++ public void writeInto(final MapType section) { ++ if (this.isSkippable()) { ++ return; ++ } ++ ++ section.setList("Palette", this.palette); ++ section.setLongs("BlockStates", this.storage.getRaw()); ++ } ++ ++ public boolean isSkippable() { ++ return this.storage == null; ++ } ++ ++ public int getBlock(final int index) { ++ return this.storage.get(index); ++ } ++ ++ protected int getStateId(final String name, final boolean persistent, final int distance) { ++ return LEAVES_TO_ID.getInt(name) << 5 | (persistent ? 16 : 0) | distance; ++ } ++ ++ protected int getSectionY() { ++ return this.sectionY; ++ } ++ ++ protected abstract boolean initSkippable(); ++ } ++ ++ public static final class LeavesSection extends Section { ++ private IntOpenHashSet leaveIds; ++ private IntOpenHashSet logIds; ++ private Int2IntOpenHashMap stateToIdMap; ++ ++ public LeavesSection(final MapType section) { ++ super(section); ++ } ++ ++ @Override ++ protected boolean initSkippable() { ++ this.leaveIds = new IntOpenHashSet(); ++ this.logIds = new IntOpenHashSet(); ++ this.stateToIdMap = new Int2IntOpenHashMap(); ++ this.stateToIdMap.defaultReturnValue(-1); ++ ++ for(int i = 0; i < this.palette.size(); ++i) { ++ final MapType blockState = this.palette.getMap(i); ++ final String name = blockState.getString("Name", ""); ++ if (LEAVES_TO_ID.containsKey(name)) { ++ final MapType properties = blockState.getMap("Properties"); ++ final boolean notDecayable = properties != null && "false".equals(properties.getString("decayable")); ++ ++ this.leaveIds.add(i); ++ this.stateToIdMap.put(this.getStateId(name, notDecayable, 7), i); ++ this.palette.setMap(i, this.makeNewLeafTag(name, notDecayable, 7)); ++ } ++ ++ if (LOGS.contains(name)) { ++ this.logIds.add(i); ++ } ++ } ++ ++ return this.leaveIds.isEmpty() && this.logIds.isEmpty(); ++ } ++ ++ private MapType makeNewLeafTag(final String name, final boolean notDecayable, final int distance) { ++ final MapType properties = Types.NBT.createEmptyMap(); ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("Name", name); ++ ret.setMap("Properties", properties); ++ ++ properties.setString("persistent", Boolean.toString(notDecayable)); ++ properties.setString("distance", Integer.toString(distance)); ++ ++ return ret; ++ } ++ ++ public boolean isLog(final int id) { ++ return this.logIds.contains(id); ++ } ++ ++ public boolean isLeaf(final int id) { ++ return this.leaveIds.contains(id); ++ } ++ ++ // only call for logs or leaves, will throw otherwise! ++ private int getDistance(final int id) { ++ if (this.isLog(id)) { ++ return 0; ++ } ++ ++ return Integer.parseInt(this.palette.getMap(id).getMap("Properties").getString("distance")); ++ } ++ ++ private void setDistance(final int index, final int id, final int distance) { ++ final MapType state = this.palette.getMap(id); ++ final String name = state.getString("Name"); ++ final boolean persistent = "true".equals(state.getMap("Properties").getString("persistent")); ++ final int newState = this.getStateId(name, persistent, distance); ++ int newStateId; ++ if ((newStateId = this.stateToIdMap.get(newState)) == -1) { ++ newStateId = this.palette.size(); ++ this.leaveIds.add(newStateId); ++ this.stateToIdMap.put(newState, newStateId); ++ this.palette.addMap(this.makeNewLeafTag(name, persistent, distance)); ++ } ++ ++ if (1 << this.storage.getBits() <= newStateId) { ++ // need to widen storage ++ final PackedBitStorage newStorage = new PackedBitStorage(this.storage.getBits() + 1, 4096); ++ ++ for(int i = 0; i < 4096; ++i) { ++ newStorage.set(i, this.storage.get(i)); ++ } ++ ++ this.storage = newStorage; ++ } ++ ++ this.storage.set(index, newStateId); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9208152e2a158470f37b0eb022478e8e5287c12b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1500 { ++ ++ protected static final int VERSION = MCVersions.V18W22C + 1; ++ ++ private V1500() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("DUMMY", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setBoolean("keepPacked", true); ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c497faa60c37f30ceb0d7c394446d6074599c1b7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java +@@ -0,0 +1,75 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1501 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE1; ++ ++ private static final Map RENAMES = ImmutableMap.builder() ++ .put("minecraft:recipes/brewing/speckled_melon", "minecraft:recipes/brewing/glistering_melon_slice") ++ .put("minecraft:recipes/building_blocks/black_stained_hardened_clay", "minecraft:recipes/building_blocks/black_terracotta") ++ .put("minecraft:recipes/building_blocks/blue_stained_hardened_clay", "minecraft:recipes/building_blocks/blue_terracotta") ++ .put("minecraft:recipes/building_blocks/brown_stained_hardened_clay", "minecraft:recipes/building_blocks/brown_terracotta") ++ .put("minecraft:recipes/building_blocks/cyan_stained_hardened_clay", "minecraft:recipes/building_blocks/cyan_terracotta") ++ .put("minecraft:recipes/building_blocks/gray_stained_hardened_clay", "minecraft:recipes/building_blocks/gray_terracotta") ++ .put("minecraft:recipes/building_blocks/green_stained_hardened_clay", "minecraft:recipes/building_blocks/green_terracotta") ++ .put("minecraft:recipes/building_blocks/light_blue_stained_hardened_clay", "minecraft:recipes/building_blocks/light_blue_terracotta") ++ .put("minecraft:recipes/building_blocks/light_gray_stained_hardened_clay", "minecraft:recipes/building_blocks/light_gray_terracotta") ++ .put("minecraft:recipes/building_blocks/lime_stained_hardened_clay", "minecraft:recipes/building_blocks/lime_terracotta") ++ .put("minecraft:recipes/building_blocks/magenta_stained_hardened_clay", "minecraft:recipes/building_blocks/magenta_terracotta") ++ .put("minecraft:recipes/building_blocks/orange_stained_hardened_clay", "minecraft:recipes/building_blocks/orange_terracotta") ++ .put("minecraft:recipes/building_blocks/pink_stained_hardened_clay", "minecraft:recipes/building_blocks/pink_terracotta") ++ .put("minecraft:recipes/building_blocks/purple_stained_hardened_clay", "minecraft:recipes/building_blocks/purple_terracotta") ++ .put("minecraft:recipes/building_blocks/red_stained_hardened_clay", "minecraft:recipes/building_blocks/red_terracotta") ++ .put("minecraft:recipes/building_blocks/white_stained_hardened_clay", "minecraft:recipes/building_blocks/white_terracotta") ++ .put("minecraft:recipes/building_blocks/yellow_stained_hardened_clay", "minecraft:recipes/building_blocks/yellow_terracotta") ++ .put("minecraft:recipes/building_blocks/acacia_wooden_slab", "minecraft:recipes/building_blocks/acacia_slab") ++ .put("minecraft:recipes/building_blocks/birch_wooden_slab", "minecraft:recipes/building_blocks/birch_slab") ++ .put("minecraft:recipes/building_blocks/dark_oak_wooden_slab", "minecraft:recipes/building_blocks/dark_oak_slab") ++ .put("minecraft:recipes/building_blocks/jungle_wooden_slab", "minecraft:recipes/building_blocks/jungle_slab") ++ .put("minecraft:recipes/building_blocks/oak_wooden_slab", "minecraft:recipes/building_blocks/oak_slab") ++ .put("minecraft:recipes/building_blocks/spruce_wooden_slab", "minecraft:recipes/building_blocks/spruce_slab") ++ .put("minecraft:recipes/building_blocks/brick_block", "minecraft:recipes/building_blocks/bricks") ++ .put("minecraft:recipes/building_blocks/chiseled_stonebrick", "minecraft:recipes/building_blocks/chiseled_stone_bricks") ++ .put("minecraft:recipes/building_blocks/end_bricks", "minecraft:recipes/building_blocks/end_stone_bricks") ++ .put("minecraft:recipes/building_blocks/lit_pumpkin", "minecraft:recipes/building_blocks/jack_o_lantern") ++ .put("minecraft:recipes/building_blocks/magma", "minecraft:recipes/building_blocks/magma_block") ++ .put("minecraft:recipes/building_blocks/melon_block", "minecraft:recipes/building_blocks/melon") ++ .put("minecraft:recipes/building_blocks/mossy_stonebrick", "minecraft:recipes/building_blocks/mossy_stone_bricks") ++ .put("minecraft:recipes/building_blocks/nether_brick", "minecraft:recipes/building_blocks/nether_bricks") ++ .put("minecraft:recipes/building_blocks/pillar_quartz_block", "minecraft:recipes/building_blocks/quartz_pillar") ++ .put("minecraft:recipes/building_blocks/red_nether_brick", "minecraft:recipes/building_blocks/red_nether_bricks") ++ .put("minecraft:recipes/building_blocks/snow", "minecraft:recipes/building_blocks/snow_block") ++ .put("minecraft:recipes/building_blocks/smooth_red_sandstone", "minecraft:recipes/building_blocks/cut_red_sandstone") ++ .put("minecraft:recipes/building_blocks/smooth_sandstone", "minecraft:recipes/building_blocks/cut_sandstone") ++ .put("minecraft:recipes/building_blocks/stonebrick", "minecraft:recipes/building_blocks/stone_bricks") ++ .put("minecraft:recipes/building_blocks/stone_stairs", "minecraft:recipes/building_blocks/cobblestone_stairs") ++ .put("minecraft:recipes/building_blocks/string_to_wool", "minecraft:recipes/building_blocks/white_wool_from_string") ++ .put("minecraft:recipes/decorations/fence", "minecraft:recipes/decorations/oak_fence") ++ .put("minecraft:recipes/decorations/purple_shulker_box", "minecraft:recipes/decorations/shulker_box") ++ .put("minecraft:recipes/decorations/slime", "minecraft:recipes/decorations/slime_block") ++ .put("minecraft:recipes/decorations/snow_layer", "minecraft:recipes/decorations/snow") ++ .put("minecraft:recipes/misc/bone_meal_from_block", "minecraft:recipes/misc/bone_meal_from_bone_block") ++ .put("minecraft:recipes/misc/bone_meal_from_bone", "minecraft:recipes/misc/bone_meal") ++ .put("minecraft:recipes/misc/gold_ingot_from_block", "minecraft:recipes/misc/gold_ingot_from_gold_block") ++ .put("minecraft:recipes/misc/iron_ingot_from_block", "minecraft:recipes/misc/iron_ingot_from_iron_block") ++ .put("minecraft:recipes/redstone/fence_gate", "minecraft:recipes/redstone/oak_fence_gate") ++ .put("minecraft:recipes/redstone/noteblock", "minecraft:recipes/redstone/note_block") ++ .put("minecraft:recipes/redstone/trapdoor", "minecraft:recipes/redstone/oak_trapdoor") ++ .put("minecraft:recipes/redstone/wooden_button", "minecraft:recipes/redstone/oak_button") ++ .put("minecraft:recipes/redstone/wooden_door", "minecraft:recipes/redstone/oak_door") ++ .put("minecraft:recipes/redstone/wooden_pressure_plate", "minecraft:recipes/redstone/oak_pressure_plate") ++ .put("minecraft:recipes/transportation/boat", "minecraft:recipes/transportation/oak_boat") ++ .put("minecraft:recipes/transportation/golden_rail", "minecraft:recipes/transportation/powered_rail") ++ .build(); ++ ++ private V1501() {} ++ ++ public static void register() { ++ ConverterAbstractAdvancementsRename.register(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7db79b279a047ec5907a3fb2c6e1a75be14a2f68 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java +@@ -0,0 +1,74 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1502 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE2; ++ ++ private static final Map RECIPES_UPDATES = ImmutableMap.builder() ++ .put("minecraft:acacia_wooden_slab", "minecraft:acacia_slab") ++ .put("minecraft:birch_wooden_slab", "minecraft:birch_slab") ++ .put("minecraft:black_stained_hardened_clay", "minecraft:black_terracotta") ++ .put("minecraft:blue_stained_hardened_clay", "minecraft:blue_terracotta") ++ .put("minecraft:boat", "minecraft:oak_boat") ++ .put("minecraft:bone_meal_from_block", "minecraft:bone_meal_from_bone_block") ++ .put("minecraft:bone_meal_from_bone", "minecraft:bone_meal") ++ .put("minecraft:brick_block", "minecraft:bricks") ++ .put("minecraft:brown_stained_hardened_clay", "minecraft:brown_terracotta") ++ .put("minecraft:chiseled_stonebrick", "minecraft:chiseled_stone_bricks") ++ .put("minecraft:cyan_stained_hardened_clay", "minecraft:cyan_terracotta") ++ .put("minecraft:dark_oak_wooden_slab", "minecraft:dark_oak_slab") ++ .put("minecraft:end_bricks", "minecraft:end_stone_bricks") ++ .put("minecraft:fence_gate", "minecraft:oak_fence_gate") ++ .put("minecraft:fence", "minecraft:oak_fence") ++ .put("minecraft:golden_rail", "minecraft:powered_rail") ++ .put("minecraft:gold_ingot_from_block", "minecraft:gold_ingot_from_gold_block") ++ .put("minecraft:gray_stained_hardened_clay", "minecraft:gray_terracotta") ++ .put("minecraft:green_stained_hardened_clay", "minecraft:green_terracotta") ++ .put("minecraft:iron_ingot_from_block", "minecraft:iron_ingot_from_iron_block") ++ .put("minecraft:jungle_wooden_slab", "minecraft:jungle_slab") ++ .put("minecraft:light_blue_stained_hardened_clay", "minecraft:light_blue_terracotta") ++ .put("minecraft:light_gray_stained_hardened_clay", "minecraft:light_gray_terracotta") ++ .put("minecraft:lime_stained_hardened_clay", "minecraft:lime_terracotta") ++ .put("minecraft:lit_pumpkin", "minecraft:jack_o_lantern") ++ .put("minecraft:magenta_stained_hardened_clay", "minecraft:magenta_terracotta") ++ .put("minecraft:magma", "minecraft:magma_block") ++ .put("minecraft:melon_block", "minecraft:melon") ++ .put("minecraft:mossy_stonebrick", "minecraft:mossy_stone_bricks") ++ .put("minecraft:noteblock", "minecraft:note_block") ++ .put("minecraft:oak_wooden_slab", "minecraft:oak_slab") ++ .put("minecraft:orange_stained_hardened_clay", "minecraft:orange_terracotta") ++ .put("minecraft:pillar_quartz_block", "minecraft:quartz_pillar") ++ .put("minecraft:pink_stained_hardened_clay", "minecraft:pink_terracotta") ++ .put("minecraft:purple_shulker_box", "minecraft:shulker_box") ++ .put("minecraft:purple_stained_hardened_clay", "minecraft:purple_terracotta") ++ .put("minecraft:red_nether_brick", "minecraft:red_nether_bricks") ++ .put("minecraft:red_stained_hardened_clay", "minecraft:red_terracotta") ++ .put("minecraft:slime", "minecraft:slime_block") ++ .put("minecraft:smooth_red_sandstone", "minecraft:cut_red_sandstone") ++ .put("minecraft:smooth_sandstone", "minecraft:cut_sandstone") ++ .put("minecraft:snow_layer", "minecraft:snow") ++ .put("minecraft:snow", "minecraft:snow_block") ++ .put("minecraft:speckled_melon", "minecraft:glistering_melon_slice") ++ .put("minecraft:spruce_wooden_slab", "minecraft:spruce_slab") ++ .put("minecraft:stonebrick", "minecraft:stone_bricks") ++ .put("minecraft:stone_stairs", "minecraft:cobblestone_stairs") ++ .put("minecraft:string_to_wool", "minecraft:white_wool_from_string") ++ .put("minecraft:trapdoor", "minecraft:oak_trapdoor") ++ .put("minecraft:white_stained_hardened_clay", "minecraft:white_terracotta") ++ .put("minecraft:wooden_button", "minecraft:oak_button") ++ .put("minecraft:wooden_door", "minecraft:oak_door") ++ .put("minecraft:wooden_pressure_plate", "minecraft:oak_pressure_plate") ++ .put("minecraft:yellow_stained_hardened_clay", "minecraft:yellow_terracotta") ++ .build(); ++ ++ private V1502() {} ++ ++ public static void register() { ++ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ef679762aec326e5e1310390bca46971b548e7cd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java +@@ -0,0 +1,219 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.json.JsonMapType; ++import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.common.base.Splitter; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.collect.Maps; ++import com.mojang.datafixers.util.Pair; ++import com.mojang.serialization.Dynamic; ++import com.mojang.serialization.DynamicOps; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.NbtOps; ++import net.minecraft.nbt.Tag; ++import net.minecraft.util.GsonHelper; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.stream.Collectors; ++ ++public final class V1506 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE4 + 2; ++ ++ static final Map MAP = new HashMap<>(); ++ static { ++ MAP.put("0", "minecraft:ocean"); ++ MAP.put("1", "minecraft:plains"); ++ MAP.put("2", "minecraft:desert"); ++ MAP.put("3", "minecraft:mountains"); ++ MAP.put("4", "minecraft:forest"); ++ MAP.put("5", "minecraft:taiga"); ++ MAP.put("6", "minecraft:swamp"); ++ MAP.put("7", "minecraft:river"); ++ MAP.put("8", "minecraft:nether"); ++ MAP.put("9", "minecraft:the_end"); ++ MAP.put("10", "minecraft:frozen_ocean"); ++ MAP.put("11", "minecraft:frozen_river"); ++ MAP.put("12", "minecraft:snowy_tundra"); ++ MAP.put("13", "minecraft:snowy_mountains"); ++ MAP.put("14", "minecraft:mushroom_fields"); ++ MAP.put("15", "minecraft:mushroom_field_shore"); ++ MAP.put("16", "minecraft:beach"); ++ MAP.put("17", "minecraft:desert_hills"); ++ MAP.put("18", "minecraft:wooded_hills"); ++ MAP.put("19", "minecraft:taiga_hills"); ++ MAP.put("20", "minecraft:mountain_edge"); ++ MAP.put("21", "minecraft:jungle"); ++ MAP.put("22", "minecraft:jungle_hills"); ++ MAP.put("23", "minecraft:jungle_edge"); ++ MAP.put("24", "minecraft:deep_ocean"); ++ MAP.put("25", "minecraft:stone_shore"); ++ MAP.put("26", "minecraft:snowy_beach"); ++ MAP.put("27", "minecraft:birch_forest"); ++ MAP.put("28", "minecraft:birch_forest_hills"); ++ MAP.put("29", "minecraft:dark_forest"); ++ MAP.put("30", "minecraft:snowy_taiga"); ++ MAP.put("31", "minecraft:snowy_taiga_hills"); ++ MAP.put("32", "minecraft:giant_tree_taiga"); ++ MAP.put("33", "minecraft:giant_tree_taiga_hills"); ++ MAP.put("34", "minecraft:wooded_mountains"); ++ MAP.put("35", "minecraft:savanna"); ++ MAP.put("36", "minecraft:savanna_plateau"); ++ MAP.put("37", "minecraft:badlands"); ++ MAP.put("38", "minecraft:wooded_badlands_plateau"); ++ MAP.put("39", "minecraft:badlands_plateau"); ++ MAP.put("40", "minecraft:small_end_islands"); ++ MAP.put("41", "minecraft:end_midlands"); ++ MAP.put("42", "minecraft:end_highlands"); ++ MAP.put("43", "minecraft:end_barrens"); ++ MAP.put("44", "minecraft:warm_ocean"); ++ MAP.put("45", "minecraft:lukewarm_ocean"); ++ MAP.put("46", "minecraft:cold_ocean"); ++ MAP.put("47", "minecraft:deep_warm_ocean"); ++ MAP.put("48", "minecraft:deep_lukewarm_ocean"); ++ MAP.put("49", "minecraft:deep_cold_ocean"); ++ MAP.put("50", "minecraft:deep_frozen_ocean"); ++ MAP.put("127", "minecraft:the_void"); ++ MAP.put("129", "minecraft:sunflower_plains"); ++ MAP.put("130", "minecraft:desert_lakes"); ++ MAP.put("131", "minecraft:gravelly_mountains"); ++ MAP.put("132", "minecraft:flower_forest"); ++ MAP.put("133", "minecraft:taiga_mountains"); ++ MAP.put("134", "minecraft:swamp_hills"); ++ MAP.put("140", "minecraft:ice_spikes"); ++ MAP.put("149", "minecraft:modified_jungle"); ++ MAP.put("151", "minecraft:modified_jungle_edge"); ++ MAP.put("155", "minecraft:tall_birch_forest"); ++ MAP.put("156", "minecraft:tall_birch_hills"); ++ MAP.put("157", "minecraft:dark_forest_hills"); ++ MAP.put("158", "minecraft:snowy_taiga_mountains"); ++ MAP.put("160", "minecraft:giant_spruce_taiga"); ++ MAP.put("161", "minecraft:giant_spruce_taiga_hills"); ++ MAP.put("162", "minecraft:modified_gravelly_mountains"); ++ MAP.put("163", "minecraft:shattered_savanna"); ++ MAP.put("164", "minecraft:shattered_savanna_plateau"); ++ MAP.put("165", "minecraft:eroded_badlands"); ++ MAP.put("166", "minecraft:modified_wooded_badlands_plateau"); ++ MAP.put("167", "minecraft:modified_badlands_plateau"); ++ } ++ ++ private V1506() {} ++ ++ public static void register() { ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String generatorOptions = data.getString("generatorOptions"); ++ final String generatorName = data.getString("generatorName"); ++ if ("flat".equalsIgnoreCase(generatorName)) { ++ data.setMap("generatorOptions", V1506.convert(generatorOptions == null ? "" : generatorOptions)); ++ } else if ("buffet".equalsIgnoreCase(generatorName) && generatorOptions != null) { ++ data.setMap("generatorOptions", JsonTypeUtil.convertJsonToNBT(new JsonMapType(GsonHelper.parse(generatorOptions, true), false))); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private static MapType convert(final String param0) { ++ final Dynamic dynamic = convert(param0, NbtOps.INSTANCE); ++ ++ return new NBTMapType((CompoundTag)dynamic.getValue()); ++ } ++ ++ // Yeah I ain't touching that. This is basically magic value hell. ++ private static Dynamic convert(final String generatorSettings, final DynamicOps ops) { ++ final Iterator splitSettings = Splitter.on(';').split(generatorSettings).iterator(); ++ String biome = "minecraft:plains"; ++ final Map> structures = Maps.newHashMap(); ++ final List> layers; ++ if (!generatorSettings.isEmpty() && splitSettings.hasNext()) { ++ layers = getLayersInfoFromString(splitSettings.next()); ++ if (!layers.isEmpty()) { ++ // biome is next ++ if (splitSettings.hasNext()) { ++ biome = MAP.getOrDefault(splitSettings.next(), "minecraft:plains"); ++ } ++ ++ // structures is next ++ if (splitSettings.hasNext()) { ++ final String[] structuresSplit = splitSettings.next().toLowerCase(Locale.ROOT).split(","); ++ ++ for (final String structureString : structuresSplit) { ++ final String[] structureInfo = structureString.split("\\(", 2); ++ if (!structureInfo[0].isEmpty()) { ++ structures.put(structureInfo[0], Maps.newHashMap()); ++ if (structureInfo.length > 1 && structureInfo[1].endsWith(")") && structureInfo[1].length() > 1) { ++ // I can't even guess the mappings for these. Not worth my time, it will work regardless of the mappings ++ final String[] var7 = structureInfo[1].substring(0, structureInfo[1].length() - 1).split(" "); ++ ++ for (final String var8 : var7) { ++ String[] var9 = var8.split("=", 2); ++ if (var9.length == 2) { ++ structures.get(structureInfo[0]).put(var9[0], var9[1]); ++ } ++ } ++ } ++ } ++ } ++ } else { ++ structures.put("village", Maps.newHashMap()); ++ } ++ } ++ } else { ++ layers = Lists.newArrayList(); ++ layers.add(Pair.of(1, "minecraft:bedrock")); ++ layers.add(Pair.of(2, "minecraft:dirt")); ++ layers.add(Pair.of(1, "minecraft:grass_block")); ++ structures.put("village", Maps.newHashMap()); ++ } ++ ++ final T layerTag = ops.createList(layers.stream().map((param1x) -> ops.createMap(ImmutableMap.of(ops.createString("height"), ops.createInt(param1x.getFirst()), ops.createString("block"), ops.createString(param1x.getSecond()))))); ++ final T structuresTag = ops.createMap(structures.entrySet().stream().map((param1x) -> Pair.of(ops.createString(param1x.getKey().toLowerCase(Locale.ROOT)), ops.createMap(param1x.getValue().entrySet().stream().map((param1xx) -> Pair.of(ops.createString(param1xx.getKey()), ops.createString(param1xx.getValue()))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))); ++ return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("layers"), layerTag, ops.createString("biome"), ops.createString(biome), ops.createString("structures"), structuresTag))); ++ } ++ ++ private static Pair getLayerInfoFromString(final String layerString) { ++ final String[] split = layerString.split("\\*", 2); ++ int layerCount; ++ if (split.length == 2) { ++ try { ++ layerCount = Integer.parseInt(split[0]); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } else { ++ layerCount = 1; ++ } ++ ++ final String blockName = split[split.length - 1]; ++ return Pair.of(layerCount, blockName); ++ } ++ ++ private static List> getLayersInfoFromString(final String layersString) { ++ final List> ret = new ArrayList<>(); ++ final String[] layers = layersString.split(","); ++ ++ for (final String layerString : layers) { ++ final Pair layer = getLayerInfoFromString(layerString); ++ if (layer == null) { ++ return Collections.emptyList(); ++ } ++ ++ ret.add(layer); ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7dbc6ac66a29d3b5e4df5d0105c8fc03a6aea0e0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java +@@ -0,0 +1,103 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++ ++import java.util.Map; ++ ++public final class V1510 { ++ ++ public static final Map RENAMED_ENTITY_IDS = ImmutableMap.builder() ++ .put("minecraft:commandblock_minecart", "minecraft:command_block_minecart") ++ .put("minecraft:ender_crystal", "minecraft:end_crystal") ++ .put("minecraft:snowman", "minecraft:snow_golem") ++ .put("minecraft:evocation_illager", "minecraft:evoker") ++ .put("minecraft:evocation_fangs", "minecraft:evoker_fangs") ++ .put("minecraft:illusion_illager", "minecraft:illusioner") ++ .put("minecraft:vindication_illager", "minecraft:vindicator") ++ .put("minecraft:villager_golem", "minecraft:iron_golem") ++ .put("minecraft:xp_orb", "minecraft:experience_orb") ++ .put("minecraft:xp_bottle", "minecraft:experience_bottle") ++ .put("minecraft:eye_of_ender_signal", "minecraft:eye_of_ender") ++ .put("minecraft:fireworks_rocket", "minecraft:firework_rocket") ++ .build(); ++ ++ public static final Map RENAMED_BLOCKS = ImmutableMap.builder() ++ .put("minecraft:portal", "minecraft:nether_portal") ++ .put("minecraft:oak_bark", "minecraft:oak_wood") ++ .put("minecraft:spruce_bark", "minecraft:spruce_wood") ++ .put("minecraft:birch_bark", "minecraft:birch_wood") ++ .put("minecraft:jungle_bark", "minecraft:jungle_wood") ++ .put("minecraft:acacia_bark", "minecraft:acacia_wood") ++ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") ++ .put("minecraft:stripped_oak_bark", "minecraft:stripped_oak_wood") ++ .put("minecraft:stripped_spruce_bark", "minecraft:stripped_spruce_wood") ++ .put("minecraft:stripped_birch_bark", "minecraft:stripped_birch_wood") ++ .put("minecraft:stripped_jungle_bark", "minecraft:stripped_jungle_wood") ++ .put("minecraft:stripped_acacia_bark", "minecraft:stripped_acacia_wood") ++ .put("minecraft:stripped_dark_oak_bark", "minecraft:stripped_dark_oak_wood") ++ .put("minecraft:mob_spawner", "minecraft:spawner") ++ .build(); ++ ++ public static final Map RENAMED_ITEMS = ImmutableMap.builder() ++ .putAll(RENAMED_BLOCKS) ++ .put("minecraft:clownfish", "minecraft:tropical_fish") ++ .put("minecraft:chorus_fruit_popped", "minecraft:popped_chorus_fruit") ++ .put("minecraft:evocation_illager_spawn_egg", "minecraft:evoker_spawn_egg") ++ .put("minecraft:vindication_illager_spawn_egg", "minecraft:vindicator_spawn_egg") ++ .build(); ++ ++ private static final Map RECIPES_UPDATES = ImmutableMap.builder() ++ .put("minecraft:acacia_bark", "minecraft:acacia_wood") ++ .put("minecraft:birch_bark", "minecraft:birch_wood") ++ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") ++ .put("minecraft:jungle_bark", "minecraft:jungle_wood") ++ .put("minecraft:oak_bark", "minecraft:oak_wood") ++ .put("minecraft:spruce_bark", "minecraft:spruce_wood") ++ .build(); ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE4 + 6; ++ ++ private V1510() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCKS::get); ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEMS::get); ++ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); ++ ++ ConverterAbstractEntityRename.register(VERSION, (String input) -> { ++ if (input.startsWith("minecraft:bred_")) { ++ input = "minecraft:".concat(input.substring("minecraft:bred_".length())); ++ } ++ ++ return RENAMED_ENTITY_IDS.get(input); ++ }); ++ ++ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:swim_one_cm", "minecraft:walk_on_water_one_cm", ++ "minecraft:dive_one_cm", "minecraft:walk_under_water_one_cm" ++ )::get); ++ ++ ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:commandblock_minecart", "minecraft:command_block_minecart"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:ender_crystal", "minecraft:end_crystal"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:snowman", "minecraft:snow_golem"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_illager", "minecraft:evoker"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_fangs", "minecraft:evoker_fangs"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:illusion_illager", "minecraft:illusioner"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:vindication_illager", "minecraft:vindicator"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:villager_golem", "minecraft:iron_golem"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_orb", "minecraft:experience_orb"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_bottle", "minecraft:experience_bottle"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:eye_of_ender_signal", "minecraft:eye_of_ender"); ++ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:fireworks_rocket", "minecraft:firework_rocket"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java +new file mode 100644 +index 0000000000000000000000000000000000000000..86be7a09d19ca94a30186e69655dc164344ae74d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java +@@ -0,0 +1,69 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import net.minecraft.network.chat.Component; ++import net.minecraft.world.scores.criteria.ObjectiveCriteria; ++ ++public final class V1514 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE7 + 1; ++ ++ private V1514() {} ++ ++ public static void register() { ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String displayName = data.getString("DisplayName"); ++ if (displayName == null) { ++ return null; ++ } ++ ++ final String update = Component.Serializer.toJson(Component.literal(displayName)); ++ ++ data.setString("DisplayName", update); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TEAM.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String displayName = data.getString("DisplayName"); ++ if (displayName == null) { ++ return null; ++ } ++ ++ final String update = Component.Serializer.toJson(Component.literal(displayName)); ++ ++ data.setString("DisplayName", update); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { ++ private static ObjectiveCriteria.RenderType getRenderType(String string) { ++ return string.equals("health") ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String renderType = data.getString("RenderType"); ++ if (renderType != null) { ++ return null; ++ } ++ ++ final String criteriaName = data.getString("CriteriaName", ""); ++ ++ data.setString("RenderType", getRenderType(criteriaName).getId()); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f7a2ff2d5a154f667da35215f0e1d0756dbe2a0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1515 { ++ ++ protected static final int VERSION = MCVersions.V1_13_PRE7 + 2; ++ ++ public static final Map RENAMED_BLOCK_IDS = ImmutableMap.builder() ++ .put("minecraft:tube_coral_fan", "minecraft:tube_coral_wall_fan") ++ .put("minecraft:brain_coral_fan", "minecraft:brain_coral_wall_fan") ++ .put("minecraft:bubble_coral_fan", "minecraft:bubble_coral_wall_fan") ++ .put("minecraft:fire_coral_fan", "minecraft:fire_coral_wall_fan") ++ .put("minecraft:horn_coral_fan", "minecraft:horn_coral_wall_fan") ++ .build(); ++ ++ private V1515() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCK_IDS::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7b304031e1e8af120c6535e599c2ee4fdbce1682 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java +@@ -0,0 +1,110 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import org.slf4j.Logger; ++ ++public final class V1624 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V18W32A + 1; ++ ++ private V1624() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ final IntOpenHashSet positionsToLook = new IntOpenHashSet(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final TrappedChestSection section = new TrappedChestSection(sections.getMap(i)); ++ if (section.isSkippable()) { ++ continue; ++ } ++ ++ for (int index = 0; index < 4096; ++index) { ++ if (section.isTrappedChest(section.getBlock(index))) { ++ positionsToLook.add(section.getSectionY() << 12 | index); ++ } ++ } ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); ++ ++ if (tileEntities != null) { ++ for (int i = 0, len = tileEntities.size(); i < len; ++i) { ++ final MapType tile = tileEntities.getMap(i); ++ ++ final int x = tile.getInt("x"); ++ final int y = tile.getInt("y"); ++ final int z = tile.getInt("z"); ++ ++ final int index = V1496.getIndex(x - (chunkX << 4), y, z - (chunkZ << 4)); ++ if (!positionsToLook.contains(index)) { ++ continue; ++ } ++ ++ final String id = tile.getString("id"); ++ if (!"minecraft:chest".equals(id)) { ++ LOGGER.warn("Block Entity ({},{},{}) was expected to be a chest (V1624)", x, y, z); ++ } ++ ++ tile.setString("id", "minecraft:trapped_chest"); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static final class TrappedChestSection extends V1496.Section { ++ ++ private IntOpenHashSet chestIds; ++ ++ public TrappedChestSection(final MapType section) { ++ super(section); ++ } ++ ++ @Override ++ protected boolean initSkippable() { ++ this.chestIds = new IntOpenHashSet(); ++ ++ for (int i = 0; i < this.palette.size(); ++i) { ++ final MapType blockState = this.palette.getMap(i); ++ final String name = blockState.getString("Name"); ++ if ("minecraft:trapped_chest".equals(name)) { ++ this.chestIds.add(i); ++ } ++ } ++ ++ return this.chestIds.isEmpty(); ++ } ++ ++ public boolean isTrappedChest(final int id) { ++ return this.chestIds.contains(id); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java +new file mode 100644 +index 0000000000000000000000000000000000000000..18c84de9e6015a40c189134cc5aaee84267f2669 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java +@@ -0,0 +1,79 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.gson.JsonParseException; ++import net.minecraft.network.chat.CommonComponents; ++import net.minecraft.network.chat.Component; ++import net.minecraft.util.GsonHelper; ++import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; ++import org.apache.commons.lang3.StringUtils; ++ ++public final class V165 { ++ ++ protected static final int VERSION = MCVersions.V1_9_PRE2; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final ListType pages = tag.getList("pages", ObjectType.STRING); ++ if (pages == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = pages.size(); i < len; ++i) { ++ final String page = pages.getString(i); ++ Component component = null; ++ ++ if (!"null".equals(page) && !StringUtils.isEmpty(page)) { ++ if (page.charAt(0) == '"' && page.charAt(page.length() - 1) == '"' || page.charAt(0) == '{' && page.charAt(page.length() - 1) == '}') { ++ try { ++ component = GsonHelper.fromJson(BlockEntitySignTextStrictJsonFix.GSON, page, Component.class, true); ++ if (component == null) { ++ component = CommonComponents.EMPTY; ++ } ++ } catch (final JsonParseException ignored) {} ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJson(page); ++ } catch (final JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ try { ++ component = Component.Serializer.fromJsonLenient(page); ++ } catch (JsonParseException ignored) {} ++ } ++ ++ if (component == null) { ++ component = Component.literal(page); ++ } ++ } else { ++ component = Component.literal(page); ++ } ++ } else { ++ component = CommonComponents.EMPTY; ++ } ++ ++ pages.setString(i, Component.Serializer.toJson(component)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V165() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1362a7917243715305650c197a8e7e5689bcfe4a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1800 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 169; ++ ++ public static final Map RENAMED_ITEM_IDS = ImmutableMap.builder() ++ .put("minecraft:cactus_green", "minecraft:green_dye") ++ .put("minecraft:rose_red", "minecraft:red_dye") ++ .put("minecraft:dandelion_yellow", "minecraft:yellow_dye") ++ .build(); ++ ++ private V1800() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); ++ ++ registerMob("minecraft:panda"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory", "ArmorItems", "HandItems")); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a4e2fa4ed6ede8d1783b5591ef1b5ebf3cd28bf0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V1801 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 170; ++ ++ private V1801() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:illager_beast"); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd5110ef3c18662871020456b60edfb3aeb67166 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1802 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 171; ++ ++ private V1802() {} ++ ++ public static void register() { ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:stone_slab", "minecraft:smooth_stone_slab", ++ "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:stone_slab", "minecraft:smooth_stone_slab", ++ "minecraft:sign", "minecraft:oak_sign" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f22419287400399dfb31653a9208a54e0811f94 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java +@@ -0,0 +1,47 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import net.minecraft.network.chat.Component; ++ ++public final class V1803 { ++ ++ protected static final int VERSION = MCVersions.V1_13_2 + 172; ++ ++ private V1803() {} ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ ++ if (display == null) { ++ return null; ++ } ++ ++ final ListType lore = display.getList("Lore", ObjectType.STRING); ++ if (lore == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = lore.size(); i < len; ++i) { ++ lore.setString(i, Component.Serializer.toJson(Component.literal(lore.getString(i)))); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java +new file mode 100644 +index 0000000000000000000000000000000000000000..09955e0c2245d8d42ce6ae664ae81e97db8a85f2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java +@@ -0,0 +1,44 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1904 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 1; ++ ++ private V1904() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ocelot", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int catType = data.getInt("CatType"); ++ ++ if (catType == 0) { ++ final String owner = data.getString("Owner"); ++ final String ownerUUID = data.getString("OwnerUUID"); ++ if ((owner != null && owner.length() > 0) || (ownerUUID != null && ownerUUID.length() > 0)) { ++ data.setBoolean("Trusting", true); ++ } ++ } else if (catType > 0 && catType < 4) { ++ data.setString("id", "minecraft:cat"); ++ data.setString("OwnerUUID", data.getString("OwnerUUID", "")); ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("minecraft:cat"); ++ } ++ ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2eeec0d9cbd35ff20ba239ea7fd9c2f52f7e4f9e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java +@@ -0,0 +1,35 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1905 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 2; ++ ++ private V1905() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final String status = level.getString("Status"); ++ ++ if ("postprocessed".equals(status)) { ++ level.setString("Status", "fullchunk"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6358c2e0861a3743a3ea6d46a644870892256a79 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V1906 { ++ ++ protected static final int VERSION = MCVersions.V18W43C + 3; ++ ++ private V1906() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:barrel", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:smoker", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:blast_furnace", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:lectern", new DataWalkerItems("Book")); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b9cc2e4a2ae42e12ccf4e0b634fd74d3aad317ab +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java +@@ -0,0 +1,48 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V1911 { ++ ++ protected static final int VERSION = MCVersions.V18W46A + 1; ++ ++ private static final Map CHUNK_STATUS_REMAP = ImmutableMap.builder() ++ .put("structure_references", "empty") ++ .put("biomes", "empty") ++ .put("base", "surface") ++ .put("carved", "carvers") ++ .put("liquid_carved", "liquid_carvers") ++ .put("decorated", "features") ++ .put("lighted", "light") ++ .put("mobs_spawned", "spawn") ++ .put("finalized", "heightmaps") ++ .put("fullchunk", "full") ++ .build(); ++ ++ ++ private V1911() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final String status = level.getString("Status", "empty"); ++ level.setString("Status", CHUNK_STATUS_REMAP.getOrDefault(status, "empty")); ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8f5a48c4824080827d2dad057ae70dfd7a11818f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1914 { ++ ++ protected static final int VERSION = MCVersions.V18W48A; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:chest", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String lootTable = data.getString("LootTable"); ++ ++ if ("minecraft:chests/village_blacksmith".equals(lootTable)) { ++ data.setString("LootTable", "minecraft:chests/village/village_weaponsmith"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java +new file mode 100644 +index 0000000000000000000000000000000000000000..71538d858a681c91f7193003e0808cdb4fd1f847 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1917 { ++ ++ protected static final int VERSION = MCVersions.V18W49A + 1; ++ ++ private V1917() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getInt("CatType") == 9) { ++ data.setInt("CatType", 10); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java +new file mode 100644 +index 0000000000000000000000000000000000000000..28fc06da723792e9abc4999376c0941f9a835aff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java +@@ -0,0 +1,65 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1918 { ++ ++ protected static final int VERSION = MCVersions.V18W49A + 2; ++ ++ private V1918() {} ++ ++ private static String getProfessionString(final int professionId, final int careerId) { ++ if (professionId == 0) { ++ if (careerId == 2) { ++ return "minecraft:fisherman"; ++ } else if (careerId == 3) { ++ return "minecraft:shepherd"; ++ } else { ++ return careerId == 4 ? "minecraft:fletcher" : "minecraft:farmer"; ++ } ++ } else if (professionId == 1) { ++ return careerId == 2 ? "minecraft:cartographer" : "minecraft:librarian"; ++ } else if (professionId == 2) { ++ return "minecraft:cleric"; ++ } else if (professionId == 3) { ++ if (careerId == 2) { ++ return "minecraft:weaponsmith"; ++ } else { ++ return careerId == 3 ? "minecraft:toolsmith" : "minecraft:armorer"; ++ } ++ } else if (professionId == 4) { ++ return careerId == 2 ? "minecraft:leatherworker" : "minecraft:butcher"; ++ } else { ++ return professionId == 5 ? "minecraft:nitwit" : "minecraft:none"; ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int profession = data.getInt("Profession"); ++ final int career = data.getInt("Career"); ++ final int careerLevel = data.getInt("CareerLevel", 1); ++ data.remove("Profession"); ++ data.remove("Career"); ++ data.remove("CareerLevel"); ++ ++ final MapType villagerData = Types.NBT.createEmptyMap(); ++ data.setMap("VillagerData", villagerData); ++ villagerData.setString("type", "minecraft:plains"); ++ villagerData.setString("profession", getProfessionString(profession, career)); ++ villagerData.setInt("level", careerLevel); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", converter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", converter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java +new file mode 100644 +index 0000000000000000000000000000000000000000..224d35620e9d9e65f0642fdb13f80fcb2667a2ee +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java +@@ -0,0 +1,75 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++ ++public final class V1920 { ++ ++ protected static final int VERSION = MCVersions.V18W50A + 1; ++ ++ private V1920() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType structures = level.getMap("Structures"); ++ if (structures == null) { ++ return null; ++ } ++ ++ final MapType starts = structures.getMap("Starts"); ++ if (starts != null) { ++ final MapType village = starts.getMap("New_Village"); ++ if (village != null) { ++ starts.remove("New_Village"); ++ starts.setMap("Village", village); ++ } else { ++ starts.remove("Village"); ++ } ++ } ++ ++ final MapType references = structures.getMap("References"); ++ if (references != null) { ++ final MapType newVillage = references.getMap("New_Village"); ++ // I believe Mojang had a typo here, removing Village from references only made sense ++ // if the new village didn't exist. DFU removes it whether or not it exists, but still relocates ++ // New_Village to Village first. It doesn't make sense to me to relocate it just to remove it, so it ++ // must be a typo. ++ if (newVillage == null) { ++ references.remove("Village"); ++ } else { ++ references.remove("New_Village"); ++ references.setMap("Village", newVillage); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ ++ if ("minecraft:new_village".equals(NamespaceUtil.correctNamespace(id))) { ++ data.setString("id", "minecraft:village"); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:campfire", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java +new file mode 100644 +index 0000000000000000000000000000000000000000..19dc3d9b18d95d5f0e898d4c52c77a527066adf1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java +@@ -0,0 +1,29 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1925 { ++ ++ protected static final int VERSION = MCVersions.V19W03C + 1; ++ ++ public static void register() { ++ MCTypeRegistry.SAVED_DATA.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setMap("data", root); ++ ++ return ret; ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java +new file mode 100644 +index 0000000000000000000000000000000000000000..86caefba615a917d3530112edcc083caaaea4bb9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V1928 { ++ ++ protected static final int VERSION = MCVersions.V19W04B + 1; ++ ++ private V1928() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:illager_beast", "minecraft:ravager" ++ )::get); ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg" ++ )::get); ++ ++ registerMob("minecraft:ravager"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d58c32aa89e416e40cfef7c5840b772dd4991173 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V1929 { ++ ++ protected static final int VERSION = MCVersions.V19W04B + 2; ++ ++ private V1929() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:wandering_trader", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trader_llama", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "SaddleItem", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "DecorItem", fromVersion, toVersion); ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java +new file mode 100644 +index 0000000000000000000000000000000000000000..da845df91d42f1059490d749b235758850ce7254 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V1931 { ++ ++ protected static final int VERSION = MCVersions.V19W06A; ++ ++ private V1931() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:fox"); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4e7b22874f17f531b583146db3aa4e57bdd5f27c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1936 { ++ ++ protected static final int VERSION = MCVersions.V19W09A + 1; ++ ++ private V1936() {} ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String chatOpacity = data.getString("chatOpacity"); ++ if (chatOpacity != null) { ++ // Vanilla uses createDouble here, but options is always string -> string. I presume they made ++ // a mistake with this converter. ++ data.setString("textBackgroundOpacity", Double.toString(calculateBackground(chatOpacity))); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private static double calculateBackground(final String opacity) { ++ try { ++ final double d = 0.9D * Double.parseDouble(opacity) + 0.1D; ++ return d / 2.0D; ++ } catch (final NumberFormatException ex) { ++ return 0.5D; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java +new file mode 100644 +index 0000000000000000000000000000000000000000..00e4bd45b04feee990d9d3414c34c0966e65e4ea +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V1946 { ++ ++ protected static final int VERSION = MCVersions.V19W14B + 1; ++ ++ private V1946() {} ++ ++ public static void register() { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = Types.NBT.createEmptyMap(); ++ data.setMap("Sections", sections); ++ ++ for (int y = 0; y < 16; ++y) { ++ final String key = Integer.toString(y); ++ final Object records = data.getGeneric(key); ++ ++ if (records == null) { ++ continue; ++ } ++ ++ data.remove(key); ++ ++ final MapType section = Types.NBT.createEmptyMap(); ++ section.setGeneric("Records", records); ++ sections.setMap(key, section); // integer keys convert to string in DFU (at least for NBT ops) ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6b4af1fe7c53e6122d7db952770d14a753f8cab3 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1948 { ++ ++ protected static final int VERSION = MCVersions.V1_14_PRE2; ++ ++ private V1948() {} ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:white_banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType display = tag.getMap("display"); ++ if (display == null) { ++ return null; ++ } ++ ++ final String name = display.getString("Name"); ++ if (name == null) { ++ return null; ++ } ++ ++ display.setString("Name", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a73471e8f3df3b22349b2f842c3e98c2ff8bb5e1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1953 { ++ ++ protected static final int VERSION = MCVersions.V1_14 + 1; ++ ++ private V1953() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String name = data.getString("CustomName"); ++ if (name != null) { ++ data.setString("CustomName", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33bfe82709b507c4fd57199f5d8a44d131718d7f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java +@@ -0,0 +1,94 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.util.Mth; ++ ++public final class V1955 { ++ ++ protected static final int VERSION = MCVersions.V1_14_1_PRE1; ++ ++ private static final int[] LEVEL_XP_THRESHOLDS = new int[] { ++ 0, ++ 10, ++ 50, ++ 100, ++ 150 ++ }; ++ ++ private V1955() {} ++ ++ static int getMinXpPerLevel(final int level) { ++ return LEVEL_XP_THRESHOLDS[Mth.clamp(level - 1, 0, LEVEL_XP_THRESHOLDS.length - 1)]; ++ } ++ ++ static void addLevel(final MapType data, final int level) { ++ MapType villagerData = data.getMap("VillagerData"); ++ if (villagerData == null) { ++ villagerData = Types.NBT.createEmptyMap(); ++ data.setMap("VillagerData", villagerData); ++ } ++ villagerData.setInt("level", level); ++ } ++ ++ static void addXpFromLevel(final MapType data, final int level) { ++ data.setInt("Xp", getMinXpPerLevel(level)); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType villagerData = data.getMap("VillagerData"); ++ int level = villagerData == null ? 0 : villagerData.getInt("level"); ++ if (level == 0 || level == 1) { ++ // count recipes ++ final MapType offers = data.getMap("Offers"); ++ final ListType recipes = offers == null ? null : offers.getList("Recipes", ObjectType.MAP); ++ final int recipeCount; ++ if (recipes != null) { ++ recipeCount = recipes.size(); ++ } else { ++ recipeCount = 0; ++ } ++ ++ level = Mth.clamp(recipeCount / 2, 1, 5); ++ if (level > 1) { ++ addLevel(data, level); ++ } ++ } ++ ++ if (!data.hasKey("Xp", ObjectType.NUMBER)) { ++ addXpFromLevel(data, level); ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final Number xp = data.getNumber("Xp"); ++ if (xp == null) { ++ final int level; ++ final MapType villagerData = data.getMap("VillagerData"); ++ if (villagerData == null) { ++ level = 1; ++ } else { ++ level = villagerData.getInt("level", 1); ++ } ++ ++ data.setInt("Xp", getMinXpPerLevel(level)); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8bc6a8734034942a81b282b8766b21fddbe2b304 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V1961 { ++ ++ protected static final int VERSION = MCVersions.V1_14_2_PRE3 + 1; ++ ++ private V1961() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ level.remove("isLightOn"); ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e4e7299cec1d3809d1b55ae460e64ec6d2bb477 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java +@@ -0,0 +1,40 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V1963 { ++ ++ protected static final int VERSION = MCVersions.V1_14_2; ++ ++ private V1963() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType gossips = data.getList("Gossips", ObjectType.MAP); ++ if (gossips == null) { ++ return null; ++ } ++ ++ for (int i = 0; i < gossips.size();) { ++ final MapType gossip = gossips.getMap(i); ++ if ("golem".equals(gossip.getString("Type"))) { ++ gossips.remove(i); ++ continue; ++ } ++ ++ ++i; ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d905043c4f3071e8dc340ac2afe2b81aac345e1e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java +@@ -0,0 +1,46 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2100 { ++ ++ protected static final int VERSION = MCVersions.V1_14_4 + 124; ++ protected static final Map RECIPE_RENAMES = ImmutableMap.of( ++ "minecraft:sugar", "sugar_from_sugar_cane" ++ ); ++ ++ private V2100() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractRecipeRename.register(VERSION, RECIPE_RENAMES::get); ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane" ++ )::get); ++ ++ registerMob("minecraft:bee"); ++ registerMob("minecraft:bee_stinger"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:beehive", (data, fromVersion, toVersion) -> { ++ final ListType bees = data.getList("Bees", ObjectType.MAP); ++ if (bees != null) { ++ for (int i = 0, len = bees.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, bees.getMap(i), "EntityData", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0bb378ac8e8d0a087359361281644a7f39cecfbe +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java +@@ -0,0 +1,50 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2202 { ++ ++ protected static final int VERSION = MCVersions.V19W35A + 1; ++ ++ private V2202() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ final int[] oldBiomes = level.getInts("Biomes"); ++ ++ if (oldBiomes == null || oldBiomes.length != 256) { ++ return null; ++ } ++ ++ final int[] newBiomes = new int[1024]; ++ level.setInts("Biomes", newBiomes); ++ ++ int n; ++ for(n = 0; n < 4; ++n) { ++ for(int j = 0; j < 4; ++j) { ++ int k = (j << 2) + 2; ++ int l = (n << 2) + 2; ++ int m = l << 4 | k; ++ newBiomes[n << 2 | j] = oldBiomes[m]; ++ } ++ } ++ ++ for(n = 1; n < 64; ++n) { ++ System.arraycopy(newBiomes, 0, newBiomes, n * 16, 16); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6054cd31cb2e140f05b92736405a7d073be5cf5c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterAbstractPOIRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2209 { ++ ++ protected static final int VERSION = MCVersions.V19W40A + 1; ++ ++ private V2209() {} ++ ++ public static void register() { ++ final Map renamedIds = ImmutableMap.of( ++ "minecraft:bee_hive", "minecraft:beehive" ++ ); ++ ++ ConverterAbstractBlockRename.register(VERSION, renamedIds::get); ++ ConverterAbstractItemRename.register(VERSION, renamedIds::get); ++ ConverterAbstractPOIRename.register(VERSION, renamedIds::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6cff6d723616e0a38811872f7b5d28799240ddfe +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java +@@ -0,0 +1,32 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2211 { ++ ++ protected static final int VERSION = MCVersions.V19W41A + 1; ++ ++ private V2211() {} ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("references", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ final int references = data.getInt("references"); ++ if (references <= 0) { ++ data.setInt("references", 1); ++ } ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e06a98a01086c9d6eb9fc80a151f0403247b0033 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2218 { ++ ++ protected static final int VERSION = MCVersions.V1_15_PRE1; ++ ++ private V2218() {} ++ ++ public static void register() { ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType sections = data.getMap("Sections"); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (final String key : sections.keys()) { ++ final MapType section = sections.getMap(key); ++ ++ section.remove("Valid"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c8901f95e1076ae8be220c03efd83ce9bd18d2a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java +@@ -0,0 +1,65 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2501 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 271; ++ ++ private V2501() {} ++ ++ private static void registerFurnace(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, (data, fromVersion, toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); ++ ++ WalkerUtils.convertKeys(MCTypeRegistry.RECIPE, data, "RecipesUsed", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int recipesUsedSize = data.getInt("RecipesUsedSize"); ++ data.remove("RecipesUsedSize"); ++ ++ if (recipesUsedSize <= 0) { ++ return null; ++ } ++ ++ final MapType newRecipes = Types.NBT.createEmptyMap(); ++ data.setMap("RecipesUsed", newRecipes); ++ ++ for (int i = 0; i < recipesUsedSize; ++i) { ++ final String recipeKey = data.getString("RecipeLocation" + i); ++ data.remove("RecipeLocation" + i); ++ final int recipeAmount = data.getInt("RecipeAmount" + i); ++ data.remove("RecipeAmount" + i); ++ ++ if (i <= 0 || recipeKey == null) { ++ continue; ++ } ++ ++ newRecipes.setInt(recipeKey, recipeAmount); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:furnace", converter); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:blast_furnace", converter); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:smoker", converter); ++ ++ registerFurnace("minecraft:furnace"); ++ registerFurnace("minecraft:smoker"); ++ registerFurnace("minecraft:blast_furnace"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7075f7beb8aacfdadd68f4353d2cf32edaa1905d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2502 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 272; ++ ++ private V2502() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:hoglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cd1bca807236b917244bbacd8df6f25fd3c42407 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java +@@ -0,0 +1,67 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.ImmutableSet; ++import java.util.Set; ++ ++public final class V2503 { ++ ++ protected static final int VERSION = MCVersions.V1_15_2 + 273; ++ ++ private static final Set WALL_BLOCKS = ImmutableSet.of( ++ "minecraft:andesite_wall", ++ "minecraft:brick_wall", ++ "minecraft:cobblestone_wall", ++ "minecraft:diorite_wall", ++ "minecraft:end_stone_brick_wall", ++ "minecraft:granite_wall", ++ "minecraft:mossy_cobblestone_wall", ++ "minecraft:mossy_stone_brick_wall", ++ "minecraft:nether_brick_wall", ++ "minecraft:prismarine_wall", ++ "minecraft:red_nether_brick_wall", ++ "minecraft:red_sandstone_wall", ++ "minecraft:sandstone_wall", ++ "minecraft:stone_brick_wall" ++ ); ++ ++ private V2503() {} ++ ++ private static void changeWallProperty(final MapType properties, final String path) { ++ final String property = properties.getString(path); ++ if (property != null) { ++ properties.setString(path, "true".equals(property) ? "low" : "none"); ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!WALL_BLOCKS.contains(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ if (properties == null) { ++ return null; ++ } ++ ++ changeWallProperty(properties, "east"); ++ changeWallProperty(properties, "west"); ++ changeWallProperty(properties, "north"); ++ changeWallProperty(properties, "south"); ++ ++ return null; ++ } ++ }); ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java +new file mode 100644 +index 0000000000000000000000000000000000000000..350f5cca06d2a864b5a3cc028753fd6489a28ad5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java +@@ -0,0 +1,49 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2505 { ++ ++ protected static final int VERSION = MCVersions.V20W06A + 1; ++ ++ private V2505() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType brain = data.getMap("Brain"); ++ if (brain == null) { ++ return null; ++ } ++ ++ final MapType memories = brain.getMap("memories"); ++ if (memories == null) { ++ return null; ++ } ++ ++ for (final String key : memories.keys()) { ++ final Object value = memories.getGeneric(key); ++ ++ final MapType wrapped = Types.NBT.createEmptyMap(); ++ wrapped.setGeneric("value", value); ++ ++ memories.setMap(key, wrapped); ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("minecraft:piglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8cfd380e870ceea4892b8cd7bf855a6e5dc8cc6f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2508 { ++ ++ protected static final int VERSION = MCVersions.V20W08A + 1; ++ ++ private V2508() {} ++ ++ public static void register() { ++ final Map remap = ImmutableMap.of( ++ "minecraft:warped_fungi", "minecraft:warped_fungus", ++ "minecraft:crimson_fungi", "minecraft:crimson_fungus" ++ ); ++ ++ ConverterAbstractBlockRename.register(VERSION, remap::get); ++ ConverterAbstractItemRename.register(VERSION, remap::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1fa93dec8a81719a19c0f7db589778935ce44d56 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2509 { ++ ++ protected static final int VERSION = MCVersions.V20W08A + 2; ++ ++ private V2509() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg" ++ )::get); ++ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( ++ "minecraft:zombie_pigman", "minecraft:zombified_piglin" ++ )::get); ++ ++ registerMob("minecraft:zombified_piglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java +new file mode 100644 +index 0000000000000000000000000000000000000000..183ab7ed77e30bf87e71e5f682a59fc3a64a7672 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java +@@ -0,0 +1,97 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2511 { ++ ++ protected static final int VERSION = MCVersions.V20W09A + 1; ++ ++ private V2511() {} ++ ++ private static int[] createUUIDArray(final long most, final long least) { ++ return new int[] { ++ (int)(most >>> 32), ++ (int)most, ++ (int)(least >>> 32), ++ (int)least ++ }; ++ } ++ ++ private static void setUUID(final MapType data, final long most, final long least) { ++ if (most != 0L && least != 0L) { ++ data.setInts("OwnerUUID", createUUIDArray(most, least)); ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> throwableConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("owner"); ++ data.remove("owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ setUUID(data, owner.getLong("M"), owner.getLong("L")); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> potionConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType potion = data.getMap("Potion"); ++ data.remove("Potion"); ++ ++ data.setMap("Item", potion == null ? Types.NBT.createEmptyMap() : potion); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> llamaSpitConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("Owner"); ++ data.remove("Owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ setUUID(data, owner.getLong("OwnerUUIDMost"), owner.getLong("OwnerUUIDLeast")); ++ ++ return null; ++ } ++ }; ++ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ setUUID(data, data.getLong("OwnerUUIDMost"), data.getLong("OwnerUUIDLeast")); ++ ++ data.remove("OwnerUUIDMost"); ++ data.remove("OwnerUUIDLeast"); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:egg", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ender_pearl", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:experience_bottle", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:snowball", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", throwableConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", potionConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama_spit", llamaSpitConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); ++ ++ // Vanilla migrates the potion item but does not change the schema. ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Item")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d494fac0900f61e60c01617e631a0431bbda9438 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java +@@ -0,0 +1,590 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.Sets; ++import java.util.Set; ++import java.util.UUID; ++ ++public final class V2514 { ++ ++ protected static final int VERSION = MCVersions.V20W11A + 1; ++ ++ private static final Set ABSTRACT_HORSES = Sets.newHashSet(); ++ private static final Set TAMEABLE_ANIMALS = Sets.newHashSet(); ++ private static final Set ANIMALS = Sets.newHashSet(); ++ private static final Set MOBS = Sets.newHashSet(); ++ private static final Set LIVING_ENTITIES = Sets.newHashSet(); ++ private static final Set PROJECTILES = Sets.newHashSet(); ++ static { ++ ABSTRACT_HORSES.add("minecraft:donkey"); ++ ABSTRACT_HORSES.add("minecraft:horse"); ++ ABSTRACT_HORSES.add("minecraft:llama"); ++ ABSTRACT_HORSES.add("minecraft:mule"); ++ ABSTRACT_HORSES.add("minecraft:skeleton_horse"); ++ ABSTRACT_HORSES.add("minecraft:trader_llama"); ++ ABSTRACT_HORSES.add("minecraft:zombie_horse"); ++ ++ TAMEABLE_ANIMALS.add("minecraft:cat"); ++ TAMEABLE_ANIMALS.add("minecraft:parrot"); ++ TAMEABLE_ANIMALS.add("minecraft:wolf"); ++ ++ ANIMALS.add("minecraft:bee"); ++ ANIMALS.add("minecraft:chicken"); ++ ANIMALS.add("minecraft:cow"); ++ ANIMALS.add("minecraft:fox"); ++ ANIMALS.add("minecraft:mooshroom"); ++ ANIMALS.add("minecraft:ocelot"); ++ ANIMALS.add("minecraft:panda"); ++ ANIMALS.add("minecraft:pig"); ++ ANIMALS.add("minecraft:polar_bear"); ++ ANIMALS.add("minecraft:rabbit"); ++ ANIMALS.add("minecraft:sheep"); ++ ANIMALS.add("minecraft:turtle"); ++ ANIMALS.add("minecraft:hoglin"); ++ ++ MOBS.add("minecraft:bat"); ++ MOBS.add("minecraft:blaze"); ++ MOBS.add("minecraft:cave_spider"); ++ MOBS.add("minecraft:cod"); ++ MOBS.add("minecraft:creeper"); ++ MOBS.add("minecraft:dolphin"); ++ MOBS.add("minecraft:drowned"); ++ MOBS.add("minecraft:elder_guardian"); ++ MOBS.add("minecraft:ender_dragon"); ++ MOBS.add("minecraft:enderman"); ++ MOBS.add("minecraft:endermite"); ++ MOBS.add("minecraft:evoker"); ++ MOBS.add("minecraft:ghast"); ++ MOBS.add("minecraft:giant"); ++ MOBS.add("minecraft:guardian"); ++ MOBS.add("minecraft:husk"); ++ MOBS.add("minecraft:illusioner"); ++ MOBS.add("minecraft:magma_cube"); ++ MOBS.add("minecraft:pufferfish"); ++ MOBS.add("minecraft:zombified_piglin"); ++ MOBS.add("minecraft:salmon"); ++ MOBS.add("minecraft:shulker"); ++ MOBS.add("minecraft:silverfish"); ++ MOBS.add("minecraft:skeleton"); ++ MOBS.add("minecraft:slime"); ++ MOBS.add("minecraft:snow_golem"); ++ MOBS.add("minecraft:spider"); ++ MOBS.add("minecraft:squid"); ++ MOBS.add("minecraft:stray"); ++ MOBS.add("minecraft:tropical_fish"); ++ MOBS.add("minecraft:vex"); ++ MOBS.add("minecraft:villager"); ++ MOBS.add("minecraft:iron_golem"); ++ MOBS.add("minecraft:vindicator"); ++ MOBS.add("minecraft:pillager"); ++ MOBS.add("minecraft:wandering_trader"); ++ MOBS.add("minecraft:witch"); ++ MOBS.add("minecraft:wither"); ++ MOBS.add("minecraft:wither_skeleton"); ++ MOBS.add("minecraft:zombie"); ++ MOBS.add("minecraft:zombie_villager"); ++ MOBS.add("minecraft:phantom"); ++ MOBS.add("minecraft:ravager"); ++ MOBS.add("minecraft:piglin"); ++ ++ LIVING_ENTITIES.add("minecraft:armor_stand"); ++ ++ PROJECTILES.add("minecraft:arrow"); ++ PROJECTILES.add("minecraft:dragon_fireball"); ++ PROJECTILES.add("minecraft:firework_rocket"); ++ PROJECTILES.add("minecraft:fireball"); ++ PROJECTILES.add("minecraft:llama_spit"); ++ PROJECTILES.add("minecraft:small_fireball"); ++ PROJECTILES.add("minecraft:snowball"); ++ PROJECTILES.add("minecraft:spectral_arrow"); ++ PROJECTILES.add("minecraft:egg"); ++ PROJECTILES.add("minecraft:ender_pearl"); ++ PROJECTILES.add("minecraft:experience_bottle"); ++ PROJECTILES.add("minecraft:potion"); ++ PROJECTILES.add("minecraft:trident"); ++ PROJECTILES.add("minecraft:wither_skull"); ++ } ++ ++ static int[] createUUIDArray(final long most, final long least) { ++ return new int[] { ++ (int)(most >>> 32), ++ (int)most, ++ (int)(least >>> 32), ++ (int)least ++ }; ++ } ++ ++ static int[] createUUIDFromString(final MapType data, final String path) { ++ if (data == null) { ++ return null; ++ } ++ ++ final String uuidString = data.getString(path); ++ if (uuidString == null) { ++ return null; ++ } ++ ++ try { ++ final UUID uuid = UUID.fromString(uuidString); ++ return createUUIDArray(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); ++ } catch (final IllegalArgumentException ignore) { ++ return null; ++ } ++ } ++ ++ static int[] createUUIDFromLongs(final MapType data, final String most, final String least) { ++ if (data == null) { ++ return null; ++ } ++ ++ final long mostBits = data.getLong(most); ++ final long leastBits = data.getLong(least); ++ ++ return (mostBits != 0 || leastBits != 0) ? createUUIDArray(mostBits, leastBits) : null; ++ } ++ ++ static void replaceUUIDString(final MapType data, final String oldPath, final String newPath) { ++ final int[] newUUID = createUUIDFromString(data, oldPath); ++ if (newUUID != null) { ++ data.remove(oldPath); ++ data.setInts(newPath, newUUID); ++ } ++ } ++ ++ static void replaceUUIDMLTag(final MapType data, final String oldPath, final String newPath) { ++ final int[] uuid = createUUIDFromLongs(data.getMap(oldPath), "M", "L"); ++ if (uuid != null) { ++ data.remove(oldPath); ++ data.setInts(newPath, uuid); ++ } ++ } ++ ++ static void replaceUUIDLeastMost(final MapType data, final String prefix, final String newPath) { ++ final String mostPath = prefix.concat("Most"); ++ final String leastPath = prefix.concat("Least"); ++ ++ final int[] uuid = createUUIDFromLongs(data, mostPath, leastPath); ++ if (uuid != null) { ++ data.remove(mostPath); ++ data.remove(leastPath); ++ data.setInts(newPath, uuid); ++ } ++ } ++ ++ private V2514() {} ++ ++ private static void updatePiglin(final MapType data) { ++ final MapType brain = data.getMap("Brain"); ++ if (brain == null) { ++ return; ++ } ++ ++ final MapType memories = brain.getMap("memories"); ++ if (memories == null) { ++ return; ++ } ++ ++ final MapType angryAt = memories.getMap("minecraft:angry_at"); ++ ++ replaceUUIDString(angryAt, "value", "value"); ++ } ++ ++ private static void updateEvokerFangs(final MapType data) { ++ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateZombieVillager(final MapType data) { ++ replaceUUIDLeastMost(data, "ConversionPlayer", "ConversionPlayer"); ++ } ++ ++ private static void updateAreaEffectCloud(final MapType data) { ++ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateShulkerBullet(final MapType data) { ++ replaceUUIDMLTag(data, "Owner", "Owner"); ++ replaceUUIDMLTag(data, "Target", "Target"); ++ } ++ ++ private static void updateItem(final MapType data) { ++ replaceUUIDMLTag(data, "Owner", "Owner"); ++ replaceUUIDMLTag(data, "Thrower", "Thrower"); ++ } ++ ++ private static void updateFox(final MapType data) { ++ final ListType trustedUUIDS = data.getList("TrustedUUIDs", ObjectType.MAP); ++ if (trustedUUIDS == null) { ++ return; ++ } ++ ++ final ListType newUUIDs = Types.NBT.createEmptyList(); ++ data.remove("TrustedUUIDs"); ++ data.setList("Trusted", newUUIDs); ++ ++ for (int i = 0, len = trustedUUIDS.size(); i < len; ++i) { ++ final MapType uuid = trustedUUIDS.getMap(i); ++ final int[] newUUID = createUUIDFromLongs(uuid, "M", "L"); ++ if (newUUID != null) { ++ newUUIDs.addIntArray(newUUID); ++ } ++ } ++ } ++ ++ private static void updateHurtBy(final MapType data) { ++ replaceUUIDString(data, "HurtBy", "HurtBy"); ++ } ++ ++ private static void updateAnimalOwner(final MapType data) { ++ updateAnimal(data); ++ ++ replaceUUIDString(data, "OwnerUUID", "Owner"); ++ } ++ ++ private static void updateAnimal(final MapType data) { ++ updateMob(data); ++ ++ replaceUUIDLeastMost(data, "LoveCause", "LoveCause"); ++ } ++ ++ private static void updateMob(final MapType data) { ++ updateLivingEntity(data); ++ ++ final MapType leash = data.getMap("Leash"); ++ if (leash == null) { ++ return; ++ } ++ ++ replaceUUIDLeastMost(leash, "UUID", "UUID"); ++ } ++ ++ private static void updateLivingEntity(final MapType data) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ if (attributes == null) { ++ return; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ final MapType attribute = attributes.getMap(i); ++ ++ final ListType modifiers = attribute.getList("Modifiers", ObjectType.MAP); ++ if (modifiers == null) { ++ continue; ++ } ++ ++ for (int k = 0; k < modifiers.size(); ++k) { ++ replaceUUIDLeastMost(modifiers.getMap(k), "UUID", "UUID"); ++ } ++ } ++ } ++ ++ private static void updateProjectile(final MapType data) { ++ final Object ownerUUID = data.getGeneric("OwnerUUID"); ++ if (ownerUUID != null) { ++ data.remove("OwnerUUID"); ++ data.setGeneric("Owner", ownerUUID); ++ } ++ } ++ ++ private static void updateEntityUUID(final MapType data) { ++ replaceUUIDLeastMost(data, "UUID", "UUID"); ++ } ++ ++ public static void register() { ++ // Entity UUID fixes ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateEntityUUID(data); ++ return null; ++ } ++ }); ++ ++ final DataConverter, MapType> animalOwnerConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAnimalOwner(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> animalConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAnimal(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> mobConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateMob(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLivingEntity(data); ++ return null; ++ } ++ }; ++ final DataConverter, MapType> projectileConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateProjectile(data); ++ return null; ++ } ++ }; ++ for (final String id : ABSTRACT_HORSES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); ++ } ++ for (final String id : TAMEABLE_ANIMALS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); ++ } ++ for (final String id : ANIMALS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, animalConverter); ++ } ++ for (final String id : MOBS) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, mobConverter); ++ } ++ for (final String id : LIVING_ENTITIES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, livingEntityConverter); ++ } ++ for (final String id : PROJECTILES) { ++ MCTypeRegistry.ENTITY.addConverterForId(id, projectileConverter); ++ } ++ ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:bee", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateHurtBy(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombified_piglin", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateHurtBy(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:fox", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateFox(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateItem(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker_bullet", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateShulkerBullet(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateAreaEffectCloud(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateZombieVillager(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:evoker_fangs", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateEvokerFangs(data); ++ return null; ++ } ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:piglin", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updatePiglin(data); ++ return null; ++ } ++ }); ++ ++ ++ // Update TE ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:conduit", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ replaceUUIDMLTag(data, "target_uuid", "Target"); ++ return null; ++ } ++ }); ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:skull", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType owner = data.getMap("Owner"); ++ if (owner == null) { ++ return null; ++ } ++ ++ data.remove("Owner"); ++ ++ replaceUUIDString(owner, "Id", "Id"); ++ ++ data.setMap("SkullOwner", owner); ++ ++ return null; ++ } ++ }); ++ ++ // Player UUID ++ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ updateLivingEntity(data); ++ updateEntityUUID(data); ++ ++ final MapType rootVehicle = data.getMap("RootVehicle"); ++ if (rootVehicle == null) { ++ return null; ++ } ++ ++ replaceUUIDLeastMost(rootVehicle, "Attach", "Attach"); ++ ++ return null; ++ } ++ }); ++ ++ // Level.dat ++ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ replaceUUIDString(data, "WanderingTraderId", "WanderingTraderId"); ++ ++ final MapType dimensionData = data.getMap("DimensionData"); ++ if (dimensionData != null) { ++ for (final String key : dimensionData.keys()) { ++ final MapType dimension = dimensionData.getMap(key); ++ ++ final MapType dragonFight = dimension.getMap("DragonFight"); ++ if (dragonFight == null) { ++ continue; ++ } ++ ++ replaceUUIDLeastMost(dragonFight, "DragonUUID", "Dragon"); ++ } ++ } ++ ++ final MapType customBossEvents = data.getMap("CustomBossEvents"); ++ if (customBossEvents != null) { ++ for (final String key : customBossEvents.keys()) { ++ final MapType customBossEvent = customBossEvents.getMap(key); ++ ++ final ListType players = customBossEvent.getList("Players", ObjectType.MAP); ++ if (players == null) { ++ continue; ++ } ++ ++ final ListType newPlayers = Types.NBT.createEmptyList(); ++ customBossEvent.setList("Players", newPlayers); ++ ++ for (int i = 0, len = players.size(); i < len; ++i) { ++ final int[] newUUID = createUUIDFromLongs(players.getMap(i), "M", "L"); ++ if (newUUID != null) { ++ newPlayers.addIntArray(newUUID); ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.SAVED_DATA.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ final ListType raids = data.getList("Raids", ObjectType.MAP); ++ if (raids == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = raids.size(); i < len; ++i) { ++ final MapType raid = raids.getMap(i); ++ ++ final ListType heros = raid.getList("HeroesOfTheVillage", ObjectType.MAP); ++ ++ if (heros == null) { ++ continue; ++ } ++ ++ final ListType newHeros = Types.NBT.createEmptyList(); ++ raid.setList("HeroesOfTheVillage", newHeros); ++ ++ for (int k = 0, klen = heros.size(); k < klen; ++k) { ++ final MapType uuidOld = heros.getMap(i); ++ final int[] uuidNew = createUUIDFromLongs(uuidOld, "UUIDMost", "UUIDLeast"); ++ if (uuidNew != null) { ++ newHeros.addIntArray(uuidNew); ++ } ++ } ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ updateAttributeModifiers(tag); ++ ++ if ("minecraft:player_head".equals(data.getString("id"))) { ++ updateSkullOwner(tag); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static void updateAttributeModifiers(final MapType tag) { ++ final ListType attributes = tag.getList("AttributeModifiers", ObjectType.MAP); ++ if (attributes == null) { ++ return; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ replaceUUIDLeastMost(attributes.getMap(i), "UUID", "UUID"); ++ } ++ } ++ ++ private static void updateSkullOwner(final MapType tag) { ++ replaceUUIDString(tag.getMap("SkullOwner"), "Id", "Id"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java +new file mode 100644 +index 0000000000000000000000000000000000000000..40bf0a1788520bbf1d66da53b6532bdd8af246f4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java +@@ -0,0 +1,37 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2516 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 1; ++ ++ private V2516() {} ++ ++ public static void register() { ++ final DataConverter, MapType> gossipUUIDConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType gossips = data.getList("Gossips", ObjectType.MAP); ++ ++ if (gossips == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = gossips.size(); i < len; ++i) { ++ V2514.replaceUUIDLeastMost(gossips.getMap(i), "Target", "Target"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", gossipUUIDConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", gossipUUIDConverter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7a55eeb02fb99289e4c8bfe2d28fc4a0c716719 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java +@@ -0,0 +1,63 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2518 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 3; ++ ++ private static final Map FACING_RENAMES = ImmutableMap.builder() ++ .put("down", "down_south") ++ .put("up", "up_north") ++ .put("north", "north_up") ++ .put("south", "south_up") ++ .put("west", "west_up") ++ .put("east", "east_up") ++ .build(); ++ ++ ++ private V2518() {} ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String type = data.getString("attachement_type", "minecraft:empty"); ++ final String pool = data.getString("target_pool", "minecraft:empty"); ++ data.remove("attachement_type"); ++ data.remove("target_pool"); ++ ++ data.setString("name", type); ++ data.setString("target", type); ++ data.setString("pool", pool); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:jigsaw".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ if (properties == null) { ++ return null; ++ } ++ ++ final String facing = properties.getString("facing", "north"); ++ properties.remove("facing"); ++ properties.setString("orientation", FACING_RENAMES.getOrDefault(facing, facing)); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java +new file mode 100644 +index 0000000000000000000000000000000000000000..47a8bb340e72e48fd237d4001b55c81b1182a72f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2519 { ++ ++ protected static final int VERSION = MCVersions.V20W12A + 4; ++ ++ private V2519() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:strider"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3a91600427cb013cdfc03b084017b6f32d9e05fa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2522 { ++ ++ protected static final int VERSION = MCVersions.V20W13B + 1; ++ ++ private V2522() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:zoglin"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5d3726bb7670bc89feb8ebeed5c097a77e909f5a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java +@@ -0,0 +1,92 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2523 { ++ ++ protected static final int VERSION = MCVersions.V20W13B + 2; ++ ++ private static final Map RENAMES = ImmutableMap.builder() ++ .put("generic.maxHealth", "generic.max_health") ++ .put("Max Health", "generic.max_health") ++ .put("zombie.spawnReinforcements", "zombie.spawn_reinforcements") ++ .put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements") ++ .put("horse.jumpStrength", "horse.jump_strength") ++ .put("Jump Strength", "horse.jump_strength") ++ .put("generic.followRange", "generic.follow_range") ++ .put("Follow Range", "generic.follow_range") ++ .put("generic.knockbackResistance", "generic.knockback_resistance") ++ .put("Knockback Resistance", "generic.knockback_resistance") ++ .put("generic.movementSpeed", "generic.movement_speed") ++ .put("Movement Speed", "generic.movement_speed") ++ .put("generic.flyingSpeed", "generic.flying_speed") ++ .put("Flying Speed", "generic.flying_speed") ++ .put("generic.attackDamage", "generic.attack_damage") ++ .put("generic.attackKnockback", "generic.attack_knockback") ++ .put("generic.attackSpeed", "generic.attack_speed") ++ .put("generic.armorToughness", "generic.armor_toughness") ++ .build(); ++ ++ private V2523() {} ++ ++ private static void updateName(final MapType data, final String path) { ++ if (data == null) { ++ return; ++ } ++ ++ final String name = data.getString(path); ++ if (name != null) { ++ final String renamed = RENAMES.get(name); ++ if (renamed != null) { ++ data.setString(path, renamed); ++ } ++ } ++ } ++ ++ public static void register() { ++ final DataConverter, MapType> entityConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ updateName(attributes.getMap(i), "Name"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addStructureConverter(entityConverter); ++ MCTypeRegistry.PLAYER.addStructureConverter(entityConverter); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ updateName(attributes.getMap(i), "AttributeName"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5e951f91d03f95ed671bb7403592960690c65879 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java +@@ -0,0 +1,123 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.mojang.datafixers.DataFixUtils; ++import net.minecraft.util.Mth; ++ ++public final class V2527 { ++ ++ protected static final int VERSION = MCVersions.V20W16A + 1; ++ ++ private V2527() {} ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ ++ if (palette == null) { ++ continue; ++ } ++ ++ final int bits = Math.max(4, DataFixUtils.ceillog2(palette.size())); ++ ++ if (Mth.isPowerOfTwo(bits)) { ++ // fits perfectly ++ continue; ++ } ++ ++ final long[] states = section.getLongs("BlockStates"); ++ if (states == null) { ++ // wat ++ continue; ++ } ++ ++ section.setLongs("BlockStates", addPadding(4096, bits, states)); ++ } ++ } ++ ++ final MapType heightMaps = level.getMap("Heightmaps"); ++ if (heightMaps != null) { ++ for (final String key : heightMaps.keys()) { ++ final long[] old = heightMaps.getLongs(key); ++ heightMaps.setLongs(key, addPadding(256, 9, old)); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static long[] addPadding(final int indices, final int bits, final long[] old) { ++ int k = old.length; ++ if (k == 0) { ++ return old; ++ } else { ++ long l = (1L << bits) - 1L; ++ int m = 64 / bits; ++ int n = (indices + m - 1) / m; ++ long[] padded = new long[n]; ++ int o = 0; ++ int p = 0; ++ long q = 0L; ++ int r = 0; ++ long s = old[0]; ++ long t = k > 1 ? old[1] : 0L; ++ ++ for(int u = 0; u < indices; ++u) { ++ int v = u * bits; ++ int w = v >> 6; ++ int x = (u + 1) * bits - 1 >> 6; ++ int y = v ^ w << 6; ++ if (w != r) { ++ s = t; ++ t = w + 1 < k ? old[w + 1] : 0L; ++ r = w; ++ } ++ ++ long ab; ++ int ac; ++ if (w == x) { ++ ab = s >>> y & l; ++ } else { ++ ac = 64 - y; ++ ab = (s >>> y | t << ac) & l; ++ } ++ ++ ac = p + bits; ++ if (ac >= 64) { ++ padded[o++] = q; ++ q = ab; ++ p = bits; ++ } else { ++ q |= ab << p; ++ p = ac; ++ } ++ } ++ ++ if (q != 0L) { ++ padded[o] = q; ++ } ++ ++ return padded; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f7f2e3f75a9f8a5533fe010bc23e8384878d7ce8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2528 { ++ ++ protected static final int VERSION = MCVersions.V20W16A + 2; ++ ++ private V2528() {} ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:soul_fire_torch", "minecraft:soul_torch", ++ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" ++ )::get); ++ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( ++ "minecraft:soul_fire_torch", "minecraft:soul_torch", ++ "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch", ++ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f239dbf4beb87efd1fff4e5d8d6f041fa8687f01 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java +@@ -0,0 +1,25 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2529 { ++ ++ protected static final int VERSION = MCVersions.V20W17A; ++ ++ private V2529() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:strider", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("NoGravity")) { ++ data.setBoolean("NoGravity", false); ++ } ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7783d75578d29e09029b26c8c8c0b053c5526eb9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java +@@ -0,0 +1,63 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2531 { ++ ++ protected static final int VERSION = MCVersions.V20W17A + 2; ++ ++ private V2531() {} ++ ++ private static boolean isConnected(final String facing) { ++ return !"none".equals(facing); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:redstone_wire".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ ++ if (properties == null) { ++ return null; ++ } ++ ++ ++ final String east = properties.getString("east", "none"); ++ final String west = properties.getString("west", "none"); ++ final String north = properties.getString("north", "none"); ++ final String south = properties.getString("south", "none"); ++ ++ final boolean connectedX = isConnected(east) || isConnected(west); ++ final boolean connectedZ = isConnected(north) || isConnected(south); ++ ++ final String newEast = !isConnected(east) && !connectedZ ? "side" : east; ++ final String newWest = !isConnected(west) && !connectedZ ? "side" : west; ++ final String newNorth = !isConnected(north) && !connectedX ? "side" : north; ++ final String newSouth = !isConnected(south) && !connectedX ? "side" : south; ++ ++ if (properties.hasKey("east")) { ++ properties.setString("east", newEast); ++ } ++ if (properties.hasKey("west")) { ++ properties.setString("west", newWest); ++ } ++ if (properties.hasKey("north")) { ++ properties.setString("north", newNorth); ++ } ++ if (properties.hasKey("south")) { ++ properties.setString("south", newSouth); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ece1cd5afab80a8271b2ebac95dcc0a6239cd42c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java +@@ -0,0 +1,43 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2533 { ++ ++ protected static final int VERSION = MCVersions.V20W18A + 1; ++ ++ private V2533() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType attributes = data.getList("Attributes", ObjectType.MAP); ++ ++ if (attributes == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = attributes.size(); i < len; ++i) { ++ final MapType attribute = attributes.getMap(i); ++ ++ if (!"generic.follow_range".equals(attribute.getString("Name"))) { ++ continue; ++ } ++ ++ if (attribute.getDouble("Base") == 16.0) { ++ attribute.setDouble("Base", 48.0); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9648299bb96c20c783bb7c7010173a0f007584e0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2535 { ++ ++ protected static final int VERSION = MCVersions.V20W19A + 1; ++ ++ private V2535() {} ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // Mojang uses doubles for whatever reason... rotation is in FLOAT. by using double here ++ // the entity load will just ignore rotation and set it to 0... ++ final ListType rotation = data.getList("Rotation", ObjectType.FLOAT); ++ ++ if (rotation == null || rotation.size() == 0) { ++ return null; ++ } ++ ++ rotation.setFloat(0, rotation.getFloat(0) - 180.0F); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b9a50d44982abe228c5e7d58a4b917d0fbfda6b9 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java +@@ -0,0 +1,342 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import org.apache.commons.lang3.math.NumberUtils; ++import java.util.HashMap; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V2550 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 13; ++ ++ private static final ImmutableMap DEFAULTS = ImmutableMap.builder() ++ .put("minecraft:village", new StructureFeatureConfiguration(32, 8, 10387312)) ++ .put("minecraft:desert_pyramid", new StructureFeatureConfiguration(32, 8, 14357617)) ++ .put("minecraft:igloo", new StructureFeatureConfiguration(32, 8, 14357618)) ++ .put("minecraft:jungle_pyramid", new StructureFeatureConfiguration(32, 8, 14357619)) ++ .put("minecraft:swamp_hut", new StructureFeatureConfiguration(32, 8, 14357620)) ++ .put("minecraft:pillager_outpost", new StructureFeatureConfiguration(32, 8, 165745296)) ++ .put("minecraft:monument", new StructureFeatureConfiguration(32, 5, 10387313)) ++ .put("minecraft:endcity", new StructureFeatureConfiguration(20, 11, 10387313)) ++ .put("minecraft:mansion", new StructureFeatureConfiguration(80, 20, 10387319)) ++ .build(); ++ ++ record StructureFeatureConfiguration(int spacing, int separation, int salt) { ++ ++ public MapType serialize() { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setInt("spacing", this.spacing); ++ ret.setInt("separation", this.separation); ++ ret.setInt("salt", this.salt); ++ ++ return ret; ++ } ++ } ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final long seed = data.getLong("RandomSeed"); ++ String generatorName = data.getString("generatorName"); ++ if (generatorName != null) { ++ generatorName = generatorName.toLowerCase(Locale.ROOT); ++ } ++ String legacyCustomOptions = data.getString("legacy_custom_options"); ++ if (legacyCustomOptions == null) { ++ legacyCustomOptions = "customized".equals(generatorName) ? data.getString("generatorOptions") : null; ++ } ++ ++ final MapType generator; ++ boolean caves = false; ++ ++ if ("customized".equals(generatorName) || generatorName == null) { ++ generator = defaultOverworld(seed); ++ } else { ++ switch (generatorName) { ++ case "flat": { ++ final MapType generatorOptions = data.getMap("generatorOptions"); ++ ++ final MapType structures = fixFlatStructures(generatorOptions); ++ final MapType settings = Types.NBT.createEmptyMap(); ++ generator = Types.NBT.createEmptyMap(); ++ generator.setString("type", "minecraft:flat"); ++ generator.setMap("settings", settings); ++ ++ settings.setMap("structures", structures); ++ ++ ListType layers = generatorOptions.getList("layers", ObjectType.MAP); ++ if (layers == null) { ++ layers = Types.NBT.createEmptyList(); ++ ++ final int[] heights = new int[] { 1, 2, 1 }; ++ final String[] blocks = new String[] { "minecraft:bedrock", "minecraft:dirt", "minecraft:grass_block" }; ++ for (int i = 0; i < 3; ++i) { ++ final MapType layer = Types.NBT.createEmptyMap(); ++ layer.setInt("height", heights[i]); ++ layer.setString("block", blocks[i]); ++ layers.addMap(layer); ++ } ++ } ++ ++ settings.setList("layers", layers); ++ settings.setString("biome", generatorOptions.getString("biome", "minecraft:plains")); ++ ++ break; ++ } ++ ++ case "debug_all_block_states": { ++ generator = Types.NBT.createEmptyMap(); ++ generator.setString("type", "minecraft:debug"); ++ break; ++ } ++ ++ case "buffet": { ++ final MapType generatorOptions = data.getMap("generatorOptions"); ++ final MapType chunkGenerator = generatorOptions == null ? null : generatorOptions.getMap("chunk_generator"); ++ final String chunkGeneratorType = chunkGenerator == null ? null : chunkGenerator.getString("type"); ++ ++ final String newType; ++ if ("minecraft:caves".equals(chunkGeneratorType)) { ++ newType = "minecraft:caves"; ++ caves = true; ++ } else if ("minecraft:floating_islands".equals(chunkGeneratorType)) { ++ newType = "minecraft:floating_islands"; ++ } else { ++ newType = "minecraft:overworld"; ++ } ++ ++ MapType biomeSource = generatorOptions == null ? null : generatorOptions.getMap("biome_source"); ++ if (biomeSource == null) { ++ biomeSource = Types.NBT.createEmptyMap(); ++ biomeSource.setString("type", "minecraft:fixed"); ++ } ++ ++ if ("minecraft:fixed".equals(biomeSource.getString("type"))) { ++ final MapType options = biomeSource.getMap("options"); ++ final ListType biomes = options == null ? null : options.getList("biomes", ObjectType.STRING); ++ final String biome = biomes == null || biomes.size() == 0 ? "minecraft:ocean" : biomes.getString(0); ++ biomeSource.remove("options"); ++ biomeSource.setString("biome", biome); ++ } ++ ++ generator = noise(seed, newType, biomeSource); ++ break; ++ } ++ ++ default: { ++ boolean defaultGen = generatorName.equals("default"); ++ boolean default11Gen = generatorName.equals("default_1_1") || defaultGen && data.getInt("generatorVersion") == 0; ++ boolean amplified = generatorName.equals("amplified"); ++ boolean largeBiomes = generatorName.equals("largebiomes"); ++ ++ generator = noise(seed, amplified ? "minecraft:amplified" : "minecraft:overworld", ++ vanillaBiomeSource(seed, default11Gen, largeBiomes)); ++ break; ++ } ++ } ++ } ++ ++ final boolean mapFeatures = data.getBoolean("MapFeatures", true); ++ final boolean bonusChest = data.getBoolean("BonusChest", false); ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setLong("seed", seed); ++ ret.setBoolean("generate_features", mapFeatures); ++ ret.setBoolean("bonus_chest", bonusChest); ++ ret.setMap("dimensions", vanillaLevels(seed, generator, caves)); ++ if (legacyCustomOptions != null) { ++ ret.setString("legacy_custom_options", legacyCustomOptions); ++ } ++ ++ return ret; ++ } ++ }); ++ } ++ ++ public static MapType noise(final long seed, final String worldType, final MapType biomeSource) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("type", "minecraft:noise"); ++ ret.setMap("biome_source", biomeSource); ++ ret.setLong("seed", seed); ++ ret.setString("settings", worldType); ++ ++ return ret; ++ } ++ ++ public static MapType vanillaBiomeSource(final long seed, final boolean default11Gen, final boolean largeBiomes) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("type", "minecraft:vanilla_layered"); ++ ret.setLong("seed", seed); ++ ret.setBoolean("large_biomes", largeBiomes); ++ if (default11Gen) { ++ ret.setBoolean("legacy_biome_init_layer", default11Gen); ++ } ++ ++ return ret; ++ } ++ ++ public static MapType fixFlatStructures(final MapType generatorOptions) { ++ int distance = 32; ++ int spread = 3; ++ int count = 128; ++ boolean stronghold = false; ++ final Map newStructures = new HashMap<>(); ++ ++ if (generatorOptions == null) { ++ stronghold = true; ++ newStructures.put("minecraft:village", DEFAULTS.get("minecraft:village")); ++ } ++ ++ final MapType oldStructures = generatorOptions == null ? null : generatorOptions.getMap("structures"); ++ if (oldStructures != null) { ++ for (final String structureName : oldStructures.keys()) { ++ final MapType structureValues = oldStructures.getMap(structureName); ++ if (structureValues == null) { ++ continue; ++ } ++ ++ for (final String structureValueKey : structureValues.keys()) { ++ final String structureValue = structureValues.getString(structureValueKey); ++ ++ if ("stronghold".equals(structureName)) { ++ stronghold = true; ++ switch (structureValueKey) { ++ case "distance": ++ distance = getInt(structureValue, distance, 1); ++ break; ++ case "spread": ++ spread = getInt(structureValue, spread, 1); ++ break; ++ case "count": ++ count = getInt(structureValue, count, 1); ++ break; ++ } ++ } else { ++ switch (structureValueKey) { ++ case "distance": ++ switch (structureName) { ++ case "village": ++ setSpacing(newStructures, "minecraft:village", structureValue, 9); ++ break; ++ case "biome_1": ++ setSpacing(newStructures, "minecraft:desert_pyramid", structureValue, 9); ++ setSpacing(newStructures, "minecraft:igloo", structureValue, 9); ++ setSpacing(newStructures, "minecraft:jungle_pyramid", structureValue, 9); ++ setSpacing(newStructures, "minecraft:swamp_hut", structureValue, 9); ++ setSpacing(newStructures, "minecraft:pillager_outpost", structureValue, 9); ++ break; ++ case "endcity": ++ setSpacing(newStructures, "minecraft:endcity", structureValue, 1); ++ break; ++ case "mansion": ++ setSpacing(newStructures, "minecraft:mansion", structureValue, 1); ++ break; ++ default: ++ break; ++ } ++ case "separation": ++ if ("oceanmonument".equals(structureName)) { ++ final StructureFeatureConfiguration structure = newStructures.getOrDefault("minecraft:monument", DEFAULTS.get("minecraft:monument")); ++ final int newSpacing = getInt(structureValue, structure.separation, 1); ++ newStructures.put("minecraft:monument", new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); ++ } ++ ++ break; ++ case "spacing": ++ if ("oceanmonument".equals(structureName)) { ++ setSpacing(newStructures, "minecraft:monument", structureValue, 1); ++ } ++ ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ final MapType structuresSerialized = Types.NBT.createEmptyMap(); ++ ret.setMap("structures", structuresSerialized); ++ for (final String key : newStructures.keySet()) { ++ structuresSerialized.setMap(key, newStructures.get(key).serialize()); ++ } ++ ++ if (stronghold) { ++ final MapType strongholdData = Types.NBT.createEmptyMap(); ++ ret.setMap("stronghold", strongholdData); ++ ++ strongholdData.setInt("distance", distance); ++ strongholdData.setInt("spread", spread); ++ strongholdData.setInt("count", count); ++ } ++ ++ return ret; ++ } ++ ++ public static MapType vanillaLevels(final long seed, final MapType generator, final boolean caves) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ final MapType overworld = Types.NBT.createEmptyMap(); ++ final MapType nether = Types.NBT.createEmptyMap(); ++ final MapType end = Types.NBT.createEmptyMap(); ++ ++ ret.setMap("minecraft:overworld", overworld); ++ ret.setMap("minecraft:the_nether", nether); ++ ret.setMap("minecraft:the_end", end); ++ ++ // overworld ++ overworld.setString("type", caves ? "minecraft:overworld_caves" : "minecraft:overworld"); ++ overworld.setMap("generator", generator); ++ ++ // nether ++ nether.setString("type", "minecraft:the_nether"); ++ final MapType netherBiomeSource = Types.NBT.createEmptyMap(); ++ netherBiomeSource.setString("type", "minecraft:multi_noise"); ++ netherBiomeSource.setLong("seed", seed); ++ netherBiomeSource.setString("preset", "minecraft:nether"); ++ ++ nether.setMap("generator", noise(seed, "minecraft:nether", netherBiomeSource)); ++ ++ // end ++ end.setString("type", "minecraft:the_end"); ++ final MapType endBiomeSource = Types.NBT.createEmptyMap(); ++ endBiomeSource.setString("type", "minecraft:the_end"); ++ endBiomeSource.setLong("seed", seed); ++ end.setMap("generator", noise(seed,"minecraft:end", endBiomeSource)); ++ ++ return ret; ++ } ++ ++ public static MapType defaultOverworld(final long seed) { ++ return noise(seed, "minecraft:overworld", vanillaBiomeSource(seed, false, false)); ++ } ++ ++ private static int getInt(final String value, final int dfl) { ++ return NumberUtils.toInt(value, dfl); ++ } ++ ++ private static int getInt(final String value, final int dfl, final int minVal) { ++ return Math.max(minVal, getInt(value, dfl)); ++ } ++ ++ private static void setSpacing(final Map structures, final String structureName, ++ final String value, final int minVal) { ++ final StructureFeatureConfiguration structure = structures.getOrDefault(structureName, DEFAULTS.get(structureName)); ++ final int newSpacing = getInt(value, structure.spacing, minVal); ++ ++ structures.put(structureName, new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ac0c4475556fe5202a6aa5724cb47b35c0cc9c00 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java +@@ -0,0 +1,101 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2551 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 14; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final String type = generator.getString("type"); ++ if (type == null) { ++ continue; ++ } ++ ++ switch (type) { ++ case "minecraft:flat": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); ++ ++ final ListType layers = settings.getList("layers", ObjectType.MAP); ++ if (layers != null) { ++ for (int i = 0, len = layers.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:noise": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings != null) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); ++ } ++ ++ final MapType biomeSource = generator.getMap("biome_source"); ++ if (biomeSource != null) { ++ final String biomeSourceType = biomeSource.getString("type", ""); ++ switch (biomeSourceType) { ++ case "minecraft:fixed": { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:multi_noise": { ++ // Vanilla's schema is wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) ++ // But it just contains the list part. That obviously can never be the case, because ++ // the root object is a compound, not a list. ++ ++ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); ++ if (biomes != null) { ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); ++ } ++ } ++ break; ++ } ++ ++ case "minecraft:checkerboard": { ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); ++ break; ++ } ++ } ++ } ++ ++ break; ++ } ++ } ++ } ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c06aa2099494f82bad2c1f212b2db07405f8f6ff +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2552 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 15; ++ ++ private V2552() {} ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, ImmutableMap.of( ++ "minecraft:nether", "minecraft:nether_wastes" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java +new file mode 100644 +index 0000000000000000000000000000000000000000..20c57f66e92922a8843887fb032f01c873c45679 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java +@@ -0,0 +1,75 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import java.util.Map; ++ ++public final class V2553 { ++ ++ protected static final int VERSION = MCVersions.V20W20B + 16; ++ ++ public static final Map BIOME_RENAMES = ImmutableMap.builder() ++ .put("minecraft:extreme_hills", "minecraft:mountains") ++ .put("minecraft:swampland", "minecraft:swamp") ++ .put("minecraft:hell", "minecraft:nether_wastes") ++ .put("minecraft:sky", "minecraft:the_end") ++ .put("minecraft:ice_flats", "minecraft:snowy_tundra") ++ .put("minecraft:ice_mountains", "minecraft:snowy_mountains") ++ .put("minecraft:mushroom_island", "minecraft:mushroom_fields") ++ .put("minecraft:mushroom_island_shore", "minecraft:mushroom_field_shore") ++ .put("minecraft:beaches", "minecraft:beach") ++ .put("minecraft:forest_hills", "minecraft:wooded_hills") ++ .put("minecraft:smaller_extreme_hills", "minecraft:mountain_edge") ++ .put("minecraft:stone_beach", "minecraft:stone_shore") ++ .put("minecraft:cold_beach", "minecraft:snowy_beach") ++ .put("minecraft:roofed_forest", "minecraft:dark_forest") ++ .put("minecraft:taiga_cold", "minecraft:snowy_taiga") ++ .put("minecraft:taiga_cold_hills", "minecraft:snowy_taiga_hills") ++ .put("minecraft:redwood_taiga", "minecraft:giant_tree_taiga") ++ .put("minecraft:redwood_taiga_hills", "minecraft:giant_tree_taiga_hills") ++ .put("minecraft:extreme_hills_with_trees", "minecraft:wooded_mountains") ++ .put("minecraft:savanna_rock", "minecraft:savanna_plateau") ++ .put("minecraft:mesa", "minecraft:badlands") ++ .put("minecraft:mesa_rock", "minecraft:wooded_badlands_plateau") ++ .put("minecraft:mesa_clear_rock", "minecraft:badlands_plateau") ++ .put("minecraft:sky_island_low", "minecraft:small_end_islands") ++ .put("minecraft:sky_island_medium", "minecraft:end_midlands") ++ .put("minecraft:sky_island_high", "minecraft:end_highlands") ++ .put("minecraft:sky_island_barren", "minecraft:end_barrens") ++ .put("minecraft:void", "minecraft:the_void") ++ .put("minecraft:mutated_plains", "minecraft:sunflower_plains") ++ .put("minecraft:mutated_desert", "minecraft:desert_lakes") ++ .put("minecraft:mutated_extreme_hills", "minecraft:gravelly_mountains") ++ .put("minecraft:mutated_forest", "minecraft:flower_forest") ++ .put("minecraft:mutated_taiga", "minecraft:taiga_mountains") ++ .put("minecraft:mutated_swampland", "minecraft:swamp_hills") ++ .put("minecraft:mutated_ice_flats", "minecraft:ice_spikes") ++ .put("minecraft:mutated_jungle", "minecraft:modified_jungle") ++ .put("minecraft:mutated_jungle_edge", "minecraft:modified_jungle_edge") ++ .put("minecraft:mutated_birch_forest", "minecraft:tall_birch_forest") ++ .put("minecraft:mutated_birch_forest_hills", "minecraft:tall_birch_hills") ++ .put("minecraft:mutated_roofed_forest", "minecraft:dark_forest_hills") ++ .put("minecraft:mutated_taiga_cold", "minecraft:snowy_taiga_mountains") ++ .put("minecraft:mutated_redwood_taiga", "minecraft:giant_spruce_taiga") ++ .put("minecraft:mutated_redwood_taiga_hills", "minecraft:giant_spruce_taiga_hills") ++ .put("minecraft:mutated_extreme_hills_with_trees", "minecraft:modified_gravelly_mountains") ++ .put("minecraft:mutated_savanna", "minecraft:shattered_savanna") ++ .put("minecraft:mutated_savanna_rock", "minecraft:shattered_savanna_plateau") ++ .put("minecraft:mutated_mesa", "minecraft:eroded_badlands") ++ .put("minecraft:mutated_mesa_rock", "minecraft:modified_wooded_badlands_plateau") ++ .put("minecraft:mutated_mesa_clear_rock", "minecraft:modified_badlands_plateau") ++ .put("minecraft:warm_deep_ocean", "minecraft:deep_warm_ocean") ++ .put("minecraft:lukewarm_deep_ocean", "minecraft:deep_lukewarm_ocean") ++ .put("minecraft:cold_deep_ocean", "minecraft:deep_cold_ocean") ++ .put("minecraft:frozen_deep_ocean", "minecraft:deep_frozen_ocean") ++ .build(); ++ ++ ++ private V2553() {} ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0e228fd642cbca13e8682950b5f0ec4e3e8a4da7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java +@@ -0,0 +1,45 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.options.ConverterAbstractOptionsRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2558 { ++ ++ protected static final int VERSION = MCVersions.V1_16_PRE2 + 1; ++ ++ private V2558() {} ++ ++ public static void register() { ++ ConverterAbstractOptionsRename.register(VERSION, ImmutableMap.of( ++ "key_key.swapHands", "key_key.swapOffhand" ++ )::get); ++ ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType dimensions = data.getMap("dimensions"); ++ if (dimensions == null) { ++ dimensions = Types.NBT.createEmptyMap(); ++ data.setMap("dimensions", dimensions); ++ } ++ ++ if (dimensions.isEmpty()) { ++ data.setMap("dimensions", recreateSettings(data)); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static MapType recreateSettings(final MapType data) { ++ final long seed = data.getLong("seed"); ++ ++ return V2550.vanillaLevels(seed, V2550.defaultOverworld(seed), false); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5835159d7016fb4f05d137acba709fa7d8e8e752 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2568 { ++ ++ protected static final int VERSION = MCVersions.V1_16_1 + 1; ++ ++ private V2568() {} ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:piglin_brute"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java +new file mode 100644 +index 0000000000000000000000000000000000000000..374d24db80f3ed8cd241e18d776c39a8da72d8fd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2671 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 85; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:goat"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6c788f51be0439797bf9fc8711d4cf8e382f5c11 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2679 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 93; ++ ++ public static void register() { ++ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!"minecraft:cauldron".equals(data.getString("Name"))) { ++ return null; ++ } ++ ++ final MapType properties = data.getMap("Properties"); ++ ++ if (properties == null) { ++ return null; ++ } ++ ++ if (properties.getString("level", "0").equals("0")) { ++ data.remove("Properties"); ++ return null; ++ } else { ++ data.setString("Name", "minecraft:water_cauldron"); ++ return null; ++ } ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java +new file mode 100644 +index 0000000000000000000000000000000000000000..232a28c07c6341a996253282d9872d76b3fce0e3 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2680 { ++ ++ protected static final int VERSION = MCVersions.V1_16_5 + 94; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:grass_path", "minecraft:dirt_path" ++ )::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( ++ "minecraft:grass_path", "minecraft:dirt_path" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4706a7cfb97d3d5c521914f8dfc894db277bcfe0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++ ++public final class V2684 { ++ ++ protected static final int VERSION = MCVersions.V20W48A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_sensor", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6a6f33d4f701f4188828994c8e56dea21950366 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2686 { ++ ++ protected static final int VERSION = MCVersions.V20W49A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:axolotl"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6fcfcb66e1fd9291abad47e41ee076a7816b4244 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++ ++public final class V2688 { ++ ++ protected static final int VERSION = MCVersions.V20W51A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:glow_squid"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:glow_item_frame", new DataWalkerItems("Item")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1638f04efd4063c23807b29bc226ad33eed27e7b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2690 { ++ ++ protected static final int VERSION = MCVersions.V21W05A; ++ ++ protected static final ImmutableMap RENAMES = ImmutableMap.builder() ++ .put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block") ++ .put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block") ++ .put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block") ++ .put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper") ++ .put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper") ++ .put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper") ++ .put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs") ++ .put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs") ++ .put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs") ++ .put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab") ++ .put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab") ++ .put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab") ++ .put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper") ++ .put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper") ++ .put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper") ++ .put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper") ++ .put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs") ++ .put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs") ++ .put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab") ++ .put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab") ++ .build(); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java +new file mode 100644 +index 0000000000000000000000000000000000000000..3841780d52c2e242609fc076efa5902c063b7b48 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java +@@ -0,0 +1,23 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2691 { ++ ++ protected static final int VERSION = MCVersions.V21W05A + 1; ++ ++ protected static final ImmutableMap RENAMES = ImmutableMap.builder() ++ .put("minecraft:waxed_copper", "minecraft:waxed_copper_block") ++ .put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper") ++ .put("minecraft:weathered_copper_block", "minecraft:weathered_copper") ++ .put("minecraft:exposed_copper_block", "minecraft:exposed_copper") ++ .build(); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java +new file mode 100644 +index 0000000000000000000000000000000000000000..deac34afe6a3681db9a7630ad6526f71d4dd6e1f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V2693 { ++ ++ protected static final int VERSION = MCVersions.V21W05B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0094154c1da7cb95120e01bceeb836ca7ab68e25 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java +@@ -0,0 +1,36 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2696 { ++ ++ protected static final int VERSION = MCVersions.V21W07A + 1; ++ ++ protected static final ImmutableMap RENAMES = ImmutableMap.builder() ++ .put("minecraft:grimstone", "minecraft:deepslate") ++ .put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab") ++ .put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs") ++ .put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall") ++ .put("minecraft:polished_grimstone", "minecraft:polished_deepslate") ++ .put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab") ++ .put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs") ++ .put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall") ++ .put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles") ++ .put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab") ++ .put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs") ++ .put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall") ++ .put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks") ++ .put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab") ++ .put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs") ++ .put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall") ++ .put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate") ++ .build(); ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, RENAMES::get); ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c37142033061a3e4865686ee64d8f15f040d7d41 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2700 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 1; ++ ++ public static void register() { ++ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( ++ "minecraft:cave_vines_head", "minecraft:cave_vines", ++ "minecraft:cave_vines_body", "minecraft:cave_vines_plant" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9d6b03410c4665e19a2a35226d11f77b2cae3bbf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java +@@ -0,0 +1,203 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import com.google.common.collect.Sets; ++import java.util.Set; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++public final class V2701 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 2; ++ ++ private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]"); ++ ++ private static final Set PIECE_TYPE = Sets.newHashSet( ++ "minecraft:jigsaw", ++ "minecraft:nvi", ++ "minecraft:pcp", ++ "minecraft:bastionremnant", ++ "minecraft:runtime" ++ ); ++ private static final Set FEATURES = Sets.newHashSet( ++ "minecraft:tree", ++ "minecraft:flower", ++ "minecraft:block_pile", ++ "minecraft:random_patch" ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final ListType children = data.getList("Children", ObjectType.MAP); ++ ++ if (children == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = children.size(); i < len; ++i) { ++ final MapType child = children.getMap(i); ++ ++ if (!PIECE_TYPE.contains(child.getString("id"))) { ++ continue; ++ } ++ ++ final String poolElement = child.getString("pool_element"); ++ if (!"minecraft:feature_pool_element".equals(poolElement)) { ++ continue; ++ } ++ ++ final MapType feature = child.getMap("feature"); ++ if (feature == null) { ++ continue; ++ } ++ ++ final String replacement = convertToString(feature); ++ ++ if (replacement != null) { ++ child.setString("feature", replacement); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static String getNestedString(final MapType root, final String... paths) { ++ if (paths.length == 0) { ++ throw new IllegalArgumentException("Missing path"); ++ } ++ ++ Object current = root.getGeneric(paths[0]); ++ ++ for (int i = 1; i < paths.length; ++i) { ++ final String path = paths[i]; ++ ++ final Matcher indexMatcher = INDEX_PATTERN.matcher(path); ++ if (!indexMatcher.matches()) { ++ current = (current instanceof MapType) ? ((MapType)current).getGeneric(path) : null; ++ if (current == null) { ++ break; ++ } ++ continue; ++ } ++ ++ final int index = Integer.parseInt(indexMatcher.group(1)); ++ if (!(current instanceof ListType)) { ++ current = null; ++ break; ++ } else { ++ final ListType list = (ListType)current; ++ if (index >= 0 && index < list.size()) { ++ current = list.getGeneric(index); ++ } else { ++ current = null; ++ break; ++ } ++ } ++ } ++ ++ return current instanceof String ? (String)current : ""; ++ } ++ ++ protected static String convertToString(final MapType feature) { ++ return getReplacement( ++ getNestedString(feature, "type"), ++ getNestedString(feature, "name"), ++ getNestedString(feature, "config", "state_provider", "type"), ++ getNestedString(feature, "config", "state_provider", "state", "Name"), ++ getNestedString(feature, "config", "state_provider", "entries", "[0]", "data", "Name"), ++ getNestedString(feature, "config", "foliage_placer", "type"), ++ getNestedString(feature, "config", "leaves_provider", "state", "Name") ++ ); ++ } ++ ++ private static String getReplacement(final String type, final String name, final String stateType, final String stateName, ++ final String firstEntryName, final String foliageName, final String leavesName) { ++ final String actualType; ++ if (!type.isEmpty()) { ++ actualType = type; ++ } else { ++ if (name.isEmpty()) { ++ return null; ++ } ++ ++ if ("minecraft:normal_tree".equals(name)) { ++ actualType = "minecraft:tree"; ++ } else { ++ actualType = name; ++ } ++ } ++ ++ if (FEATURES.contains(actualType)) { ++ if ("minecraft:random_patch".equals(actualType)) { ++ if ("minecraft:simple_state_provider".equals(stateType)) { ++ if ("minecraft:sweet_berry_bush".equals(stateName)) { ++ return "minecraft:patch_berry_bush"; ++ } ++ ++ if ("minecraft:cactus".equals(stateName)) { ++ return "minecraft:patch_cactus"; ++ } ++ } else if ("minecraft:weighted_state_provider".equals(stateType) && ("minecraft:grass".equals(firstEntryName) || "minecraft:fern".equals(firstEntryName))) { ++ return "minecraft:patch_taiga_grass"; ++ } ++ } else if ("minecraft:block_pile".equals(actualType)) { ++ if (!"minecraft:simple_state_provider".equals(stateType) && !"minecraft:rotated_block_provider".equals(stateType)) { ++ if ("minecraft:weighted_state_provider".equals(stateType)) { ++ if ("minecraft:packed_ice".equals(firstEntryName) || "minecraft:blue_ice".equals(firstEntryName)) { ++ return "minecraft:pile_ice"; ++ } ++ ++ if ("minecraft:jack_o_lantern".equals(firstEntryName) || "minecraft:pumpkin".equals(firstEntryName)) { ++ return "minecraft:pile_pumpkin"; ++ } ++ } ++ } else { ++ if ("minecraft:hay_block".equals(stateName)) { ++ return "minecraft:pile_hay"; ++ } ++ ++ if ("minecraft:melon".equals(stateName)) { ++ return "minecraft:pile_melon"; ++ } ++ ++ if ("minecraft:snow".equals(stateName)) { ++ return "minecraft:pile_snow"; ++ } ++ } ++ } else { ++ if ("minecraft:flower".equals(actualType)) { ++ return "minecraft:flower_plain"; ++ } ++ ++ if ("minecraft:tree".equals(actualType)) { ++ if ("minecraft:acacia_foliage_placer".equals(foliageName)) { ++ return "minecraft:acacia"; ++ } ++ ++ if ("minecraft:blob_foliage_placer".equals(foliageName) && "minecraft:oak_leaves".equals(leavesName)) { ++ return "minecraft:oak"; ++ } ++ ++ if ("minecraft:pine_foliage_placer".equals(foliageName)) { ++ return "minecraft:pine"; ++ } ++ ++ if ("minecraft:spruce_foliage_placer".equals(foliageName)) { ++ return "minecraft:spruce"; ++ } ++ } ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java +new file mode 100644 +index 0000000000000000000000000000000000000000..53e45b14c05dab35cd5725998458d47e28718075 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2702 { ++ ++ protected static final int VERSION = MCVersions.V21W10A + 3; ++ ++ public static void register() { ++ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.hasKey("pickup")) { ++ return null; ++ } ++ ++ final boolean player = data.getBoolean("player", true); ++ data.remove("player"); ++ ++ data.setByte("pickup", player ? (byte)1 : (byte)0); ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java +new file mode 100644 +index 0000000000000000000000000000000000000000..74c1df97036059b3a5147f7cf94752ef4516a33d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V2707 { ++ ++ protected static final int VERSION = MCVersions.V21W14A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", true)); ++ ++ registerMob("minecraft:marker"); // ????????????? ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0d31b10a4f93c0eb8bff66dd062ffb950b2182ec +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java +@@ -0,0 +1,17 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2710 { ++ ++ protected static final int VERSION = MCVersions.V21W15A + 1; ++ ++ public static void register() { ++ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:play_one_minute", "minecraft:play_time" ++ )::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8678ba95b5abe96b399a310623078f8827dfa0f0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2717 { ++ ++ protected static final int VERSION = MCVersions.V1_17_PRE1 + 1; ++ ++ public static void register() { ++ final ImmutableMap rename = ImmutableMap.of( ++ "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves" ++ ); ++ ConverterAbstractItemRename.register(VERSION, rename::get); ++ ConverterAbstractBlockRename.register(VERSION, rename::get); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c2d2b7c10e5b988b1111b20b778c475a12bef353 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++ ++public final class V2825 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 95; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d28ade80499dce882a9a84309a2a0da527fe01a0 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java +@@ -0,0 +1,69 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V2831 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 101; ++ ++ public static void register() { ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { ++ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential.getMap("data"), "entity", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, root.getMap("SpawnData"), "entity", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType spawnData = root.getMap("SpawnData"); ++ if (spawnData != null) { ++ final MapType wrapped = Types.NBT.createEmptyMap(); ++ root.setMap("SpawnData", wrapped); ++ ++ wrapped.setMap("entity", spawnData); ++ } ++ ++ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); ++ if (spawnPotentials != null) { ++ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { ++ final MapType spawnPotential = spawnPotentials.getMap(i); ++ ++ // new format of weighted list (SpawnPotentials): ++ // root.data -> data ++ // root.weight -> weight ++ ++ final MapType entity = spawnPotential.getMap("Entity"); ++ final int weight = spawnPotential.getInt("Weight", 1); ++ spawnPotential.remove("Entity"); ++ spawnPotential.remove("Weight"); ++ spawnPotential.setInt("weight", weight); ++ ++ final MapType data = Types.NBT.createEmptyMap(); ++ spawnPotential.setMap("data", data); ++ ++ data.setMap("entity", entity); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java +new file mode 100644 +index 0000000000000000000000000000000000000000..95a306d6cc5b4ac8161d5bed80b6a7073b3e914e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java +@@ -0,0 +1,920 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.mojang.logging.LogUtils; ++import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.ints.IntIterator; ++import it.unimi.dsi.fastutil.ints.IntOpenHashSet; ++import org.apache.commons.lang3.mutable.MutableBoolean; ++import org.slf4j.Logger; ++import java.util.Arrays; ++import java.util.BitSet; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V2832 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 102; ++ ++ private static final String[] BIOMES_BY_ID = new String[256]; // rip datapacks ++ static { ++ BIOMES_BY_ID[0] = "minecraft:ocean"; ++ BIOMES_BY_ID[1] = "minecraft:plains"; ++ BIOMES_BY_ID[2] = "minecraft:desert"; ++ BIOMES_BY_ID[3] = "minecraft:mountains"; ++ BIOMES_BY_ID[4] = "minecraft:forest"; ++ BIOMES_BY_ID[5] = "minecraft:taiga"; ++ BIOMES_BY_ID[6] = "minecraft:swamp"; ++ BIOMES_BY_ID[7] = "minecraft:river"; ++ BIOMES_BY_ID[8] = "minecraft:nether_wastes"; ++ BIOMES_BY_ID[9] = "minecraft:the_end"; ++ BIOMES_BY_ID[10] = "minecraft:frozen_ocean"; ++ BIOMES_BY_ID[11] = "minecraft:frozen_river"; ++ BIOMES_BY_ID[12] = "minecraft:snowy_tundra"; ++ BIOMES_BY_ID[13] = "minecraft:snowy_mountains"; ++ BIOMES_BY_ID[14] = "minecraft:mushroom_fields"; ++ BIOMES_BY_ID[15] = "minecraft:mushroom_field_shore"; ++ BIOMES_BY_ID[16] = "minecraft:beach"; ++ BIOMES_BY_ID[17] = "minecraft:desert_hills"; ++ BIOMES_BY_ID[18] = "minecraft:wooded_hills"; ++ BIOMES_BY_ID[19] = "minecraft:taiga_hills"; ++ BIOMES_BY_ID[20] = "minecraft:mountain_edge"; ++ BIOMES_BY_ID[21] = "minecraft:jungle"; ++ BIOMES_BY_ID[22] = "minecraft:jungle_hills"; ++ BIOMES_BY_ID[23] = "minecraft:jungle_edge"; ++ BIOMES_BY_ID[24] = "minecraft:deep_ocean"; ++ BIOMES_BY_ID[25] = "minecraft:stone_shore"; ++ BIOMES_BY_ID[26] = "minecraft:snowy_beach"; ++ BIOMES_BY_ID[27] = "minecraft:birch_forest"; ++ BIOMES_BY_ID[28] = "minecraft:birch_forest_hills"; ++ BIOMES_BY_ID[29] = "minecraft:dark_forest"; ++ BIOMES_BY_ID[30] = "minecraft:snowy_taiga"; ++ BIOMES_BY_ID[31] = "minecraft:snowy_taiga_hills"; ++ BIOMES_BY_ID[32] = "minecraft:giant_tree_taiga"; ++ BIOMES_BY_ID[33] = "minecraft:giant_tree_taiga_hills"; ++ BIOMES_BY_ID[34] = "minecraft:wooded_mountains"; ++ BIOMES_BY_ID[35] = "minecraft:savanna"; ++ BIOMES_BY_ID[36] = "minecraft:savanna_plateau"; ++ BIOMES_BY_ID[37] = "minecraft:badlands"; ++ BIOMES_BY_ID[38] = "minecraft:wooded_badlands_plateau"; ++ BIOMES_BY_ID[39] = "minecraft:badlands_plateau"; ++ BIOMES_BY_ID[40] = "minecraft:small_end_islands"; ++ BIOMES_BY_ID[41] = "minecraft:end_midlands"; ++ BIOMES_BY_ID[42] = "minecraft:end_highlands"; ++ BIOMES_BY_ID[43] = "minecraft:end_barrens"; ++ BIOMES_BY_ID[44] = "minecraft:warm_ocean"; ++ BIOMES_BY_ID[45] = "minecraft:lukewarm_ocean"; ++ BIOMES_BY_ID[46] = "minecraft:cold_ocean"; ++ BIOMES_BY_ID[47] = "minecraft:deep_warm_ocean"; ++ BIOMES_BY_ID[48] = "minecraft:deep_lukewarm_ocean"; ++ BIOMES_BY_ID[49] = "minecraft:deep_cold_ocean"; ++ BIOMES_BY_ID[50] = "minecraft:deep_frozen_ocean"; ++ BIOMES_BY_ID[127] = "minecraft:the_void"; ++ BIOMES_BY_ID[129] = "minecraft:sunflower_plains"; ++ BIOMES_BY_ID[130] = "minecraft:desert_lakes"; ++ BIOMES_BY_ID[131] = "minecraft:gravelly_mountains"; ++ BIOMES_BY_ID[132] = "minecraft:flower_forest"; ++ BIOMES_BY_ID[133] = "minecraft:taiga_mountains"; ++ BIOMES_BY_ID[134] = "minecraft:swamp_hills"; ++ BIOMES_BY_ID[140] = "minecraft:ice_spikes"; ++ BIOMES_BY_ID[149] = "minecraft:modified_jungle"; ++ BIOMES_BY_ID[151] = "minecraft:modified_jungle_edge"; ++ BIOMES_BY_ID[155] = "minecraft:tall_birch_forest"; ++ BIOMES_BY_ID[156] = "minecraft:tall_birch_hills"; ++ BIOMES_BY_ID[157] = "minecraft:dark_forest_hills"; ++ BIOMES_BY_ID[158] = "minecraft:snowy_taiga_mountains"; ++ BIOMES_BY_ID[160] = "minecraft:giant_spruce_taiga"; ++ BIOMES_BY_ID[161] = "minecraft:giant_spruce_taiga_hills"; ++ BIOMES_BY_ID[162] = "minecraft:modified_gravelly_mountains"; ++ BIOMES_BY_ID[163] = "minecraft:shattered_savanna"; ++ BIOMES_BY_ID[164] = "minecraft:shattered_savanna_plateau"; ++ BIOMES_BY_ID[165] = "minecraft:eroded_badlands"; ++ BIOMES_BY_ID[166] = "minecraft:modified_wooded_badlands_plateau"; ++ BIOMES_BY_ID[167] = "minecraft:modified_badlands_plateau"; ++ BIOMES_BY_ID[168] = "minecraft:bamboo_jungle"; ++ BIOMES_BY_ID[169] = "minecraft:bamboo_jungle_hills"; ++ BIOMES_BY_ID[170] = "minecraft:soul_sand_valley"; ++ BIOMES_BY_ID[171] = "minecraft:crimson_forest"; ++ BIOMES_BY_ID[172] = "minecraft:warped_forest"; ++ BIOMES_BY_ID[173] = "minecraft:basalt_deltas"; ++ BIOMES_BY_ID[174] = "minecraft:dripstone_caves"; ++ BIOMES_BY_ID[175] = "minecraft:lush_caves"; ++ BIOMES_BY_ID[177] = "minecraft:meadow"; ++ BIOMES_BY_ID[178] = "minecraft:grove"; ++ BIOMES_BY_ID[179] = "minecraft:snowy_slopes"; ++ BIOMES_BY_ID[180] = "minecraft:snowcapped_peaks"; ++ BIOMES_BY_ID[181] = "minecraft:lofty_peaks"; ++ BIOMES_BY_ID[182] = "minecraft:stony_peaks"; ++ } ++ ++ private static final String[] HEIGHTMAP_TYPES = new String[] { ++ "WORLD_SURFACE_WG", ++ "WORLD_SURFACE", ++ "WORLD_SURFACE_IGNORE_SNOW", ++ "OCEAN_FLOOR_WG", ++ "OCEAN_FLOOR", ++ "MOTION_BLOCKING", ++ "MOTION_BLOCKING_NO_LEAVES" ++ }; ++ ++ private static final Set STATUS_IS_OR_AFTER_SURFACE = new HashSet<>(Arrays.asList( ++ "surface", ++ "carvers", ++ "liquid_carvers", ++ "features", ++ "light", ++ "spawn", ++ "heightmaps", ++ "full" ++ )); ++ private static final Set STATUS_IS_OR_AFTER_NOISE = new HashSet<>(Arrays.asList( ++ "noise", ++ "surface", ++ "carvers", ++ "liquid_carvers", ++ "features", ++ "light", ++ "spawn", ++ "heightmaps", ++ "full" ++ )); ++ private static final Set BLOCKS_BEFORE_FEATURE_STATUS = new HashSet<>(Arrays.asList( ++ "minecraft:air", ++ "minecraft:basalt", ++ "minecraft:bedrock", ++ "minecraft:blackstone", ++ "minecraft:calcite", ++ "minecraft:cave_air", ++ "minecraft:coarse_dirt", ++ "minecraft:crimson_nylium", ++ "minecraft:dirt", ++ "minecraft:end_stone", ++ "minecraft:grass_block", ++ "minecraft:gravel", ++ "minecraft:ice", ++ "minecraft:lava", ++ "minecraft:mycelium", ++ "minecraft:nether_wart_block", ++ "minecraft:netherrack", ++ "minecraft:orange_terracotta", ++ "minecraft:packed_ice", ++ "minecraft:podzol", ++ "minecraft:powder_snow", ++ "minecraft:red_sand", ++ "minecraft:red_sandstone", ++ "minecraft:sand", ++ "minecraft:sandstone", ++ "minecraft:snow_block", ++ "minecraft:soul_sand", ++ "minecraft:soul_soil", ++ "minecraft:stone", ++ "minecraft:terracotta", ++ "minecraft:warped_nylium", ++ "minecraft:warped_wart_block", ++ "minecraft:water", ++ "minecraft:white_terracotta" ++ )); ++ ++ private static int getObjectsPerValue(final long[] val) { ++ return (4096 + val.length - 1) / (val.length); // expression is invalid if it returns > 64 ++ } ++ ++ private static long[] resize(final long[] val, final int oldBitsPerObject, final int newBitsPerObject) { ++ final long oldMask = (1L << oldBitsPerObject) - 1; // works even if bitsPerObject == 64 ++ final long newMask = (1L << newBitsPerObject) - 1; ++ final int oldObjectsPerValue = 64 / oldBitsPerObject; ++ final int newObjectsPerValue = 64 / newBitsPerObject; ++ ++ if (newBitsPerObject == oldBitsPerObject) { ++ return val; ++ } ++ ++ final int items = 4096; ++ ++ final long[] ret = new long[(items + newObjectsPerValue - 1) / newObjectsPerValue]; ++ ++ final int expectedSize = ((items + oldObjectsPerValue - 1) / oldObjectsPerValue); ++ if (val.length != expectedSize) { ++ throw new IllegalStateException("Expected size: " + expectedSize + ", got: " + val.length); ++ } ++ ++ int shift = 0; ++ int idx = 0; ++ long newCurr = 0L; ++ ++ int currItem = 0; ++ for (int i = 0; i < val.length; ++i) { ++ final long oldCurr = val[i]; ++ ++ for (int objIdx = 0; currItem < items && objIdx + oldBitsPerObject <= 64; objIdx += oldBitsPerObject, ++currItem) { ++ final long value = (oldCurr >> objIdx) & oldMask; ++ ++ if ((value & newMask) != value) { ++ throw new IllegalStateException("Old data storage has values that cannot be moved into new palette (would erase data)!"); ++ } ++ ++ newCurr |= value << shift; ++ shift += newBitsPerObject; ++ ++ if (shift + newBitsPerObject > 64) { // will next write overflow? ++ // must move to next idx ++ ret[idx++] = newCurr; ++ shift = 0; ++ newCurr = 0L; ++ } ++ } ++ } ++ ++ // don't forget to write the last one ++ if (shift != 0) { ++ ret[idx] = newCurr; ++ } ++ ++ return ret; ++ } ++ ++ private static void fixLithiumChunks(final MapType data) { ++ // See https://github.com/CaffeineMC/lithium-fabric/issues/279 ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return; ++ } ++ ++ final int chunkX = level.getInt("xPos"); ++ final int chunkZ = level.getInt("zPos"); ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ return; ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int sectionY = section.getInt("Y"); ++ ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ final long[] blockStates = section.getLongs("BlockStates"); ++ ++ if (palette == null || blockStates == null) { ++ continue; ++ } ++ ++ final int expectedBits = Math.max(4, ceilLog2(palette.size())); ++ final int gotObjectsPerValue = getObjectsPerValue(blockStates); ++ final int gotBits = 64 / gotObjectsPerValue; ++ ++ if (expectedBits == gotBits) { ++ continue; ++ } ++ ++ try { ++ section.setLongs("BlockStates", resize(blockStates, gotBits, expectedBits)); ++ } catch (final Exception ex) { ++ LOGGER.error("Failed to rewrite mismatched palette and data storage for section y: " + sectionY ++ + " for chunk [" + chunkX + "," + chunkZ + "], palette entries: " + palette.size() + ", data storage size: " ++ + blockStates.length, ++ ex ++ ); ++ } ++ } ++ } ++ ++ public static void register() { ++ // See V2551 for the layout of world gen settings ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // converters were added to older versions note whether the world has increased height already or not ++ final boolean noHeightFlag = !data.hasKey("has_increased_height_already"); ++ final boolean hasIncreasedHeight = data.getBoolean("has_increased_height_already", true); ++ data.remove("has_increased_height_already"); ++ ++ final MapType dimensions = data.getMap("dimensions"); ++ if (dimensions == null) { ++ // wat ++ return null; ++ } ++ ++ // only care about overworld ++ final MapType overworld = dimensions.getMap("minecraft:overworld"); ++ if (overworld == null) { ++ // wat ++ return null; ++ } ++ ++ final MapType generator = overworld.getMap("generator"); ++ if (generator == null) { ++ // wat ++ return null; ++ } ++ ++ final String type = generator.getString("type", ""); ++ switch (type) { ++ case "minecraft:noise": { ++ final MapType biomeSource = generator.getMap("biome_source"); ++ final String sourceType = biomeSource.getString("type"); ++ ++ boolean largeBiomes = false; ++ ++ if ("minecraft:vanilla_layered".equals(sourceType) || (noHeightFlag && "minecraft:multi_noise".equals(sourceType))) { ++ largeBiomes = biomeSource.getBoolean("large_biomes"); ++ ++ final MapType newBiomeSource = Types.NBT.createEmptyMap(); ++ generator.setMap("biome_source", newBiomeSource); ++ ++ newBiomeSource.setString("preset", "minecraft:overworld"); ++ newBiomeSource.setString("type", "minecraft:multi_noise"); ++ } ++ ++ if (largeBiomes) { ++ if ("minecraft:overworld".equals(generator.getString("settings"))) { ++ generator.setString("settings", "minecraft:large_biomes"); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:flat": { ++ if (!hasIncreasedHeight) { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ break; ++ } ++ ++ updateLayers(settings.getList("layers", ObjectType.MAP)); ++ } ++ break; ++ } ++ default: ++ // do nothing ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ ++ // It looks like DFU will only support worlds in the old height format or the new one, any custom one isn't supported ++ // and by not supported I mean it will just treat it as the old format... maybe at least throw in that case? ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // The below covers padPaletteEntries - this was written BEFORE that code was added to the datafixer - ++ // and this still works, so I'm keeping it. Don't fix what isn't broken. ++ fixLithiumChunks(data); // See https://github.com/CaffeineMC/lithium-fabric/issues/279 ++ ++ final MapType level = data.getMap("Level"); ++ ++ if (level == null) { ++ return null; ++ } ++ ++ final MapType context = data.getMap("__context"); // Passed through by ChunkStorage ++ final String dimension = context == null ? "" : context.getString("dimension", ""); ++ final String generator = context == null ? "" : context.getString("generator", ""); ++ final boolean isOverworld = "minecraft:overworld".equals(dimension); ++ final int minSection = isOverworld ? -4 : 0; ++ final MutableBoolean isAlreadyExtended = new MutableBoolean(); ++ ++ final MapType[] newBiomes = createBiomeSections(level, isOverworld, minSection, isAlreadyExtended); ++ final MapType wrappedEmptyBlockPalette = getEmptyBlockPalette(); ++ ++ ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections == null) { ++ level.setList("Sections", sections = Types.NBT.createEmptyList()); ++ } ++ ++ // must update sections for two things: ++ // 1. the biomes are now stored per section, so we must insert the biomes palette into each section (and create them if they don't exist) ++ // 2. each section must now have block states (or at least DFU is ensuring they do, but current code does not require) ++ V2841.SimplePaletteReader bottomSection = null; ++ final Set allBlocks = new HashSet<>(); ++ final IntOpenHashSet existingSections = new IntOpenHashSet(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int y = section.getInt("Y"); ++ final int sectionIndex = y - minSection; ++ ++ existingSections.add(y); ++ ++ // add in relevant biome section ++ if (sectionIndex >= 0 && sectionIndex < newBiomes.length) { ++ // exclude out of bounds sections (i.e the light sections above and below the world) ++ section.setMap("biomes", newBiomes[sectionIndex]); ++ } ++ ++ // update palette ++ final ListType palette = section.getList("Palette", ObjectType.MAP); ++ final long[] blockStates = section.getLongs("BlockStates"); ++ ++ section.remove("Palette"); ++ section.remove("BlockStates"); ++ ++ if (palette != null) { ++ for (int j = 0, len2 = palette.size(); j < len2; ++j) { ++ allBlocks.add(V2841.getBlockId(palette.getMap(j))); ++ } ++ } ++ ++ final MapType palettedContainer; ++ if (palette != null && blockStates != null) { ++ // only if both exist, same as DFU, same as legacy chunk loading code ++ section.setMap("block_states", palettedContainer = wrapPaletteOptimised(palette, blockStates)); ++ } else { ++ section.setMap("block_states", palettedContainer = wrappedEmptyBlockPalette.copy()); // must write a palette now, copy so that later edits do not edit them all ++ } ++ ++ if (section.getInt("Y", Integer.MAX_VALUE) == 0) { ++ bottomSection = new V2841.SimplePaletteReader(palettedContainer.getList("palette", ObjectType.MAP), palettedContainer.getLongs("data")); ++ } ++ } ++ ++ // all existing sections updated, now we must create new sections just for the biomes migration ++ for (int sectionIndex = 0; sectionIndex < newBiomes.length; ++sectionIndex) { ++ final int sectionY = sectionIndex + minSection; ++ if (!existingSections.add(sectionY)) { ++ // exists already ++ continue; ++ } ++ ++ final MapType newSection = Types.NBT.createEmptyMap(); ++ sections.addMap(newSection); ++ ++ newSection.setByte("Y", (byte)sectionY); ++ // must write a palette now, copy so that later edits do not edit them all ++ newSection.setMap("block_states", wrappedEmptyBlockPalette.copy()); ++ ++ newSection.setGeneric("biomes", newBiomes[sectionIndex]); ++ } ++ ++ // update status so interpolation can take place ++ predictChunkStatusBeforeSurface(level, allBlocks); ++ ++ // done with sections, update the rest of the chunk ++ updateChunkData(level, isOverworld, isAlreadyExtended.getValue(), "minecraft:noise".equals(generator), bottomSection); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final String type = generator.getString("type"); ++ if (type == null) { ++ continue; ++ } ++ ++ switch (type) { ++ case "minecraft:flat": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); ++ ++ final ListType layers = settings.getList("layers", ObjectType.MAP); ++ if (layers != null) { ++ for (int i = 0, len = layers.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); ++ } ++ } ++ ++ break; ++ } ++ case "minecraft:noise": { ++ final MapType settings = generator.getMap("settings"); ++ if (settings != null) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); ++ } ++ ++ final MapType biomeSource = generator.getMap("biome_source"); ++ if (biomeSource != null) { ++ final String biomeSourceType = biomeSource.getString("type", ""); ++ switch (biomeSourceType) { ++ case "minecraft:fixed": { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); ++ break; ++ } ++ ++ case "minecraft:multi_noise": { ++ // preset is absent, no type for namespaced string ++ ++ // Vanilla's schema is _still_ wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) ++ // But it just contains the list part. That obviously can never be the case, because ++ // the root object is a compound, not a list. ++ ++ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); ++ if (biomes != null) { ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); ++ } ++ } ++ break; ++ } ++ ++ case "minecraft:checkerboard": { ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); ++ break; ++ } ++ } ++ } ++ ++ break; ++ } ++ } ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++ ++ private static void predictChunkStatusBeforeSurface(final MapType level, final Set chunkBlocks) { ++ final String status = level.getString("Status", "empty"); ++ if (STATUS_IS_OR_AFTER_SURFACE.contains(status)) { ++ return; ++ } ++ ++ chunkBlocks.remove("minecraft:air"); ++ final boolean chunkNotEmpty = !chunkBlocks.isEmpty(); ++ chunkBlocks.removeAll(BLOCKS_BEFORE_FEATURE_STATUS); ++ final boolean chunkFeatureStatus = !chunkBlocks.isEmpty(); ++ ++ final String update; ++ if (chunkFeatureStatus) { ++ update = "liquid_carvers"; ++ } else if (!"noise".equals(status) && !chunkNotEmpty) { ++ update = "biomes".equals(status) ? "structure_references" : status; ++ } else { ++ update = "noise"; ++ } ++ ++ level.setString("Status", update); ++ } ++ ++ private static MapType getEmptyBlockPalette() { ++ final MapType airBlockState = Types.NBT.createEmptyMap(); ++ airBlockState.setString("Name", "minecraft:air"); ++ ++ final ListType emptyBlockPalette = Types.NBT.createEmptyList(); ++ emptyBlockPalette.addMap(airBlockState); ++ ++ return V2832.wrapPalette(emptyBlockPalette); ++ } ++ ++ private static void shiftUpgradeData(final MapType upgradeData, final int shift) { ++ if (upgradeData == null) { ++ return; ++ } ++ ++ final MapType indices = upgradeData.getMap("Indices"); ++ if (indices == null) { ++ return; ++ } ++ ++ RenameHelper.renameKeys(indices, (final String input) -> { ++ return Integer.toString(Integer.parseInt(input) + shift); ++ }); ++ } ++ ++ private static void updateChunkData(final MapType level, final boolean wantExtendedHeight, final boolean isAlreadyExtended, ++ final boolean onNoiseGenerator, final V2841.SimplePaletteReader bottomSection) { ++ level.remove("Biomes"); ++ if (!wantExtendedHeight) { ++ padCarvingMasks(level, 16, 0); ++ return; ++ } ++ ++ if (isAlreadyExtended) { ++ padCarvingMasks(level, 24, 0); ++ return; ++ } ++ ++ offsetHeightmaps(level); ++ addEmptyListPadding(level, "Lights"); ++ addEmptyListPadding(level, "LiquidsToBeTicked"); ++ addEmptyListPadding(level, "PostProcessing"); ++ addEmptyListPadding(level, "ToBeTicked"); ++ shiftUpgradeData(level.getMap("UpgradeData"), 4); // https://bugs.mojang.com/browse/MC-238076 - fixed now, Mojang fix is identical. No change required. ++ padCarvingMasks(level, 24, 4); ++ ++ if (!onNoiseGenerator) { ++ return; ++ } ++ ++ final String status = level.getString("Status"); ++ if (status == null || "empty".equals(status)) { ++ return; ++ } ++ ++ final MapType blendingData = Types.NBT.createEmptyMap(); ++ level.setMap("blending_data", blendingData); ++ ++ blendingData.setBoolean("old_noise", STATUS_IS_OR_AFTER_NOISE.contains(status)); ++ ++ if (bottomSection == null) { ++ return; ++ } ++ ++ final BitSet missingBedrock = new BitSet(256); ++ boolean hasBedrock = status.equals("noise"); ++ ++ for (int z = 0; z <= 15; ++z) { ++ for (int x = 0; x <= 15; ++x) { ++ final MapType state = bottomSection.getState(x, 0, z); ++ final String blockId = V2841.getBlockId(state); ++ final boolean isBedrock = state != null && "minecraft:bedrock".equals(blockId); ++ final boolean isAir = state != null && "minecraft:air".equals(blockId); ++ if (isAir) { ++ missingBedrock.set((z << 4) | x); ++ } ++ ++ hasBedrock |= isBedrock; ++ } ++ } ++ ++ if (hasBedrock && missingBedrock.cardinality() != missingBedrock.size()) { ++ final String targetStatus = "full".equals(status) ? "heightmaps" : status; ++ ++ final MapType belowZeroRetrogen = Types.NBT.createEmptyMap(); ++ level.setMap("below_zero_retrogen", belowZeroRetrogen); ++ ++ belowZeroRetrogen.setString("target_status", targetStatus); ++ belowZeroRetrogen.setLongs("missing_bedrock", missingBedrock.toLongArray()); ++ ++ level.setString("Status", "empty"); ++ } ++ ++ level.setBoolean("isLightOn", false); ++ } ++ ++ private static void padCarvingMasks(final MapType level, final int newSize, final int offset) { ++ final MapType carvingMasks = level.getMap("CarvingMasks"); ++ if (carvingMasks == null) { ++ // if empty, DFU still writes ++ level.setMap("CarvingMasks", Types.NBT.createEmptyMap()); ++ return; ++ } ++ ++ for (final String key : carvingMasks.keys()) { ++ final long[] old = BitSet.valueOf(carvingMasks.getBytes(key)).toLongArray(); ++ final long[] newVal = new long[64 * newSize]; ++ ++ System.arraycopy(old, 0, newVal, 64 * offset, old.length); ++ ++ carvingMasks.setLongs(key, newVal); // no CME: key exists already ++ } ++ } ++ ++ private static void addEmptyListPadding(final MapType level, final String path) { ++ ListType list = level.getListUnchecked(path); ++ if (list != null && list.size() == 24) { ++ return; ++ } ++ ++ if (list == null) { ++ // difference from DFU: Don't create the damn thing! ++ return; ++ } ++ ++ ++ // offset the section array to the new format ++ for (int i = 0; i < 4; ++i) { ++ // always create new copies, so that modifying one doesn't modify ALL of them! ++ list.addList(0, Types.NBT.createEmptyList()); // add below ++ list.addList(Types.NBT.createEmptyList()); // add above ++ } ++ } ++ ++ private static void offsetHeightmaps(final MapType level) { ++ final MapType heightmaps = level.getMap("Heightmaps"); ++ if (heightmaps == null) { ++ return; ++ } ++ ++ for (final String key : HEIGHTMAP_TYPES) { ++ offsetHeightmap(heightmaps.getLongs(key)); ++ } ++ } ++ ++ private static void offsetHeightmap(final long[] heightmap) { ++ if (heightmap == null) { ++ return; ++ } ++ ++ // heightmaps are configured to have 9 bits per value, with 256 total values ++ // heightmaps are also relative to the lowest position ++ for (int idx = 0, len = heightmap.length; idx < len; ++idx) { ++ long curr = heightmap[idx]; ++ long next = 0L; ++ ++ for (int objIdx = 0; objIdx + 9 <= 64; objIdx += 9) { ++ final long value = (curr >> objIdx) & 511L; ++ if (value != 0L) { ++ final long offset = Math.min(511L, value + 64L); ++ ++ next |= (offset << objIdx); ++ } ++ } ++ ++ heightmap[idx] = next; ++ } ++ } ++ ++ private static MapType[] createBiomeSections(final MapType level, final boolean wantExtendedHeight, ++ final int minSection, final MutableBoolean isAlreadyExtended) { ++ final MapType[] ret = new MapType[wantExtendedHeight ? 24 : 16]; ++ ++ final int[] biomes = level.getInts("Biomes"); ++ ++ if (biomes != null && biomes.length == 1536) { // magic value for 24 sections of biomes (24 * 4^3) ++ isAlreadyExtended.setValue(true); ++ for (int sectionIndex = 0; sectionIndex < 24; ++sectionIndex) { ++ ret[sectionIndex] = createBiomeSection(biomes, sectionIndex * 64, -1); // -1 is all 1s ++ } ++ } else if (biomes != null && biomes.length == 1024) { // magic value for 24 sections of biomes (16 * 4^3) ++ for (int sectionY = 0; sectionY < 16; ++sectionY) { ++ ret[sectionY - minSection] = createBiomeSection(biomes, sectionY * 64, -1); // -1 is all 1s ++ } ++ ++ if (wantExtendedHeight) { ++ // must set the new sections at top and bottom ++ final MapType bottomCopy = createBiomeSection(biomes, 0, 15); // just want the biomes at y = 0 ++ final MapType topCopy = createBiomeSection(biomes, 1008, 15); // just want the biomes at y = 252 ++ ++ for (int sectionIndex = 0; sectionIndex < 4; ++sectionIndex) { ++ ret[sectionIndex] = bottomCopy.copy(); // copy palette so that later possible modifications don't trash all sections ++ } ++ ++ for (int sectionIndex = 20; sectionIndex < 24; ++sectionIndex) { ++ ret[sectionIndex] = topCopy.copy(); // copy palette so that later possible modifications don't trash all sections ++ } ++ } ++ } else { ++ final ListType palette = Types.NBT.createEmptyList(); ++ palette.addString("minecraft:plains"); ++ ++ for (int i = 0; i < ret.length; ++i) { ++ ret[i] = wrapPalette(palette.copy()); // copy palette so that later possible modifications don't trash all sections ++ } ++ } ++ ++ return ret; ++ } ++ ++ private static MapType createBiomeSection(final int[] biomes, final int offset, final int mask) { ++ final Int2IntLinkedOpenHashMap paletteId = new Int2IntLinkedOpenHashMap(); ++ ++ for (int idx = 0; idx < 64; ++idx) { ++ final int biome = biomes[offset + (idx & mask)]; ++ paletteId.putIfAbsent(biome, paletteId.size()); ++ } ++ ++ final ListType paletteString = Types.NBT.createEmptyList(); ++ for (final IntIterator iterator = paletteId.keySet().iterator(); iterator.hasNext();) { ++ final int biomeId = iterator.nextInt(); ++ final String biome = biomeId >= 0 && biomeId < BIOMES_BY_ID.length ? BIOMES_BY_ID[biomeId] : null; ++ paletteString.addString(biome == null ? "minecraft:plains" : biome); ++ } ++ ++ final int bitsPerObject = ceilLog2(paletteString.size()); ++ if (bitsPerObject == 0) { ++ return wrapPalette(paletteString); ++ } ++ ++ // manually create packed integer data ++ final int objectsPerValue = 64 / bitsPerObject; ++ final long[] packed = new long[(64 + objectsPerValue - 1) / objectsPerValue]; ++ ++ int shift = 0; ++ int idx = 0; ++ long curr = 0; ++ ++ for (int biome_idx = 0; biome_idx < 64; ++biome_idx) { ++ final int biome = biomes[offset + (biome_idx & mask)]; ++ ++ curr |= ((long)paletteId.get(biome)) << shift; ++ ++ shift += bitsPerObject; ++ ++ if (shift + bitsPerObject > 64) { // will next write overflow? ++ // must move to next idx ++ packed[idx++] = curr; ++ shift = 0; ++ curr = 0L; ++ } ++ } ++ ++ // don't forget to write the last one ++ if (shift != 0) { ++ packed[idx] = curr; ++ } ++ ++ return wrapPalette(paletteString, packed); ++ } ++ ++ private static MapType wrapPalette(final ListType palette) { ++ return wrapPalette(palette, null); ++ } ++ ++ private static MapType wrapPalette(final ListType palette, final long[] blockStates) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setList("palette", palette); ++ if (blockStates != null) { ++ ret.setLongs("data", blockStates); ++ } ++ ++ return ret; ++ } ++ ++ private static MapType wrapPaletteOptimised(final ListType palette, final long[] blockStates) { ++ if (palette.size() == 1) { ++ return wrapPalette(palette); ++ } ++ ++ return wrapPalette(palette, blockStates); ++ } ++ ++ public static int ceilLog2(final int value) { ++ return value == 0 ? 0 : Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ private static void updateLayers(final ListType layers) { ++ if (layers == null) { ++ return; ++ } ++ ++ layers.addMap(0, createEmptyLayer()); // add at the bottom ++ } ++ ++ private static MapType createEmptyLayer() { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ret.setInt("height", 64); ++ ret.setString("block", "minecraft:air"); ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4bdac86810c51e9f87ea82ba9f6c6d8ae8ce2bdf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2833 { ++ ++ protected static final int VERSION = MCVersions.V1_17_1 + 103; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ for (final String dimensionKey : dimensions.keys()) { ++ final MapType dimension = dimensions.getMap(dimensionKey); ++ if (!dimension.hasKey("type")) { ++ throw new IllegalStateException("Unable load old custom worlds."); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java +new file mode 100644 +index 0000000000000000000000000000000000000000..586e711163e2bdea110442dd181289fc06f6f7f1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java +@@ -0,0 +1,56 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2838 { ++ ++ protected static final int VERSION = MCVersions.V21W40A; ++ ++ public static final ImmutableMap BIOME_UPDATE = ImmutableMap.builder() ++ .put("minecraft:badlands_plateau", "minecraft:badlands") ++ .put("minecraft:bamboo_jungle_hills", "minecraft:bamboo_jungle") ++ .put("minecraft:birch_forest_hills", "minecraft:birch_forest") ++ .put("minecraft:dark_forest_hills", "minecraft:dark_forest") ++ .put("minecraft:desert_hills", "minecraft:desert") ++ .put("minecraft:desert_lakes", "minecraft:desert") ++ .put("minecraft:giant_spruce_taiga_hills", "minecraft:old_growth_spruce_taiga") ++ .put("minecraft:giant_spruce_taiga", "minecraft:old_growth_spruce_taiga") ++ .put("minecraft:giant_tree_taiga_hills", "minecraft:old_growth_pine_taiga") ++ .put("minecraft:giant_tree_taiga", "minecraft:old_growth_pine_taiga") ++ .put("minecraft:gravelly_mountains", "minecraft:windswept_gravelly_hills") ++ .put("minecraft:jungle_edge", "minecraft:sparse_jungle") ++ .put("minecraft:jungle_hills", "minecraft:jungle") ++ .put("minecraft:modified_badlands_plateau", "minecraft:badlands") ++ .put("minecraft:modified_gravelly_mountains", "minecraft:windswept_gravelly_hills") ++ .put("minecraft:modified_jungle_edge", "minecraft:sparse_jungle") ++ .put("minecraft:modified_jungle", "minecraft:jungle") ++ .put("minecraft:modified_wooded_badlands_plateau", "minecraft:wooded_badlands") ++ .put("minecraft:mountain_edge", "minecraft:windswept_hills") ++ .put("minecraft:mountains", "minecraft:windswept_hills") ++ .put("minecraft:mushroom_field_shore", "minecraft:mushroom_fields") ++ .put("minecraft:shattered_savanna", "minecraft:windswept_savanna") ++ .put("minecraft:shattered_savanna_plateau", "minecraft:windswept_savanna") ++ .put("minecraft:snowy_mountains", "minecraft:snowy_plains") ++ .put("minecraft:snowy_taiga_hills", "minecraft:snowy_taiga") ++ .put("minecraft:snowy_taiga_mountains", "minecraft:snowy_taiga") ++ .put("minecraft:snowy_tundra", "minecraft:snowy_plains") ++ .put("minecraft:stone_shore", "minecraft:stony_shore") ++ .put("minecraft:swamp_hills", "minecraft:swamp") ++ .put("minecraft:taiga_hills", "minecraft:taiga") ++ .put("minecraft:taiga_mountains", "minecraft:taiga") ++ .put("minecraft:tall_birch_forest", "minecraft:old_growth_birch_forest") ++ .put("minecraft:tall_birch_hills", "minecraft:old_growth_birch_forest") ++ .put("minecraft:wooded_badlands_plateau", "minecraft:wooded_badlands") ++ .put("minecraft:wooded_hills", "minecraft:forest") ++ .put("minecraft:wooded_mountains", "minecraft:windswept_forest") ++ .put("minecraft:lofty_peaks", "minecraft:jagged_peaks") ++ .put("minecraft:snowcapped_peaks", "minecraft:frozen_peaks") ++ .build(); ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_UPDATE::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java +new file mode 100644 +index 0000000000000000000000000000000000000000..41b41ff084662bbc2e323713473e4e13b8e50cd7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java +@@ -0,0 +1,205 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import ca.spottedleaf.dataconverter.util.IntegerUtil; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V2841 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 1; ++ ++ protected static final Set ALWAYS_WATERLOGGED = new HashSet<>(Arrays.asList( ++ "minecraft:bubble_column", ++ "minecraft:kelp", ++ "minecraft:kelp_plant", ++ "minecraft:seagrass", ++ "minecraft:tall_seagrass" ++ )); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType level = root.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ { ++ // Why it's renamed here and not the next data version is beyond me. ++ final MapType liquidTicks = level.getMap("LiquidTicks"); ++ if (liquidTicks != null) { ++ level.remove("LiquidTicks"); ++ level.setMap("fluid_ticks", liquidTicks); ++ } ++ } ++ ++ final Int2ObjectOpenHashMap sectionBlocks = new Int2ObjectOpenHashMap<>(); ++ final ListType sections = level.getList("Sections", ObjectType.MAP); ++ int minSection = 0; // TODO wtf is this ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final int sectionY = section.getInt("Y"); ++ if (sectionY < minSection && section.hasKey("biomes")) { ++ minSection = sectionY; ++ } ++ ++ final MapType blockStates = section.getMap("block_states"); ++ if (blockStates == null) { ++ continue; ++ } ++ ++ sectionBlocks.put(sectionY, new SimplePaletteReader(section.getList("palette", ObjectType.MAP), section.getLongs("data"))); ++ } ++ } ++ ++ level.setByte("yPos", (byte)minSection); // TODO ??????????????????????????????????????? ++ ++ if (level.hasKey("fluid_ticks") || level.hasKey("TileTicks")) { ++ return null; ++ } ++ ++ final int sectionX = level.getInt("xPos"); ++ final int sectionZ = level.getInt("zPos"); ++ ++ final ListType fluidTicks = level.getList("LiquidsToBeTicked", ObjectType.LIST); ++ final ListType blockTicks = level.getList("ToBeTicked", ObjectType.LIST); ++ level.remove("LiquidsToBeTicked"); ++ level.remove("ToBeTicked"); ++ ++ level.setList("fluid_ticks", migrateTickList(fluidTicks, false, sectionBlocks, sectionX, minSection, sectionZ)); ++ level.setList("TileTicks", migrateTickList(blockTicks, true, sectionBlocks, sectionX, minSection, sectionZ)); ++ ++ return null; ++ } ++ }); ++ } ++ ++ public static ListType migrateTickList(final ListType ticks, final boolean blockTicks, final Int2ObjectOpenHashMap sectionBlocks, ++ final int sectionX, final int minSection, final int sectionZ) { ++ final ListType ret = Types.NBT.createEmptyList(); ++ ++ if (ticks == null) { ++ return ret; ++ } ++ ++ for (int sectionIndex = 0, totalSections = ticks.size(); sectionIndex < totalSections; ++sectionIndex) { ++ final int sectionY = sectionIndex + minSection; ++ final ListType sectionTicks = ticks.getList(sectionIndex); ++ final SimplePaletteReader palette = sectionBlocks.get(sectionY); ++ ++ for (int i = 0, len = sectionTicks.size(); i < len; ++i) { ++ final int localIndex = sectionTicks.getShort(i) & 0xFFFF; ++ final MapType blockState = palette == null ? null : palette.getState(localIndex); ++ final String subjectId = blockTicks ? getBlockId(blockState) : getLiquidId(blockState); ++ ++ ret.addMap(createNewTick(subjectId, localIndex, sectionX, sectionY, sectionZ)); ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static MapType createNewTick(final String subjectId, final int localIndex, final int sectionX, final int sectionY, final int sectionZ) { ++ final int newX = (localIndex & 15) + (sectionX << 4); ++ final int newZ = ((localIndex >> 4) & 15) + (sectionZ << 4); ++ final int newY = ((localIndex >> 8) & 15) + (sectionY << 4); ++ ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setString("i", subjectId); ++ ret.setInt("x", newX); ++ ret.setInt("y", newY); ++ ret.setInt("z", newZ); ++ ret.setInt("t", 0); ++ ret.setInt("p", 0); ++ ++ return ret; ++ } ++ ++ public static String getBlockId(final MapType blockState) { ++ return blockState == null ? "minecraft:air" : blockState.getString("Name", "minecraft:air"); ++ } ++ ++ private static String getLiquidId(final MapType blockState) { ++ if (blockState == null) { ++ return "minecraft:empty"; ++ } ++ ++ final String name = blockState.getString("Name"); ++ if (ALWAYS_WATERLOGGED.contains(name)) { ++ return "minecraft:water"; ++ } ++ ++ final MapType properties = blockState.getMap("Properties"); ++ if ("minecraft:water".equals(name)) { ++ return properties != null && properties.getInt("level") == 0 ? "minecraft:water" : "minecraft:flowing_water"; ++ } else if ("minecraft:lava".equals(name)) { ++ return properties != null && properties.getInt("level") == 0 ? "minecraft:lava" : "minecraft:flowing_lava"; ++ } ++ ++ return (properties != null && properties.getBoolean("waterlogged")) ? "minecraft:water" : "minecraft:empty"; ++ } ++ ++ public static final class SimplePaletteReader { ++ ++ public final ListType palette; ++ public final long[] data; ++ private final int bitsPerValue; ++ private final long mask; ++ private final int valuesPerLong; ++ ++ public SimplePaletteReader(final ListType palette, final long[] data) { ++ this.palette = palette == null ? null : (palette.size() == 0 ? null : palette); ++ this.data = data; ++ this.bitsPerValue = Math.max(4, IntegerUtil.ceilLog2(this.palette == null ? 0 : this.palette.size())); ++ this.mask = (1L << this.bitsPerValue) - 1L; ++ this.valuesPerLong = (int)(64L / this.bitsPerValue); ++ } ++ ++ public MapType getState(final int x, final int y, final int z) { ++ final int index = x | (z << 4) | (y << 8); ++ return this.getState(index); ++ } ++ ++ public MapType getState(final int index) { ++ final ListType palette = this.palette; ++ if (palette == null) { ++ return null; ++ } ++ ++ final int paletteSize = palette.size(); ++ if (paletteSize == 1) { ++ return palette.getMap(0); ++ } ++ ++ // x86 div computes mod. no loss here using mod ++ // if needed, can compute magic mul and shift for div values using IntegerUtil ++ final int dataIndex = index / this.valuesPerLong; ++ final int localIndex = (index % this.valuesPerLong) * this.bitsPerValue; ++ final long[] data = this.data; ++ if (dataIndex < 0 || dataIndex >= data.length) { ++ return null; ++ } ++ ++ final long value = data[dataIndex]; ++ final int paletteIndex = (int)((value >>> localIndex) & this.mask); ++ if (paletteIndex < 0 || paletteIndex >= paletteSize) { ++ return null; ++ } ++ ++ return palette.getMap(paletteIndex); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f06e24bb87baf01b1386fb7a6af1ea04f4d6f2ef +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V2842 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { ++ final MapType level = root.getMap("Level"); ++ root.remove("Level"); ++ ++ if (!root.isEmpty()) { ++ for (final String key : root.keys()) { ++ if (level.hasKey(key)) { ++ // Don't clobber level's data ++ continue; ++ } ++ level.setGeneric(key, root.getGeneric(key)); ++ } ++ } ++ ++ // Rename top level first ++ RenameHelper.renameSingle(level, "TileEntities", "block_entities"); ++ RenameHelper.renameSingle(level, "TileTicks", "block_ticks"); ++ RenameHelper.renameSingle(level, "Entities", "entities"); ++ RenameHelper.renameSingle(level, "Sections", "sections"); ++ RenameHelper.renameSingle(level, "Structures", "structures"); ++ ++ // 2nd level ++ final MapType structures = level.getMap("structures"); ++ if (structures != null) { ++ RenameHelper.renameSingle(structures, "Starts", "starts"); ++ } ++ ++ return level; // Level is now root tag. ++ } ++ }); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); ++ ++ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); ++ if (blockTicks != null) { ++ for (int i = 0, len = blockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6333eabb123f13495f7a828fe79c204aff9dcedd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java +@@ -0,0 +1,105 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import java.util.Map; ++ ++public final class V2843 { ++ ++ protected static final int VERSION = MCVersions.V21W42A + 3; ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")::get); ++ ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ private static void moveOutOfBoundTicks(final ListType ticks, final MapType chunkRoot, final int chunkX, final int chunkZ, final String intoKey) { ++ if (ticks == null) { ++ return; ++ } ++ ++ ListType outOfBounds = null; ++ for (int i = 0, len = ticks.size(); i < len; ++i) { ++ final MapType tick = ticks.getMap(i); ++ final int x = tick.getInt("x"); ++ final int z = tick.getInt("z"); ++ // anything > 1 is lost, anything less than 1 stays. ++ if (Math.max(Math.abs(chunkX - (x >> 4)), Math.abs(chunkZ - (z >> 4))) == 1) { ++ // DFU doesn't remove, so neither do we. ++ if (outOfBounds == null) { ++ outOfBounds = Types.NBT.createEmptyList(); ++ } ++ outOfBounds.addMap(tick); ++ } ++ } ++ ++ if (outOfBounds != null) { ++ MapType upgradeData = chunkRoot.getMap("UpgradeData"); ++ if (upgradeData == null) { ++ chunkRoot.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); ++ } ++ upgradeData.setList(intoKey, outOfBounds); ++ } ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ // After renames, so use new names ++ final int x = data.getInt("xPos"); ++ final int z = data.getInt("zPos"); ++ ++ moveOutOfBoundTicks(data.getList("block_ticks", ObjectType.MAP), data, x, z, "neighbor_block_ticks"); ++ moveOutOfBoundTicks(data.getList("fluid_ticks", ObjectType.MAP), data, x, z, "neighbor_fluid_ticks"); ++ ++ return null; ++ } ++ }); ++ ++ // DFU is missing schema for UpgradeData block names ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); ++ ++ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); ++ if (blockTicks != null) { ++ for (int i = 0, len = blockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ ++ final MapType upgradeData = data.getMap("UpgradeData"); ++ if (upgradeData != null) { ++ // Even though UpgradeData will retrieve the block from the World when the type no longer exists, ++ // the type from the world may not match what was actually queued. So, even though it may look like we ++ // can skip the walker here, we actually don't if we want to be thorough. ++ final ListType neighbourBlockTicks = upgradeData.getList("neighbor_block_ticks", ObjectType.MAP); ++ if (neighbourBlockTicks != null) { ++ for (int i = 0, len = neighbourBlockTicks.size(); i < len; ++i) { ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, neighbourBlockTicks.getMap(i), "i", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections != null) { ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); ++ } ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java +new file mode 100644 +index 0000000000000000000000000000000000000000..236327249d2b95b799b90172d457601167492249 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V2846 { ++ ++ protected static final int VERSION = MCVersions.V21W44A + 1; ++ ++ public static void register() { ++ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( ++ "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows", ++ "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height", ++ "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava" ++ )::get); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94ab7be8c34d2ebb557df5a0864130f7f12c2185 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java +@@ -0,0 +1,29 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2852 { ++ ++ protected static final int VERSION = MCVersions.V1_18_PRE5 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ for (final String dimensionKey : dimensions.keys()) { ++ final MapType dimension = dimensions.getMap(dimensionKey); ++ if (!dimension.hasKey("type")) { ++ throw new IllegalStateException("Unable load old custom worlds."); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7af7bf450080f65b8b7d7a8d2f941846c029e504 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java +@@ -0,0 +1,56 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V2967 { ++ ++ protected static final int VERSION = MCVersions.V22W05A; ++ ++ public static void register() { ++ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType dimensions = data.getMap("dimensions"); ++ ++ if (dimensions == null) { ++ return null; ++ } ++ ++ for (final String dimension : dimensions.keys()) { ++ final MapType dimensionData = dimensions.getMap(dimension); ++ if (dimensionData == null) { ++ continue; ++ } ++ ++ final MapType generator = dimensionData.getMap("generator"); ++ if (generator == null) { ++ continue; ++ } ++ ++ final MapType settings = generator.getMap("settings"); ++ if (settings == null) { ++ continue; ++ } ++ ++ final MapType structures = settings.getMap("structures"); ++ if (structures == null) { ++ continue; ++ } ++ ++ for (final String structureKey : structures.keys()) { ++ structures.getMap(structureKey).setString("type", "minecraft:random_spread"); ++ } ++ ++ final MapType stronghold = structures.getMap("stronghold"); ++ stronghold.setString("type", "minecraft:concentric_rings"); ++ structures.setMap("minecraft:stronghold", stronghold.copy()); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fa824cdf629caec745eff7c09eb4570c62263752 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java +@@ -0,0 +1,192 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.common.collect.ImmutableMap; ++import it.unimi.dsi.fastutil.objects.Object2IntMap; ++import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; ++import java.util.HashMap; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++ ++public final class V2970 { ++ ++ protected static final int VERSION = MCVersions.V22W07A + 1; ++ private static final Map CONVERSION_MAP = new HashMap<>( ++ ImmutableMap.builder() ++ .put("mineshaft", BiomeRemap.create(Map.of(List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands"), "minecraft:mineshaft_mesa"), "minecraft:mineshaft")) ++ .put("shipwreck", BiomeRemap.create(Map.of(List.of("minecraft:beach", "minecraft:snowy_beach"), "minecraft:shipwreck_beached"), "minecraft:shipwreck")) ++ .put("ocean_ruin", BiomeRemap.create(Map.of(List.of("minecraft:warm_ocean", "minecraft:lukewarm_ocean", "minecraft:deep_lukewarm_ocean"), "minecraft:ocean_ruin_warm"), "minecraft:ocean_ruin_cold")) ++ .put("village", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:village_desert", List.of("minecraft:savanna"), "minecraft:village_savanna", List.of("minecraft:snowy_plains"), "minecraft:village_snowy", List.of("minecraft:taiga"), "minecraft:village_taiga"), "minecraft:village_plains")) ++ .put("ruined_portal", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:ruined_portal_desert", List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands", "minecraft:windswept_hills", "minecraft:windswept_forest", "minecraft:windswept_gravelly_hills", "minecraft:savanna_plateau", "minecraft:windswept_savanna", "minecraft:stony_shore", "minecraft:meadow", "minecraft:frozen_peaks", "minecraft:jagged_peaks", "minecraft:stony_peaks", "minecraft:snowy_slopes"), "minecraft:ruined_portal_mountain", List.of("minecraft:bamboo_jungle", "minecraft:jungle", "minecraft:sparse_jungle"), "minecraft:ruined_portal_jungle", List.of("minecraft:deep_frozen_ocean", "minecraft:deep_cold_ocean", "minecraft:deep_ocean", "minecraft:deep_lukewarm_ocean", "minecraft:frozen_ocean", "minecraft:ocean", "minecraft:cold_ocean", "minecraft:lukewarm_ocean", "minecraft:warm_ocean"), "minecraft:ruined_portal_ocean"), "minecraft:ruined_portal")) // Fix MC-248814, ruined_portal_standard->ruined_portal ++ .put("pillager_outpost", BiomeRemap.create("minecraft:pillager_outpost")) ++ .put("mansion", BiomeRemap.create("minecraft:mansion")) ++ .put("jungle_pyramid", BiomeRemap.create("minecraft:jungle_pyramid")) ++ .put("desert_pyramid", BiomeRemap.create("minecraft:desert_pyramid")) ++ .put("igloo", BiomeRemap.create("minecraft:igloo")) ++ .put("swamp_hut", BiomeRemap.create("minecraft:swamp_hut")) ++ .put("stronghold", BiomeRemap.create("minecraft:stronghold")) ++ .put("monument", BiomeRemap.create("minecraft:monument")) ++ .put("fortress", BiomeRemap.create("minecraft:fortress")) ++ .put("endcity", BiomeRemap.create("minecraft:end_city")) ++ .put("buried_treasure", BiomeRemap.create("minecraft:buried_treasure")) ++ .put("nether_fossil", BiomeRemap.create("minecraft:nether_fossil")) ++ .put("bastion_remnant", BiomeRemap.create("minecraft:bastion_remnant")) ++ .build() ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ private static Object2IntOpenHashMap countBiomes(final MapType chunk) { ++ final ListType sections = chunk.getList("sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ final Object2IntOpenHashMap ret = new Object2IntOpenHashMap<>(); ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ ++ final MapType biomes = section.getMap("biomes"); ++ ++ if (biomes == null) { ++ continue; ++ } ++ ++ final ListType palette = biomes.getList("palette", ObjectType.STRING); ++ ++ if (palette == null) { ++ continue; ++ } ++ ++ for (int k = 0, len2 = palette.size(); k < len2; ++k) { ++ ret.addTo(palette.getString(k), 1); ++ } ++ } ++ ++ return ret; ++ } ++ ++ private static String getStructureConverted(String id, final Object2IntOpenHashMap biomeCount) { ++ id = id.toLowerCase(Locale.ROOT); ++ final BiomeRemap remap = CONVERSION_MAP.get(id); ++ if (remap == null) { ++ throw new IllegalArgumentException("Unknown structure " + id); ++ } ++ if (remap.biomeToNewStructure == null || biomeCount == null) { ++ return remap.dfl; ++ } ++ ++ final Object2IntOpenHashMap remapCount = new Object2IntOpenHashMap<>(); ++ ++ for (final Iterator> iterator = biomeCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ final Object2IntMap.Entry entry = iterator.next(); ++ final String remappedStructure = remap.biomeToNewStructure.get(entry.getKey()); ++ if (remappedStructure != null) { ++ remapCount.addTo(remappedStructure, entry.getIntValue()); ++ } ++ } ++ ++ String converted = remap.dfl; ++ int maxCount = 0; ++ ++ for (final Iterator> iterator = remapCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ final Object2IntMap.Entry entry = iterator.next(); ++ final int count = entry.getIntValue(); ++ if (count > maxCount) { ++ maxCount = count; ++ converted = entry.getKey(); ++ } ++ } ++ ++ return converted; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType structures = data.getMap("structures"); ++ if (structures == null || structures.isEmpty()) { ++ return null; ++ } ++ ++ final Object2IntOpenHashMap biomeCounts = countBiomes(data); ++ ++ final MapType starts = structures.getMap("starts"); ++ final MapType references = structures.getMap("References"); ++ ++ if (starts != null) { ++ final MapType newStarts = Types.NBT.createEmptyMap(); ++ structures.setMap("starts", newStarts); ++ ++ for (final String key : starts.keys()) { ++ final MapType value = starts.getMap(key); ++ if ("INVALID".equals(value.getString("id", "INVALID"))) { ++ continue; ++ } ++ ++ final String remapped = getStructureConverted(key, biomeCounts); ++ value.setString("id", remapped); ++ newStarts.setMap(remapped, value); ++ } ++ } ++ ++ // This TRULY is a guess, no idea what biomes the referent has. ++ if (references != null) { ++ final MapType newReferences = Types.NBT.createEmptyMap(); ++ structures.setMap("References", newReferences); ++ for (final String key : references.keys()) { ++ final long[] value = references.getLongs(key); ++ if (value.length == 0) { ++ continue; ++ } ++ ++ newReferences.setLongs(getStructureConverted(key, biomeCounts), value); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private static final class BiomeRemap { ++ ++ public final Map biomeToNewStructure; ++ public final String dfl; ++ ++ private BiomeRemap(final Map biomeToNewStructure, final String dfl) { ++ this.biomeToNewStructure = biomeToNewStructure; ++ this.dfl = dfl; ++ } ++ ++ public static BiomeRemap create(final String newId) { ++ return new BiomeRemap(null, newId); ++ } ++ ++ public static BiomeRemap create(final Map, String> biomeMap, final String newId) { ++ final Map biomeToNewStructure = new HashMap<>(); ++ ++ for (final Map.Entry, String> entry : biomeMap.entrySet()) { ++ final List biomes = entry.getKey(); ++ final String newBiomeStructure = entry.getValue(); ++ ++ for (int i = 0, len = biomes.size(); i < len; ++i) { ++ final String biome = biomes.get(i); ++ if (biomeToNewStructure.putIfAbsent(biome, newBiomeStructure) != null) { ++ throw new IllegalStateException("Duplicate biome remap: " + biome + " -> " + newBiomeStructure + ", but already mapped to " + biomeToNewStructure.get(biome)); ++ } ++ } ++ } ++ ++ return new BiomeRemap(biomeToNewStructure, newId); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java +new file mode 100644 +index 0000000000000000000000000000000000000000..97da66165f3e3788af0dfe667509ca7edb15b0a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++ ++public final class V3077 { ++ ++ protected static final int VERSION = MCVersions.V1_18_2 + 102; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final boolean isLightOn = data.getBoolean("isLightOn"); ++ if (isLightOn) { ++ return null; ++ } ++ ++ final ListType sections = data.getList("sections", ObjectType.MAP); ++ if (sections == null) { ++ return null; ++ } ++ ++ for (int i = 0, len = sections.size(); i < len; ++i) { ++ final MapType section = sections.getMap(i); ++ section.remove("BlockLight"); ++ section.remove("SkyLight"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4271eae27756eb5cbad77679dd562e676d644748 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3078 { ++ ++ protected static final int VERSION = MCVersions.V1_18_2 + 103; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:frog"); ++ registerMob("minecraft:tadpole"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_shrieker", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c0cd7cd8c2656713b97f83b7e02b65008b62c297 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3081 { ++ ++ protected static final int VERSION = MCVersions.V22W11A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:warden"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:warden", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ab6ebf4d10842d20c20bcbcc76483d9cfe081862 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java +@@ -0,0 +1,14 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3082 { ++ ++ protected static final int VERSION = MCVersions.V22W11A + 2; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_boat", new DataWalkerItemLists("Items")); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b133d6a806d571b976d8e96b9ca1e3b6933af20 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java +@@ -0,0 +1,20 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V3083 { ++ ++ protected static final int VERSION = MCVersions.V22W12A + 1; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("minecraft:allay"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new GameEventListenerWalker()); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java +new file mode 100644 +index 0000000000000000000000000000000000000000..52d8510e00d2373226f35e77db6fc7a893ec0764 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java +@@ -0,0 +1,39 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++import com.google.common.collect.ImmutableMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3084 { ++ ++ protected static final int VERSION = MCVersions.V22W12A + 2; ++ ++ protected static final Map GAME_EVENT_RENAMES = new HashMap<>(ImmutableMap.builder() ++ .put("minecraft:block_press", "minecraft:block_activate") ++ .put("minecraft:block_switch", "minecraft:block_activate") ++ .put("minecraft:block_unpress", "minecraft:block_deactivate") ++ .put("minecraft:block_unswitch", "minecraft:block_deactivate") ++ .put("minecraft:drinking_finish", "minecraft:drink") ++ .put("minecraft:elytra_free_fall", "minecraft:elytra_glide") ++ .put("minecraft:entity_damaged", "minecraft:entity_damage") ++ .put("minecraft:entity_dying", "minecraft:entity_die") ++ .put("minecraft:entity_killed", "minecraft:entity_die") ++ .put("minecraft:mob_interact", "minecraft:entity_interact") ++ .put("minecraft:ravager_roar", "minecraft:entity_roar") ++ .put("minecraft:ring_bell", "minecraft:block_change") ++ .put("minecraft:shulker_close", "minecraft:container_close") ++ .put("minecraft:shulker_open", "minecraft:container_open") ++ .put("minecraft:wolf_shaking", "minecraft:entity_shake") ++ .build() ++ ); ++ ++ public static void register() { ++ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.GAME_EVENT_NAME, (final String name) -> { ++ return GAME_EVENT_RENAMES.get(NamespaceUtil.correctNamespace(name)); ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java +new file mode 100644 +index 0000000000000000000000000000000000000000..554df81bb4f1a66bce539b42493f3ea7d4dff153 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java +@@ -0,0 +1,51 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import com.google.common.collect.ImmutableMap; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V3086 { ++ ++ protected static final int VERSION = MCVersions.V22W13A + 1; ++ ++ protected static final Int2ObjectOpenHashMap CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); ++ static { ++ CAT_ID_CONVERSION.defaultReturnValue("minecraft:tabby"); ++ CAT_ID_CONVERSION.put(0, "minecraft:tabby"); ++ CAT_ID_CONVERSION.put(1, "minecraft:black"); ++ CAT_ID_CONVERSION.put(2, "minecraft:red"); ++ CAT_ID_CONVERSION.put(3, "minecraft:siamese"); ++ CAT_ID_CONVERSION.put(4, "minecraft:british"); ++ CAT_ID_CONVERSION.put(5, "minecraft:calico"); ++ CAT_ID_CONVERSION.put(6, "minecraft:persian"); ++ CAT_ID_CONVERSION.put(7, "minecraft:ragdoll"); ++ CAT_ID_CONVERSION.put(8, "minecraft:white"); ++ CAT_ID_CONVERSION.put(9, "minecraft:jellie"); ++ CAT_ID_CONVERSION.put(10, "minecraft:all_black"); ++ } ++ ++ protected static final Map CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(ImmutableMap.builder() ++ .put("textures/entity/cat/tabby.png", "minecraft:tabby") ++ .put("textures/entity/cat/black.png", "minecraft:black") ++ .put("textures/entity/cat/red.png", "minecraft:red") ++ .put("textures/entity/cat/siamese.png", "minecraft:siamese") ++ .put("textures/entity/cat/british_shorthair.png", "minecraft:british") ++ .put("textures/entity/cat/calico.png", "minecraft:calico") ++ .put("textures/entity/cat/persian.png", "minecraft:persian") ++ .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll") ++ .put("textures/entity/cat/white.png", "minecraft:white") ++ .put("textures/entity/cat/jellie.png", "minecraft:jellie") ++ .put("textures/entity/cat/all_black.png", "minecraft:all_black") ++ .build() ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityToVariant(VERSION, "CatType", CAT_ID_CONVERSION::get)); ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", CAT_ADVANCEMENTS_CONVERSION::get)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8cc7cadb921d52ebb5b8ed25078145536db5e7b5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; ++ ++public final class V3087 { ++ ++ protected static final int VERSION = MCVersions.V22W13A + 2; ++ ++ protected static Int2ObjectOpenHashMap FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); ++ static { ++ FROG_ID_CONVERSION.put(0, "minecraft:temperate"); ++ FROG_ID_CONVERSION.put(1, "minecraft:warm"); ++ FROG_ID_CONVERSION.put(2, "minecraft:cold"); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:frog", new ConverterEntityToVariant(VERSION, "Variant", FROG_ID_CONVERSION::get)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d21124f6e70c887b9ca67305c8b0c1f4668af8e2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java +@@ -0,0 +1,67 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++import ca.spottedleaf.dataconverter.util.NamespaceUtil; ++import java.util.Arrays; ++import java.util.HashSet; ++import java.util.Set; ++ ++public final class V3088 { ++ ++ // this class originally targeted 3079 but was changed to target a later version without changing the converter, zero clue why ++ protected static final int VERSION = MCVersions.V22W14A; ++ ++ private static final Set STATUSES_TO_SKIP_BLENDING = new HashSet<>( ++ Arrays.asList( ++ "minecraft:empty", ++ "minecraft:structure_starts", ++ "minecraft:structure_references", ++ "minecraft:biomes" ++ ) ++ ); ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ ++ private static MapType createBlendingData(final int height, final int minY) { ++ final MapType ret = Types.NBT.createEmptyMap(); ++ ++ ret.setInt("min_section", minY >> 4); ++ ret.setInt("max_section", (minY + height) >> 4); ++ ++ return ret; ++ } ++ ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("blending_data"); ++ final MapType context = data.getMap("__context"); ++ if (!"minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { ++ return null; ++ } ++ ++ final String status = NamespaceUtil.correctNamespace(data.getString("Status")); ++ if (status == null) { ++ return null; ++ } ++ ++ final MapType belowZeroRetrogen = data.getMap("below_zero_retrogen"); ++ ++ if (!STATUSES_TO_SKIP_BLENDING.contains(status)) { ++ data.setMap("blending_data", createBlendingData(384, -64)); ++ } else if (belowZeroRetrogen != null) { ++ final String realStatus = NamespaceUtil.correctNamespace(belowZeroRetrogen.getString("target_status", "empty")); ++ if (!STATUSES_TO_SKIP_BLENDING.contains(realStatus)) { ++ data.setMap("blending_data", createBlendingData(256, 0)); ++ } ++ } ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b3250a0b5ae2ab0aa5fffaace882052388861fd8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java +@@ -0,0 +1,23 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3090 { ++ ++ protected static final int VERSION = MCVersions.V22W15A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ RenameHelper.renameSingle(data, "Motive", "variant"); ++ RenameHelper.renameSingle(data, "Facing", "facing"); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8354c85fc4d92f36555c7de9dc0dffd1da05529a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java +@@ -0,0 +1,22 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3093 { ++ ++ protected static final int VERSION = MCVersions.V22W17A; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:goat", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setBoolean("HasLeftHorn", true); ++ data.setBoolean("HasRightHorn", true); ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java +new file mode 100644 +index 0000000000000000000000000000000000000000..39540b5f76af1c7d51a51db9d711f32a3c7f624c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java +@@ -0,0 +1,42 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3094 { ++ ++ public static final int VERSION = MCVersions.V22W17A + 1; ++ ++ private static final String[] SOUND_VARIANT_TO_INSTRUMENT = new String[] { ++ "minecraft:ponder_goat_horn", ++ "minecraft:sing_goat_horn", ++ "minecraft:seek_goat_horn", ++ "minecraft:feel_goat_horn", ++ "minecraft:admire_goat_horn", ++ "minecraft:call_goat_horn", ++ "minecraft:yearn_goat_horn", ++ "minecraft:dream_goat_horn" ++ }; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:goat_horn", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ ++ if (tag == null) { ++ return null; ++ } ++ ++ final int soundVariant = tag.getInt("SoundVariant"); ++ tag.remove("SoundVariant"); ++ ++ tag.setString("instrument", SOUND_VARIANT_TO_INSTRUMENT[soundVariant < 0 || soundVariant >= SOUND_VARIANT_TO_INSTRUMENT.length ? 0 : soundVariant]); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d5ac17b59c0dcc9baaeff022ecbf827c237cf9d6 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java +@@ -0,0 +1,61 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityVariantRename; ++import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterPoiDelete; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class V3097 { ++ ++ private static final int VERSION = MCVersions.V22W19A + 1; ++ ++ public static void register() { ++ final DataConverter, MapType> removeFilteredBookText = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ tag.remove("filtered_title"); ++ tag.remove("filtered_pages"); ++ ++ return null; ++ } ++ }; ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:writable_book", removeFilteredBookText); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:written_book", removeFilteredBookText); ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("FilteredText1"); ++ data.remove("FilteredText2"); ++ data.remove("FilteredText3"); ++ data.remove("FilteredText4"); ++ ++ return null; ++ } ++ }); ++ ++ final Map britishRenamer = new HashMap<>(Map.of( ++ "minecraft:british", "minecraft:british_shorthair" ++ )); ++ final Set poiRemove = new HashSet<>(Set.of( ++ "minecraft:unemployed", ++ "minecraft:nitwit" ++ )); ++ ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityVariantRename(VERSION, britishRenamer::get)); ++ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", britishRenamer::get)); ++ MCTypeRegistry.POI_CHUNK.addStructureConverter(new ConverterPoiDelete(VERSION, poiRemove::contains)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java +new file mode 100644 +index 0000000000000000000000000000000000000000..381b49f2c50d46e52f7f9c8f6baede4e72eb343d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java +@@ -0,0 +1,27 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V3108 { ++ ++ private static final int VERSION = MCVersions.V1_19_1_PRE1 + 1; ++ ++ public static void register() { ++ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType context = data.getMap("__context"); ++ if ("minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { ++ return null; ++ } ++ ++ data.remove("blending_data"); ++ ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6ab2bf99d72983fc2742a1f6f2f7fa671611526d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java +@@ -0,0 +1,21 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++ ++public final class V501 { ++ ++ protected static final int VERSION = MCVersions.V16W20A; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ registerMob("PolarBear"); ++ } ++ ++ private V501() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java +new file mode 100644 +index 0000000000000000000000000000000000000000..febeab68a5eec229ecca4f9e7b82c9ca99b3dbe1 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java +@@ -0,0 +1,46 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.concurrent.ThreadLocalRandom; ++ ++public final class V502 { ++ ++ protected static final int VERSION = MCVersions.V16W20A + 1; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, (final String name) -> { ++ return "minecraft:cooked_fished".equals(name) ? "minecraft:cooked_fish" : null; ++ }); ++ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.getBoolean("IsVillager")) { ++ return null; ++ } ++ ++ data.remove("IsVillager"); ++ ++ if (data.hasKey("ZombieType")) { ++ return null; ++ } ++ ++ int type = data.getInt("VillagerProfession", -1); ++ // Vanilla doesn't remove the profession tag, so we don't! ++ if (type < 0 || type >= 6) { ++ type = ThreadLocalRandom.current().nextInt(6); ++ } ++ ++ data.setInt("ZombieType", type); ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V502() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java +new file mode 100644 +index 0000000000000000000000000000000000000000..30769f902c7d694bce41ab319d0b9a87c6103f11 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V505 { ++ ++ protected static final int VERSION = MCVersions.V16W21B + 1; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.setString("useVbo", "true"); ++ return null; ++ } ++ }); ++ } ++ ++ private V505() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f46b1a0c4bc96d638853cc61e5703798dbf6b886 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java +@@ -0,0 +1,33 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V700 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 188; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Guardian", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (data.getBoolean("Elder")) { ++ data.setString("id", "ElderGuardian"); ++ } ++ data.remove("Elder"); ++ return null; ++ } ++ }); ++ ++ registerMob("ElderGuardian"); ++ } ++ ++ private V700() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java +new file mode 100644 +index 0000000000000000000000000000000000000000..42f173f426fb7d26e5ddb5a1c92c63b2e6a4930c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java +@@ -0,0 +1,43 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V701 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 189; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Skeleton", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int type = data.getInt("SkeletonType"); ++ data.remove("SkeletonType"); ++ ++ switch (type) { ++ case 1: ++ data.setString("id", "WitherSkeleton"); ++ break; ++ case 2: ++ data.setString("id", "Stray"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("WitherSkeleton"); ++ registerMob("Stray"); ++ } ++ ++ private V701() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5cc91edde9c8160f75165bcef554023246e0a224 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java +@@ -0,0 +1,53 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V702 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 190; ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int zombieType = data.getInt("ZombieType"); ++ data.remove("ZombieType"); ++ ++ switch (zombieType) { ++ case 0: ++ default: ++ break; ++ ++ case 1: ++ case 2: ++ case 3: ++ case 4: ++ case 5: ++ data.setString("id", "ZombieVillager"); ++ data.setInt("Profession", zombieType - 1); ++ break; ++ ++ case 6: ++ data.setString("id", "Husk"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ registerMob("ZombieVillager"); ++ registerMob( "Husk"); ++ } ++ ++ private V702() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java +new file mode 100644 +index 0000000000000000000000000000000000000000..88d9c0fcd88ccfd6d6b46ae050914079c816fa3f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java +@@ -0,0 +1,66 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V703 { ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 191; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final int type = data.getInt("Type"); ++ data.remove("Type"); ++ ++ switch (type) { ++ case 0: ++ default: ++ data.setString("id", "Horse"); ++ break; ++ ++ case 1: ++ data.setString("id", "Donkey"); ++ break; ++ ++ case 2: ++ data.setString("id", "Mule"); ++ break; ++ ++ case 3: ++ data.setString("id", "ZombieHorse"); ++ break; ++ ++ case 4: ++ data.setString("id", "SkeletonHorse"); ++ break; ++ } ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private V703() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0185fd60943dfb53c9d0707f1ff879658230d488 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java +@@ -0,0 +1,341 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import net.minecraft.core.Registry; ++import net.minecraft.world.item.BlockItem; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.block.EntityBlock; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V704 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 192; ++ ++ public static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap() { ++ @Override ++ public String put(final String key, final String value) { ++ if (this.containsKey(key)) { ++ LOGGER.error("Duplicate item id to tile key: " + key); ++ throw new RuntimeException(); // only devs should see the consequence of this... at least start up the damn thing... ++ } ++ return super.put(key, value); ++ } ++ }; ++ static { ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "minecraft:furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "minecraft:furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "minecraft:chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "minecraft:chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "minecraft:ender_chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "minecraft:jukebox"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "minecraft:dispenser"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "minecraft:dropper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "minecraft:mob_spawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spawner", "minecraft:mob_spawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "minecraft:noteblock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "minecraft:brewing_stand"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "minecraft:enchanting_table"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "minecraft:beacon"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "minecraft:daylight_detector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "minecraft:hopper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "minecraft:flower_pot"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "minecraft:command_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_shulker_box", "minecraft:shulker_box"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "minecraft:piston"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "minecraft:structure_block"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "minecraft:end_portal"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "minecraft:end_gateway"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skeleton_skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wither_skeleton_skull", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:zombie_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:player_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:creeper_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dragon_head", "minecraft:skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:barrel", "minecraft:barrel"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:conduit", "minecraft:conduit"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:smoker", "minecraft:smoker"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blast_furnace", "minecraft:blast_furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lectern", "minecraft:lectern"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bell", "minecraft:bell"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jigsaw", "minecraft:jigsaw"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:campfire", "minecraft:campfire"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bee_nest", "minecraft:beehive"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beehive", "minecraft:beehive"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_sensor", "minecraft:sculk_sensor"); ++ ++ // These are missing from Vanilla (TODO check on update) ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enchanting_table", "minecraft:enchanting_table"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:comparator", "minecraft:comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_bed", "minecraft:bed"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_banner", "minecraft:banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:soul_campfire", "minecraft:campfire"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_catalyst", "minecraft:sculk_catalyst"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_sign", "minecraft:sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_shrieker", "minecraft:sculk_shrieker"); ++ } ++ ++ // This class is responsible for also integrity checking the item id to tile id map here, we just use the item registry to figure it out ++ ++ static { ++ for (final Item item : Registry.ITEM) { ++ if (!(item instanceof BlockItem)) { ++ continue; ++ } ++ ++ if (!(((BlockItem)item).getBlock() instanceof EntityBlock)) { ++ continue; ++ } ++ ++ final String itemName = Registry.ITEM.getKey(item).toString(); ++ if (!ITEM_ID_TO_TILE_ENTITY_ID.containsKey(itemName)) { ++ LOGGER.error("Item id " + itemName + " does not contain tile mapping! (V704)"); ++ } ++ } ++ } ++ ++ protected static final Map TILE_ID_UPDATE = new HashMap<>(); ++ static { ++ TILE_ID_UPDATE.put("Airportal", "minecraft:end_portal"); ++ TILE_ID_UPDATE.put("Banner", "minecraft:banner"); ++ TILE_ID_UPDATE.put("Beacon", "minecraft:beacon"); ++ TILE_ID_UPDATE.put("Cauldron", "minecraft:brewing_stand"); ++ TILE_ID_UPDATE.put("Chest", "minecraft:chest"); ++ TILE_ID_UPDATE.put("Comparator", "minecraft:comparator"); ++ TILE_ID_UPDATE.put("Control", "minecraft:command_block"); ++ TILE_ID_UPDATE.put("DLDetector", "minecraft:daylight_detector"); ++ TILE_ID_UPDATE.put("Dropper", "minecraft:dropper"); ++ TILE_ID_UPDATE.put("EnchantTable", "minecraft:enchanting_table"); ++ TILE_ID_UPDATE.put("EndGateway", "minecraft:end_gateway"); ++ TILE_ID_UPDATE.put("EnderChest", "minecraft:ender_chest"); ++ TILE_ID_UPDATE.put("FlowerPot", "minecraft:flower_pot"); ++ TILE_ID_UPDATE.put("Furnace", "minecraft:furnace"); ++ TILE_ID_UPDATE.put("Hopper", "minecraft:hopper"); ++ TILE_ID_UPDATE.put("MobSpawner", "minecraft:mob_spawner"); ++ TILE_ID_UPDATE.put("Music", "minecraft:noteblock"); ++ TILE_ID_UPDATE.put("Piston", "minecraft:piston"); ++ TILE_ID_UPDATE.put("RecordPlayer", "minecraft:jukebox"); ++ TILE_ID_UPDATE.put("Sign", "minecraft:sign"); ++ TILE_ID_UPDATE.put("Skull", "minecraft:skull"); ++ TILE_ID_UPDATE.put("Structure", "minecraft:structure_block"); ++ TILE_ID_UPDATE.put("Trap", "minecraft:dispenser"); ++ } ++ ++ protected static void registerInventory(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); ++ } ++ ++ public static void register() { ++ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String id = data.getString("id"); ++ if (id == null) { ++ return null; ++ } ++ ++ data.setString("id", TILE_ID_UPDATE.getOrDefault(id, id)); ++ return null; ++ } ++ }); ++ ++ ++ ++ registerInventory( "minecraft:furnace"); ++ registerInventory( "minecraft:chest"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jukebox", new DataWalkerItems("RecordItem")); ++ registerInventory("minecraft:dispenser"); ++ registerInventory("minecraft:dropper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:mob_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ registerInventory("minecraft:brewing_stand"); ++ registerInventory("minecraft:hopper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:flower_pot", new DataWalkerItemNames("Item")); ++ ++ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); ++ ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ // only things here are in tag, if changed update if above ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag != null) { ++ final String itemId = data.getString("id"); ++ final String entityId; ++ if ("minecraft:armor_stand".equals(itemId)) { ++ // The check for version id is changed here. For whatever reason, the legacy ++ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, ++ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. ++ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant ++ // with the V705 schema. ++ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ArmorStand" : "minecraft:armor_stand"; ++ } else if (itemId != null && itemId.contains("_spawn_egg")) { ++ // V1451 changes spawn eggs to have the sub entity id be a part of the item id, but of course Mojang never ++ // bothered to write in logic to set the sub entity id, so we have to. ++ // format is ALWAYS :_spawn_egg post flattening ++ entityId = itemId.substring(0, itemId.indexOf("_spawn_egg")); ++ } else if ("minecraft:item_frame".equals(itemId)) { ++ // add missing item_frame entity id ++ // version check is same for armorstand, as both were namespaced at the same time ++ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ItemFrame" : "minecraft:item_frame"; ++ } else if ("minecraft:glow_item_frame".equals(itemId)) { ++ // add missing glow_item_frame entity id ++ entityId = "minecraft:glow_item_frame"; ++ } else { ++ entityId = entityTag.getString("id"); ++ } ++ ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve Entity for ItemStack (V704): " + itemId); ++ } ++ removeId = false; ++ } else { ++ removeId = !entityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ entityTag.setString("id", entityId); ++ } ++ } ++ ++ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); ++ ++ if (replace != null) { ++ entityTag = replace; ++ tag.setMap("EntityTag", entityTag); ++ } ++ if (removeId) { ++ entityTag.remove("id"); ++ } ++ } ++ ++ MapType blockEntityTag = tag.getMap("BlockEntityTag"); ++ if (blockEntityTag != null) { ++ final String itemId = data.getString("id"); ++ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V704): " + itemId); ++ } ++ removeId = false; ++ } else { ++ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ blockEntityTag.setString("id", entityId); ++ } ++ } ++ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); ++ if (replace != null) { ++ blockEntityTag = replace; ++ tag.setMap("BlockEntityTag", entityTag); ++ } ++ if (removeId) { ++ blockEntityTag.remove("id"); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ // Enforce namespace for ids ++ MCTypeRegistry.TILE_ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ } ++ ++ private V704() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e1d7013e49904dacc5e33d9c0b3f3ddb10e3d07a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java +@@ -0,0 +1,231 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V705 { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V1_10_2 + 193; ++ ++ protected static final Map ENTITY_ID_UPDATE = new HashMap<>(); ++ static { ++ ENTITY_ID_UPDATE.put("AreaEffectCloud", "minecraft:area_effect_cloud"); ++ ENTITY_ID_UPDATE.put("ArmorStand", "minecraft:armor_stand"); ++ ENTITY_ID_UPDATE.put("Arrow", "minecraft:arrow"); ++ ENTITY_ID_UPDATE.put("Bat", "minecraft:bat"); ++ ENTITY_ID_UPDATE.put("Blaze", "minecraft:blaze"); ++ ENTITY_ID_UPDATE.put("Boat", "minecraft:boat"); ++ ENTITY_ID_UPDATE.put("CaveSpider", "minecraft:cave_spider"); ++ ENTITY_ID_UPDATE.put("Chicken", "minecraft:chicken"); ++ ENTITY_ID_UPDATE.put("Cow", "minecraft:cow"); ++ ENTITY_ID_UPDATE.put("Creeper", "minecraft:creeper"); ++ ENTITY_ID_UPDATE.put("Donkey", "minecraft:donkey"); ++ ENTITY_ID_UPDATE.put("DragonFireball", "minecraft:dragon_fireball"); ++ ENTITY_ID_UPDATE.put("ElderGuardian", "minecraft:elder_guardian"); ++ ENTITY_ID_UPDATE.put("EnderCrystal", "minecraft:ender_crystal"); ++ ENTITY_ID_UPDATE.put("EnderDragon", "minecraft:ender_dragon"); ++ ENTITY_ID_UPDATE.put("Enderman", "minecraft:enderman"); ++ ENTITY_ID_UPDATE.put("Endermite", "minecraft:endermite"); ++ ENTITY_ID_UPDATE.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); ++ ENTITY_ID_UPDATE.put("FallingSand", "minecraft:falling_block"); ++ ENTITY_ID_UPDATE.put("Fireball", "minecraft:fireball"); ++ ENTITY_ID_UPDATE.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); ++ ENTITY_ID_UPDATE.put("Ghast", "minecraft:ghast"); ++ ENTITY_ID_UPDATE.put("Giant", "minecraft:giant"); ++ ENTITY_ID_UPDATE.put("Guardian", "minecraft:guardian"); ++ ENTITY_ID_UPDATE.put("Horse", "minecraft:horse"); ++ ENTITY_ID_UPDATE.put("Husk", "minecraft:husk"); ++ ENTITY_ID_UPDATE.put("Item", "minecraft:item"); ++ ENTITY_ID_UPDATE.put("ItemFrame", "minecraft:item_frame"); ++ ENTITY_ID_UPDATE.put("LavaSlime", "minecraft:magma_cube"); ++ ENTITY_ID_UPDATE.put("LeashKnot", "minecraft:leash_knot"); ++ ENTITY_ID_UPDATE.put("MinecartChest", "minecraft:chest_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartFurnace", "minecraft:furnace_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartHopper", "minecraft:hopper_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartRideable", "minecraft:minecart"); ++ ENTITY_ID_UPDATE.put("MinecartSpawner", "minecraft:spawner_minecart"); ++ ENTITY_ID_UPDATE.put("MinecartTNT", "minecraft:tnt_minecart"); ++ ENTITY_ID_UPDATE.put("Mule", "minecraft:mule"); ++ ENTITY_ID_UPDATE.put("MushroomCow", "minecraft:mooshroom"); ++ ENTITY_ID_UPDATE.put("Ozelot", "minecraft:ocelot"); ++ ENTITY_ID_UPDATE.put("Painting", "minecraft:painting"); ++ ENTITY_ID_UPDATE.put("Pig", "minecraft:pig"); ++ ENTITY_ID_UPDATE.put("PigZombie", "minecraft:zombie_pigman"); ++ ENTITY_ID_UPDATE.put("PolarBear", "minecraft:polar_bear"); ++ ENTITY_ID_UPDATE.put("PrimedTnt", "minecraft:tnt"); ++ ENTITY_ID_UPDATE.put("Rabbit", "minecraft:rabbit"); ++ ENTITY_ID_UPDATE.put("Sheep", "minecraft:sheep"); ++ ENTITY_ID_UPDATE.put("Shulker", "minecraft:shulker"); ++ ENTITY_ID_UPDATE.put("ShulkerBullet", "minecraft:shulker_bullet"); ++ ENTITY_ID_UPDATE.put("Silverfish", "minecraft:silverfish"); ++ ENTITY_ID_UPDATE.put("Skeleton", "minecraft:skeleton"); ++ ENTITY_ID_UPDATE.put("SkeletonHorse", "minecraft:skeleton_horse"); ++ ENTITY_ID_UPDATE.put("Slime", "minecraft:slime"); ++ ENTITY_ID_UPDATE.put("SmallFireball", "minecraft:small_fireball"); ++ ENTITY_ID_UPDATE.put("SnowMan", "minecraft:snowman"); ++ ENTITY_ID_UPDATE.put("Snowball", "minecraft:snowball"); ++ ENTITY_ID_UPDATE.put("SpectralArrow", "minecraft:spectral_arrow"); ++ ENTITY_ID_UPDATE.put("Spider", "minecraft:spider"); ++ ENTITY_ID_UPDATE.put("Squid", "minecraft:squid"); ++ ENTITY_ID_UPDATE.put("Stray", "minecraft:stray"); ++ ENTITY_ID_UPDATE.put("ThrownEgg", "minecraft:egg"); ++ ENTITY_ID_UPDATE.put("ThrownEnderpearl", "minecraft:ender_pearl"); ++ ENTITY_ID_UPDATE.put("ThrownExpBottle", "minecraft:xp_bottle"); ++ ENTITY_ID_UPDATE.put("ThrownPotion", "minecraft:potion"); ++ ENTITY_ID_UPDATE.put("Villager", "minecraft:villager"); ++ ENTITY_ID_UPDATE.put("VillagerGolem", "minecraft:villager_golem"); ++ ENTITY_ID_UPDATE.put("Witch", "minecraft:witch"); ++ ENTITY_ID_UPDATE.put("WitherBoss", "minecraft:wither"); ++ ENTITY_ID_UPDATE.put("WitherSkeleton", "minecraft:wither_skeleton"); ++ ENTITY_ID_UPDATE.put("WitherSkull", "minecraft:wither_skull"); ++ ENTITY_ID_UPDATE.put("Wolf", "minecraft:wolf"); ++ ENTITY_ID_UPDATE.put("XPOrb", "minecraft:xp_orb"); ++ ENTITY_ID_UPDATE.put("Zombie", "minecraft:zombie"); ++ ENTITY_ID_UPDATE.put("ZombieHorse", "minecraft:zombie_horse"); ++ ENTITY_ID_UPDATE.put("ZombieVillager", "minecraft:zombie_villager"); ++ } ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); ++ } ++ ++ private static void registerThrowableProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ public static void register() { ++ ConverterAbstractEntityRename.register(VERSION, ENTITY_ID_UPDATE::get); ++ ++ registerMob("minecraft:armor_stand"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:arrow", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:bat"); ++ registerMob("minecraft:blaze"); ++ registerMob("minecraft:cave_spider"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); ++ registerMob("minecraft:chicken"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:commandblock_minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:cow"); ++ registerMob("minecraft:creeper"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItems("SaddleItem")); ++ registerThrowableProjectile("minecraft:egg"); ++ registerMob("minecraft:elder_guardian"); ++ registerMob("minecraft:ender_dragon"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerBlockNames("carried")); ++ registerMob("minecraft:endermite"); ++ registerThrowableProjectile("minecraft:ender_pearl"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerBlockNames("Block")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); ++ registerThrowableProjectile("minecraft:fireball"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:fireworks_rocket", new DataWalkerItems("FireworksItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:furnace_minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:ghast"); ++ registerMob("minecraft:giant"); ++ registerMob("minecraft:guardian"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ registerMob("minecraft:husk"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item", new DataWalkerItems("Item")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_frame", new DataWalkerItems("Item")); ++ registerMob("minecraft:magma_cube"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:minecart", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("minecraft:mooshroom"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItems("SaddleItem")); ++ registerMob("minecraft:ocelot"); ++ registerMob("minecraft:pig"); ++ registerMob("minecraft:polar_bear"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Potion")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:rabbit"); ++ registerMob("minecraft:sheep"); ++ registerMob("minecraft:shulker"); ++ registerMob("minecraft:silverfish"); ++ registerMob("minecraft:skeleton"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItems("SaddleItem")); ++ registerMob("minecraft:slime"); ++ registerThrowableProjectile("minecraft:small_fireball"); ++ registerThrowableProjectile("minecraft:snowball"); ++ registerMob("minecraft:snowman"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spectral_arrow", new DataWalkerBlockNames("inTile")); ++ registerMob("minecraft:spider"); ++ registerMob("minecraft:squid"); ++ registerMob("minecraft:stray"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt_minecart", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); ++ ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0, len = recipes.size(); i < len; ++i) { ++ final MapType recipe = recipes.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); ++ ++ return null; ++ }); ++ registerMob("minecraft:villager_golem"); ++ registerMob("minecraft:witch"); ++ registerMob("minecraft:wither"); ++ registerMob("minecraft:wither_skeleton"); ++ registerThrowableProjectile("minecraft:wither_skull"); ++ registerMob("minecraft:wolf"); ++ registerThrowableProjectile("minecraft:xp_bottle"); ++ registerMob("minecraft:zombie"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItems("SaddleItem")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); ++ registerMob("minecraft:zombie_pigman"); ++ registerMob("minecraft:zombie_villager"); ++ registerMob("minecraft:evocation_illager"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItems("SaddleItem", "DecorItem")); ++ registerMob("minecraft:vex"); ++ registerMob("minecraft:vindication_illager"); ++ // Don't need to re-register itemstack walker, the V704 will correctly choose the right id for armorstand based on ++ // the source version ++ ++ // Enforce namespace for ids ++ MCTypeRegistry.ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ MCTypeRegistry.ENTITY_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ } ++ ++ private V705() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0070a3d02a87b0f08cd5e74d4f106f3e97f6b4f8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java +@@ -0,0 +1,60 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V804 { ++ ++ protected static final int VERSION = MCVersions.V16W35A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType blockEntity = tag.getMap("BlockEntityTag"); ++ if (blockEntity == null) { ++ return null; ++ } ++ ++ if (!blockEntity.hasKey("Base", ObjectType.NUMBER)) { ++ return null; ++ } ++ ++ data.setShort("Damage", (short)(blockEntity.getShort("Base") & 15)); ++ ++ final MapType display = tag.getMap("display"); ++ if (display != null) { ++ final ListType lore = display.getList("Lore", ObjectType.STRING); ++ if (lore != null) { ++ if (lore.size() == 1 && "(+NBT)".equals(lore.getString(0))) { ++ return null; ++ } ++ } ++ } ++ ++ blockEntity.remove("Base"); ++ if (blockEntity.isEmpty()) { ++ tag.remove("BlockEntityTag"); ++ } ++ ++ if (tag.isEmpty()) { ++ data.remove("tag"); ++ } ++ ++ return null; ++ } ++ }); ++ } ++ ++ private V804() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a2717b9d936872ec07141b0f3ae2a6eec81f2dbf +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java +@@ -0,0 +1,40 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.Types; ++ ++public final class V806 { ++ ++ protected static final int VERSION = MCVersions.V16W36A + 1; ++ ++ public static void register() { ++ final DataConverter, MapType> potionWaterUpdater = new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ tag = Types.NBT.createEmptyMap(); ++ data.setMap("tag", tag); ++ } ++ ++ if (!tag.hasKey("Potion", ObjectType.STRING)) { ++ tag.setString("Potion", "minecraft:water"); ++ } ++ ++ return null; ++ } ++ }; ++ ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:splash_potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:lingering_potion", potionWaterUpdater); ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:tipped_arrow", potionWaterUpdater); ++ } ++ ++ private V806() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b058e5e9b34a9dd134ef93e7a397b5f1e4e11fbd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java +@@ -0,0 +1,30 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V808 { ++ ++ protected static final int VERSION = MCVersions.V16W38A + 1; ++ ++ public static void register() { ++ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION, 1) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ if (!data.hasKey("Color", ObjectType.NUMBER)) { ++ data.setByte("Color", (byte)10); ++ } ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:shulker_box", new DataWalkerItemLists("Items")); ++ } ++ ++ private V808() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b6de7c32acd0adf78812edbbd184117661599c80 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java +@@ -0,0 +1,64 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class V813 { ++ ++ protected static final int VERSION = MCVersions.V16W40A; ++ ++ public static final String[] SHULKER_ID_BY_COLOUR = new String[] { ++ "minecraft:white_shulker_box", ++ "minecraft:orange_shulker_box", ++ "minecraft:magenta_shulker_box", ++ "minecraft:light_blue_shulker_box", ++ "minecraft:yellow_shulker_box", ++ "minecraft:lime_shulker_box", ++ "minecraft:pink_shulker_box", ++ "minecraft:gray_shulker_box", ++ "minecraft:silver_shulker_box", ++ "minecraft:cyan_shulker_box", ++ "minecraft:purple_shulker_box", ++ "minecraft:blue_shulker_box", ++ "minecraft:brown_shulker_box", ++ "minecraft:green_shulker_box", ++ "minecraft:red_shulker_box", ++ "minecraft:black_shulker_box" ++ }; ++ ++ public static void register() { ++ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ final MapType blockEntity = tag.getMap("BlockEntityTag"); ++ if (blockEntity == null) { ++ return null; ++ } ++ ++ final int color = blockEntity.getInt("Color"); ++ blockEntity.remove("Color"); ++ ++ data.setString("id", SHULKER_ID_BY_COLOUR[color % SHULKER_ID_BY_COLOUR.length]); ++ ++ return null; ++ } ++ }); ++ ++ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ data.remove("Color"); ++ return null; ++ } ++ }); ++ } ++ ++ private V813() {} ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4b427c128bd75d2dc8b36f0c377454385c029467 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java +@@ -0,0 +1,28 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.converters.DataConverter; ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.Locale; ++ ++public final class V816 { ++ ++ protected static final int VERSION = MCVersions.V16W43A; ++ ++ public static void register() { ++ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { ++ @Override ++ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { ++ final String lang = data.getString("lang"); ++ if (lang != null) { ++ data.setString("lang", lang.toLowerCase(Locale.ROOT)); ++ } ++ return null; ++ } ++ }); ++ } ++ ++ private V816() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9e2ef3cea4fd382a75a4d787fe2e2ff509eb49fc +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java +@@ -0,0 +1,19 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; ++import com.google.common.collect.ImmutableMap; ++ ++public final class V820 { ++ ++ protected static final int VERSION = MCVersions.V1_11 + 1; ++ ++ public static void register() { ++ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( ++ "minecraft:totem", "minecraft:totem_of_undying" ++ )::get); ++ } ++ ++ private V820() {} ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d95f8af7bd9bcedbb30be1b59e3dc749551e8cbe +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java +@@ -0,0 +1,348 @@ ++package ca.spottedleaf.dataconverter.minecraft.versions; ++ ++import ca.spottedleaf.dataconverter.minecraft.MCVersions; ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; ++import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; ++import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; ++import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; ++import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; ++import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import com.mojang.logging.LogUtils; ++import org.slf4j.Logger; ++import java.util.HashMap; ++import java.util.Map; ++ ++public final class V99 { ++ ++ // Structure for all data before data upgrading was added to minecraft (pre 15w32a) ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ protected static final int VERSION = MCVersions.V15W32A - 1; ++ ++ protected static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>(); ++ ++ static { ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "Furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "Furnace"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "Chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "Chest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "EnderChest"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "RecordPlayer"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "Trap"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "Dropper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "MobSpawner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "Music"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "Cauldron"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "EnchantTable"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "Beacon"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "Skull"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "DLDetector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "Hopper"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "FlowerPot"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "CommandBlock"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "Sign"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "Piston"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "DLDetector"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "Comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "Comparator"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "Banner"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "Structure"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "Airportal"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "EndGateway"); ++ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "Banner"); ++ } ++ ++ private static void registerMob(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Equipment")); ++ } ++ ++ private static void registerProjectile(final String id) { ++ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); ++ } ++ ++ private static void registerInventory(final String id) { ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); ++ } ++ ++ public static void register() { ++ // entities ++ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "Riding", fromVersion, toVersion); ++ ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Item", new DataWalkerItems("Item")); ++ registerProjectile("ThrownEgg"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Arrow", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "TippedArrow", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "SpectralArrow", new DataWalkerBlockNames("inTile")); ++ registerProjectile("Snowball"); ++ registerProjectile("Fireball"); ++ registerProjectile("SmallFireball"); ++ registerProjectile("ThrownEnderpearl"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerBlockNames("inTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerItems("Potion")); ++ registerProjectile("ThrownExpBottle"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "ItemFrame", new DataWalkerItems("Item")); ++ registerProjectile("WitherSkull"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerBlockNames("Block")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerTileEntities("TileEntityData")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "FireworksRocketEntity", new DataWalkerItems("FireworksItem")); ++ // Note: Minecart is the generic entity. It can be subtyped via an int to become one of the specific minecarts ++ // (i.e rideable, chest, furnace, tnt, etc) ++ // Because of this, we add all walkers to the generic type, even though they might not be needed. ++ // Vanilla does not make the generic minecart convert spawners, but we do. ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerBlockNames("DisplayTile")); // for all minecart types ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerItemLists("Items")); // for chest types ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); // for spawner type ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartRideable", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartFurnace", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartTNT", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerBlockNames("DisplayTile")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerItemLists("Items")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartCommandBlock", new DataWalkerBlockNames("DisplayTile")); ++ registerMob("ArmorStand"); ++ registerMob("Creeper"); ++ registerMob("Skeleton"); ++ registerMob("Spider"); ++ registerMob("Giant"); ++ registerMob("Zombie"); ++ registerMob("Slime"); ++ registerMob("Ghast"); ++ registerMob("PigZombie"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerItemLists("Equipment")); ++ registerMob("CaveSpider"); ++ registerMob("Silverfish"); ++ registerMob("Blaze"); ++ registerMob("LavaSlime"); ++ registerMob("EnderDragon"); ++ registerMob("WitherBoss"); ++ registerMob("Bat"); ++ registerMob("Witch"); ++ registerMob("Endermite"); ++ registerMob("Guardian"); ++ registerMob("Pig"); ++ registerMob("Sheep"); ++ registerMob("Cow"); ++ registerMob("Chicken"); ++ registerMob("Squid"); ++ registerMob("Wolf"); ++ registerMob("MushroomCow"); ++ registerMob("SnowMan"); ++ registerMob("Ozelot"); ++ registerMob("VillagerGolem"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "Equipment")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); ++ registerMob("Rabbit"); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", new DataWalkerItemLists("Inventory", "Equipment")); ++ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType offers = data.getMap("Offers"); ++ if (offers != null) { ++ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); ++ if (recipes != null) { ++ for (int i = 0; i < recipes.size(); ++i) { ++ final MapType recipe = recipes.getMap(i); ++ ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); ++ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); ++ } ++ } ++ } ++ ++ return null; ++ }); ++ registerMob("Shulker"); ++ ++ // tile entities ++ ++ // Inventory -> new DataWalkerItemLists("Items") ++ registerInventory("Furnace"); ++ registerInventory("Chest"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "RecordPlayer", new DataWalkerItems("RecordItem")); ++ registerInventory("Trap"); ++ registerInventory("Dropper"); ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "MobSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { ++ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); ++ return null; ++ }); ++ registerInventory("Cauldron"); ++ registerInventory("Hopper"); ++ // Note: Vanilla does not properly handle this case, it will not convert int ids! ++ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "FlowerPot", new DataWalkerItemNames("Item")); ++ ++ // rest ++ ++ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); ++ ++ final MapType tag = data.getMap("tag"); ++ if (tag == null) { ++ return null; ++ } ++ ++ // only things here are in tag, if changed update if above ++ ++ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); ++ ++ MapType entityTag = tag.getMap("EntityTag"); ++ if (entityTag != null) { ++ String itemId = getStringId(data.getString("id")); ++ final String entityId; ++ if ("minecraft:armor_stand".equals(itemId)) { ++ // The check for version id is removed here. For whatever reason, the legacy ++ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, ++ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. ++ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant ++ // with the V705 schema. ++ entityId = "ArmorStand"; ++ } else if ("minecraft:item_frame".equals(itemId)) { ++ // add missing item_frame entity id ++ entityId = "ItemFrame"; ++ } else { ++ entityId = entityTag.getString("id"); ++ } ++ ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve Entity for ItemStack (V99): " + data.getGeneric("id")); ++ } ++ removeId = false; ++ } else { ++ removeId = !entityTag.hasKey("id", ObjectType.STRING); ++ if (removeId) { ++ entityTag.setString("id", entityId); ++ } ++ } ++ ++ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); ++ ++ if (replace != null) { ++ entityTag = replace; ++ tag.setMap("EntityTag", entityTag); ++ } ++ if (removeId) { ++ entityTag.remove("id"); ++ } ++ } ++ ++ MapType blockEntityTag = tag.getMap("BlockEntityTag"); ++ if (blockEntityTag != null) { ++ final String itemId = getStringId(data.getString("id")); ++ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); ++ final boolean removeId; ++ if (entityId == null) { ++ if (!"minecraft:air".equals(itemId)) { ++ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V99): " + data.getGeneric("id")); ++ } ++ removeId = false; ++ } else { ++ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); ++ blockEntityTag.setString("id", entityId); ++ } ++ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); ++ if (replace != null) { ++ blockEntityTag = replace; ++ tag.setMap("BlockEntityTag", blockEntityTag); ++ } ++ if (removeId) { ++ blockEntityTag.remove("id"); ++ } ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); ++ ++ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ final MapType level = data.getMap("Level"); ++ if (level == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); ++ ++ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); ++ if (tileTicks != null) { ++ for (int i = 0, len = tileTicks.size(); i < len; ++i) { ++ final MapType tileTick = tileTicks.getMap(i); ++ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); ++ } ++ } ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.ENTITY_CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { ++ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Entities", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ MCTypeRegistry.SAVED_DATA.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { ++ final MapType data = root.getMap("data"); ++ if (data == null) { ++ return null; ++ } ++ ++ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data, "Features", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.OBJECTIVE, data, "Objectives", fromVersion, toVersion); ++ WalkerUtils.convertList(MCTypeRegistry.TEAM, data, "Teams", fromVersion, toVersion); ++ ++ return null; ++ }); ++ ++ ++ // Enforce namespacing for ids ++ MCTypeRegistry.BLOCK_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ MCTypeRegistry.ITEM_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); ++ MCTypeRegistry.ITEM_STACK.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); ++ ++ // Entity is absent; the String form is not yet namespaced, unlike the above. ++ } ++ ++ protected static String getStringId(final Object id) { ++ if (id instanceof String) { ++ return (String)id; ++ } else if (id instanceof Number) { ++ return HelperItemNameV102.getNameFromId(((Number)id).intValue()); ++ } else { ++ return null; ++ } ++ } ++ ++ private V99() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java +@@ -0,0 +1,11 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.block_name; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++ ++public final class DataWalkerBlockNames extends DataWalkerTypePaths { ++ ++ public DataWalkerBlockNames(final String... paths) { ++ super(MCTypeRegistry.BLOCK_NAME, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e7655645f5d32026a609a8c7517827653c5c5e8b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java +@@ -0,0 +1,26 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.game_event; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class GameEventListenerWalker implements DataWalker { ++ ++ @Override ++ public MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ final MapType listener = data.getMap("listener"); ++ if (listener == null) { ++ return null; ++ } ++ ++ final MapType event = listener.getMap("event"); ++ if (event == null) { ++ return null; ++ } ++ ++ WalkerUtils.convert(MCTypeRegistry.GAME_EVENT_NAME, event, "game_event", fromVersion, toVersion); ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7c8f6a5034b48e1ec2c5925211f491115ca735aa +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java +@@ -0,0 +1,38 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerListPaths implements DataWalker { ++ ++ protected final DataType type; ++ protected final String[] paths; ++ ++ public DataWalkerListPaths(final DataType type, final String... paths) { ++ this.type = type; ++ this.paths = paths; ++ } ++ ++ @Override ++ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ final DataType type = this.type; ++ for (final String path : this.paths) { ++ final ListType list = data.getListUnchecked(path); ++ if (list == null) { ++ continue; ++ } ++ ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final Object current = list.getGeneric(i); ++ final Object converted = type.convert((T)current, fromVersion, toVersion); ++ if (converted != null) { ++ list.setGeneric(i, converted); ++ } ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e66b4e0f7cdb032b545ace7ba852ad7979f3c96a +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java +@@ -0,0 +1,34 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.converters.datatypes.DataType; ++import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerTypePaths implements DataWalker { ++ ++ protected final DataType type; ++ protected final String[] paths; ++ ++ public DataWalkerTypePaths(final DataType type, final String... paths) { ++ this.type = type; ++ this.paths = paths; ++ } ++ ++ @Override ++ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { ++ for (final String path : this.paths) { ++ final Object current = data.getGeneric(path); ++ if (current == null) { ++ continue; ++ } ++ ++ final Object converted = this.type.convert((T)current, fromVersion, toVersion); ++ ++ if (converted != null) { ++ data.setGeneric(path, converted); ++ } ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1e81a1e46a9c0ffceb564a7b1fc4d1b51009f3f7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java +@@ -0,0 +1,127 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.generic; ++ ++import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import java.util.ArrayList; ++ ++public final class WalkerUtils { ++ ++ public static void convert(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final MapType map = data.getMap(path); ++ if (map != null) { ++ final MapType replace = type.convert(map, fromVersion, toVersion); ++ if (replace != null) { ++ data.setMap(path, replace); ++ } ++ } ++ } ++ ++ public static void convertList(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final ListType list = data.getList(path, ObjectType.MAP); ++ if (list != null) { ++ for (int i = 0, len = list.size(); i < len; ++i) { ++ final MapType replace = type.convert(list.getMap(i), fromVersion, toVersion); ++ if (replace != null) { ++ list.setMap(i, replace); ++ } ++ } ++ } ++ } ++ ++ public static void convert(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final Object value = data.getGeneric(path); ++ if (value != null) { ++ final Object converted = type.convert(value, fromVersion, toVersion); ++ if (converted != null) { ++ data.setGeneric(path, converted); ++ } ++ } ++ } ++ ++ public static void convert(final MCValueType type, final ListType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ for (int i = 0, len = data.size(); i < len; ++i) { ++ final Object value = data.getGeneric(i); ++ final Object converted = type.convert(value, fromVersion, toVersion); ++ if (converted != null) { ++ data.setGeneric(i, converted); ++ } ++ } ++ } ++ ++ public static void convertList(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final ListType list = data.getListUnchecked(path); ++ if (list != null) { ++ convert(type, list, fromVersion, toVersion); ++ } ++ } ++ ++ public static void convertKeys(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ final MapType map = data.getMap(path); ++ if (map != null) { ++ convertKeys(type, map, fromVersion, toVersion); ++ } ++ } ++ ++ public static void convertKeys(final MCValueType type, final MapType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ RenameHelper.renameKeys(data, (final String input) -> { ++ return (String)type.convert(input, fromVersion, toVersion); ++ }); ++ } ++ ++ public static void convertValues(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ convertValues(type, data.getMap(path), fromVersion, toVersion); ++ } ++ ++ public static void convertValues(final MCDataType type, final MapType data, final long fromVersion, final long toVersion) { ++ if (data == null) { ++ return; ++ } ++ ++ for (final String key : data.keys()) { ++ final MapType value = data.getMap(key); ++ if (value != null) { ++ final MapType replace = type.convert(value, fromVersion, toVersion); ++ if (replace != null) { ++ // no CME, key is in map already ++ data.setMap(key, replace); ++ } ++ } ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java +@@ -0,0 +1,11 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.item_name; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++ ++public final class DataWalkerItemNames extends DataWalkerTypePaths { ++ ++ public DataWalkerItemNames(final String... paths) { ++ super(MCTypeRegistry.ITEM_NAME, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerItemLists extends DataWalkerListPaths, MapType> { ++ ++ public DataWalkerItemLists(final String... paths) { ++ super(MCTypeRegistry.ITEM_STACK, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java +new file mode 100644 +index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public class DataWalkerItems extends DataWalkerTypePaths, MapType> { ++ ++ public DataWalkerItems(final String... paths) { ++ super(MCTypeRegistry.ITEM_STACK, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java +@@ -0,0 +1,12 @@ ++package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity; ++ ++import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; ++import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; ++import ca.spottedleaf.dataconverter.types.MapType; ++ ++public final class DataWalkerTileEntities extends DataWalkerTypePaths, MapType> { ++ ++ public DataWalkerTileEntities(final String... paths) { ++ super(MCTypeRegistry.TILE_ENTITY, paths); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java +@@ -0,0 +1,272 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public interface ListType { ++ ++ public TypeUtil getTypeUtil(); ++ ++ @Override ++ public int hashCode(); ++ ++ @Override ++ public boolean equals(final Object other); ++ ++ // Provides a deep copy of this list ++ public ListType copy(); ++ ++ // returns NONE if no type has been assigned. if NONE, then this list is also empty. It is not true on the other hand that an empty list has no type. ++ public ObjectType getType(); ++ ++ public int size(); ++ ++ public void remove(final int index); ++ ++ public default Object getGeneric(final int index) { ++ switch (this.getType()) { ++ case NONE: ++ throw new IllegalStateException("List is empty and has no type"); ++ case BYTE: ++ return Byte.valueOf(this.getByte(index)); ++ case SHORT: ++ return Short.valueOf(this.getShort(index)); ++ case INT: ++ return Integer.valueOf(this.getInt(index)); ++ case LONG: ++ return Long.valueOf(this.getLong(index)); ++ case FLOAT: ++ return Float.valueOf(this.getFloat(index)); ++ case DOUBLE: ++ return Double.valueOf(this.getDouble(index)); ++ case NUMBER: ++ return this.getNumber(index); ++ case BYTE_ARRAY: ++ return this.getBytes(index); ++ case SHORT_ARRAY: ++ return this.getShorts(index); ++ case INT_ARRAY: ++ return this.getInts(index); ++ case LONG_ARRAY: ++ return this.getLongs(index); ++ case LIST: ++ return this.getList(index); ++ case MAP: ++ return this.getMap(index); ++ case STRING: ++ return this.getString(index); ++ default: ++ throw new UnsupportedOperationException(this.getType().name()); ++ } ++ } ++ ++ public default void setGeneric(final int index, final Object to) { ++ if (to instanceof Number) { ++ if (to instanceof Byte) { ++ this.setByte(index, ((Byte)to).byteValue()); ++ return; ++ } else if (to instanceof Short) { ++ this.setShort(index, ((Short)to).shortValue()); ++ return; ++ } else if (to instanceof Integer) { ++ this.setInt(index, ((Integer)to).intValue()); ++ return; ++ } else if (to instanceof Long) { ++ this.setLong(index, ((Long)to).longValue()); ++ return; ++ } else if (to instanceof Float) { ++ this.setFloat(index, ((Float)to).floatValue()); ++ return; ++ } else if (to instanceof Double) { ++ this.setDouble(index, ((Double)to).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (to instanceof MapType) { ++ this.setMap(index, (MapType)to); ++ return; ++ } else if (to instanceof ListType) { ++ this.setList(index, (ListType)to); ++ return; ++ } else if (to instanceof String) { ++ this.setString(index, (String)to); ++ return; ++ } else if (to.getClass().isArray()) { ++ if (to instanceof byte[]) { ++ this.setBytes(index, (byte[])to); ++ return; ++ } else if (to instanceof short[]) { ++ this.setShorts(index, (short[])to); ++ return; ++ } else if (to instanceof int[]) { ++ this.setInts(index, (int[])to); ++ return; ++ } else if (to instanceof long[]) { ++ this.setLongs(index, (long[])to); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); ++ } ++ ++ // types here are strict. if the type on get does not match the underlying type, will throw. ++ ++ public Number getNumber(final int index); ++ ++ // if the value at index is a Number but not a byte, then returns the number casted to byte. If the value at the index is not a number, then throws ++ public byte getByte(final int index); ++ ++ public void setByte(final int index, final byte to); ++ ++ // if the value at index is a Number but not a short, then returns the number casted to short. If the value at the index is not a number, then throws ++ public short getShort(final int index); ++ ++ public void setShort(final int index, final short to); ++ ++ // if the value at index is a Number but not a int, then returns the number casted to int. If the value at the index is not a number, then throws ++ public int getInt(final int index); ++ ++ public void setInt(final int index, final int to); ++ ++ // if the value at index is a Number but not a long, then returns the number casted to long. If the value at the index is not a number, then throws ++ public long getLong(final int index); ++ ++ public void setLong(final int index, final long to); ++ ++ // if the value at index is a Number but not a float, then returns the number casted to float. If the value at the index is not a number, then throws ++ public float getFloat(final int index); ++ ++ public void setFloat(final int index, final float to); ++ ++ // if the value at index is a Number but not a double, then returns the number casted to double. If the value at the index is not a number, then throws ++ public double getDouble(final int index); ++ ++ public void setDouble(final int index, final double to); ++ ++ public byte[] getBytes(final int index); ++ ++ public void setBytes(final int index, final byte[] to); ++ ++ public short[] getShorts(final int index); ++ ++ public void setShorts(final int index, final short[] to); ++ ++ public int[] getInts(final int index); ++ ++ public void setInts(final int index, final int[] to); ++ ++ public long[] getLongs(final int index); ++ ++ public void setLongs(final int index, final long[] to); ++ ++ public ListType getList(final int index); ++ ++ public void setList(final int index, final ListType list); ++ ++ public MapType getMap(final int index); ++ ++ public void setMap(final int index, final MapType to); ++ ++ public String getString(final int index); ++ ++ public void setString(final int index, final String to); ++ ++ public default void addGeneric(final Object to) { ++ if (to instanceof Number) { ++ if (to instanceof Byte) { ++ this.addByte(((Byte)to).byteValue()); ++ return; ++ } else if (to instanceof Short) { ++ this.addShort(((Short)to).shortValue()); ++ return; ++ } else if (to instanceof Integer) { ++ this.addInt(((Integer)to).intValue()); ++ return; ++ } else if (to instanceof Long) { ++ this.addLong(((Long)to).longValue()); ++ return; ++ } else if (to instanceof Float) { ++ this.addFloat(((Float)to).floatValue()); ++ return; ++ } else if (to instanceof Double) { ++ this.addDouble(((Double)to).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (to instanceof MapType) { ++ this.addMap((MapType)to); ++ return; ++ } else if (to instanceof ListType) { ++ this.addList((ListType)to); ++ return; ++ } else if (to instanceof String) { ++ this.addString((String)to); ++ return; ++ } else if (to.getClass().isArray()) { ++ if (to instanceof byte[]) { ++ this.addByteArray((byte[])to); ++ return; ++ } else if (to instanceof short[]) { ++ this.addShortArray((short[])to); ++ return; ++ } else if (to instanceof int[]) { ++ this.addIntArray((int[])to); ++ return; ++ } else if (to instanceof long[]) { ++ this.addLongArray((long[])to); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); ++ } ++ ++ public void addByte(final byte b); ++ ++ public void addByte(final int index, final byte b); ++ ++ public void addShort(final short s); ++ ++ public void addShort(final int index, final short s); ++ ++ public void addInt(final int i); ++ ++ public void addInt(final int index, final int i); ++ ++ public void addLong(final long l); ++ ++ public void addLong(final int index, final long l); ++ ++ public void addFloat(final float f); ++ ++ public void addFloat(final int index, final float f); ++ ++ public void addDouble(final double d); ++ ++ public void addDouble(final int index, final double d); ++ ++ public void addByteArray(final byte[] arr); ++ ++ public void addByteArray(final int index, final byte[] arr); ++ ++ public void addShortArray(final short[] arr); ++ ++ public void addShortArray(final int index, final short[] arr); ++ ++ public void addIntArray(final int[] arr); ++ ++ public void addIntArray(final int index, final int[] arr); ++ ++ public void addLongArray(final long[] arr); ++ ++ public void addLongArray(final int index, final long[] arr); ++ ++ public void addList(final ListType list); ++ ++ public void addList(final int index, final ListType list); ++ ++ public void addMap(final MapType map); ++ ++ public void addMap(final int index, final MapType map); ++ ++ public void addString(final String string); ++ ++ public void addString(final int index, final String string); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1bd7809b7ee198d1ceeb2756b44105e1b0de956e +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java +@@ -0,0 +1,219 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++import java.util.Set; ++ ++public interface MapType { ++ ++ public TypeUtil getTypeUtil(); ++ ++ @Override ++ public int hashCode(); ++ ++ @Override ++ public boolean equals(final Object other); ++ ++ public int size(); ++ ++ public boolean isEmpty(); ++ ++ public void clear(); ++ ++ public Set keys(); ++ ++ // Provides a deep copy of this map ++ public MapType copy(); ++ ++ public boolean hasKey(final K key); ++ ++ public boolean hasKey(final K key, final ObjectType type); ++ ++ public void remove(final K key); ++ ++ public Object getGeneric(final K key); ++ ++ // types here are not strict. if the key maps to a different type, default is always returned ++ // if default is not a parameter, then default is always null ++ ++ public Number getNumber(final K key); ++ ++ public Number getNumber(final K key, final Number dfl); ++ ++ public boolean getBoolean(final K key); ++ ++ public boolean getBoolean(final K key, final boolean dfl); ++ ++ public void setBoolean(final K key, final boolean val); ++ ++ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns 0 ++ public byte getByte(final K key); ++ ++ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns dfl ++ public byte getByte(final K key, final byte dfl); ++ ++ public void setByte(final K key, final byte val); ++ ++ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns 0 ++ public short getShort(final K key); ++ ++ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns dfl ++ public short getShort(final K key, final short dfl); ++ ++ public void setShort(final K key, final short val); ++ ++ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns 0 ++ public int getInt(final K key); ++ ++ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns dfl ++ public int getInt(final K key, final int dfl); ++ ++ public void setInt(final K key, final int val); ++ ++ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns 0 ++ public long getLong(final K key); ++ ++ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns dfl ++ public long getLong(final K key, final long dfl); ++ ++ public void setLong(final K key, final long val); ++ ++ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns 0 ++ public float getFloat(final K key); ++ ++ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns dfl ++ public float getFloat(final K key, final float dfl); ++ ++ public void setFloat(final K key, final float val); ++ ++ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns 0 ++ public double getDouble(final K key); ++ ++ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns dfl ++ public double getDouble(final K key, final double dfl); ++ ++ public void setDouble(final K key, final double val); ++ ++ public byte[] getBytes(final K key); ++ ++ public byte[] getBytes(final K key, final byte[] dfl); ++ ++ public void setBytes(final K key, final byte[] val); ++ ++ public short[] getShorts(final K key); ++ ++ public short[] getShorts(final K key, final short[] dfl); ++ ++ public void setShorts(final K key, final short[] val); ++ ++ public int[] getInts(final K key); ++ ++ public int[] getInts(final K key, final int[] dfl); ++ ++ public void setInts(final K key, final int[] val); ++ ++ public long[] getLongs(final K key); ++ ++ public long[] getLongs(final K key, final long[] dfl); ++ ++ public void setLongs(final K key, final long[] val); ++ ++ public ListType getListUnchecked(final K key); ++ ++ public ListType getListUnchecked(final K key, final ListType dfl); ++ ++ public default ListType getList(final K key, final ObjectType type) { ++ return this.getList(key, type, null); ++ } ++ ++ public default ListType getOrCreateList(final K key, final ObjectType type) { ++ ListType ret = this.getList(key, type); ++ if (ret == null) { ++ this.setList(key, ret = this.getTypeUtil().createEmptyList()); ++ } ++ ++ return ret; ++ } ++ ++ public default ListType getList(final K key, final ObjectType type, final ListType dfl) { ++ final ListType ret = this.getListUnchecked(key, null); ++ final ObjectType retType; ++ if (ret != null && ((retType = ret.getType()) == type || retType == ObjectType.UNDEFINED)) { ++ return ret; ++ } else { ++ return dfl; ++ } ++ } ++ ++ public void setList(final K key, final ListType val); ++ ++ public MapType getMap(final K key); ++ ++ public default MapType getOrCreateMap(final K key) { ++ MapType ret = this.getMap(key); ++ if (ret == null) { ++ this.setMap(key, ret = this.getTypeUtil().createEmptyMap()); ++ } ++ ++ return ret; ++ } ++ ++ public MapType getMap(final K key, final MapType dfl); ++ ++ public void setMap(final K key, final MapType val); ++ ++ public String getString(final K key); ++ ++ public String getString(final K key, final String dfl); ++ ++ public void setString(final K key, final String val); ++ ++ public default void setGeneric(final K key, final Object value) { ++ if (value instanceof Boolean) { ++ this.setBoolean(key, ((Boolean)value).booleanValue()); ++ } else if (value instanceof Number) { ++ if (value instanceof Byte) { ++ this.setByte(key, ((Byte)value).byteValue()); ++ return; ++ } else if (value instanceof Short) { ++ this.setShort(key, ((Short)value).shortValue()); ++ return; ++ } else if (value instanceof Integer) { ++ this.setInt(key, ((Integer)value).intValue()); ++ return; ++ } else if (value instanceof Long) { ++ this.setLong(key, ((Long)value).longValue()); ++ return; ++ } else if (value instanceof Float) { ++ this.setFloat(key, ((Float)value).floatValue()); ++ return; ++ } else if (value instanceof Double) { ++ this.setDouble(key, ((Double)value).doubleValue()); ++ return; ++ } // else fall through to throw ++ } else if (value instanceof MapType) { ++ this.setMap(key, (MapType)value); ++ return; ++ } else if (value instanceof ListType) { ++ this.setList(key, (ListType)value); ++ return; ++ } else if (value instanceof String) { ++ this.setString(key, (String)value); ++ return; ++ } else if (value.getClass().isArray()) { ++ if (value instanceof byte[]) { ++ this.setBytes(key, (byte[])value); ++ return; ++ } else if (value instanceof short[]) { ++ this.setShorts(key, (short[])value); ++ return; ++ } else if (value instanceof int[]) { ++ this.setInts(key, (int[])value); ++ return; ++ } else if (value instanceof long[]) { ++ this.setLongs(key, (long[])value); ++ return; ++ } // else fall through to throw ++ } ++ ++ throw new IllegalArgumentException("Object " + value + " is not a valid type!"); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java +@@ -0,0 +1,72 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public enum ObjectType { ++ NONE(null), ++ BYTE(Byte.class), ++ SHORT(Short.class), ++ INT(Integer.class), ++ LONG(Long.class), ++ FLOAT(Float.class), ++ DOUBLE(Double.class), ++ NUMBER(Number.class), ++ BYTE_ARRAY(byte[].class), ++ SHORT_ARRAY(short[].class), ++ INT_ARRAY(int[].class), ++ LONG_ARRAY(long[].class), ++ LIST(ListType.class), ++ MAP(MapType.class), ++ STRING(String.class), ++ UNDEFINED(null); ++ ++ private final Class clazz; ++ private final boolean isNumber; ++ ++ private ObjectType(final Class clazz) { ++ this.clazz = clazz; ++ this.isNumber = clazz != null && Number.class.isAssignableFrom(clazz); ++ } ++ ++ public boolean isNumber() { ++ return this.isNumber; ++ } ++ ++ public Class getObjectClass() { ++ return this.clazz; ++ } ++ ++ public static ObjectType getType(final Object object) { ++ if (object instanceof Number) { ++ if (object instanceof Byte) { ++ return BYTE; ++ } else if (object instanceof Short) { ++ return SHORT; ++ } else if (object instanceof Integer) { ++ return INT; ++ } else if (object instanceof Long) { ++ return LONG; ++ } else if (object instanceof Float) { ++ return FLOAT; ++ } else if (object instanceof Double) { ++ return DOUBLE; ++ } // else return null ++ } else if (object instanceof MapType) { ++ return MAP; ++ } else if (object instanceof ListType) { ++ return LIST; ++ } else if (object instanceof String) { ++ return STRING; ++ } else if (object.getClass().isArray()) { ++ if (object instanceof byte[]) { ++ return BYTE_ARRAY; ++ } else if (object instanceof short[]) { ++ return SHORT_ARRAY; ++ } else if (object instanceof int[]) { ++ return INT_ARRAY; ++ } else if (object instanceof long[]) { ++ return LONG_ARRAY; ++ } // else return null ++ } ++ ++ return null; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java +@@ -0,0 +1,9 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++public interface TypeUtil { ++ ++ public ListType createEmptyList(); ++ ++ public MapType createEmptyMap(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java +@@ -0,0 +1,15 @@ ++package ca.spottedleaf.dataconverter.types; ++ ++import ca.spottedleaf.dataconverter.types.json.JsonTypeCompressedUtil; ++import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTTypeUtil; ++ ++public interface Types { ++ ++ public static final TypeUtil NBT = new NBTTypeUtil(); ++ ++ public static final TypeUtil JSON = new JsonTypeUtil(); ++ ++ // why does this exist ++ public static final TypeUtil JSON_COMPRESSED = new JsonTypeCompressedUtil(); ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java +@@ -0,0 +1,415 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++ ++public final class JsonListType implements ListType { ++ ++ protected final JsonArray array; ++ protected final boolean compressed; ++ ++ public JsonListType(final boolean compressed) { ++ this.array = new JsonArray(); ++ this.compressed = compressed; ++ } ++ ++ public JsonListType(final JsonArray array, final boolean compressed) { ++ this.array = array; ++ this.compressed = compressed; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (obj == null || obj.getClass() != JsonListType.class) { ++ return false; ++ } ++ ++ return this.array.equals(((JsonListType)obj).array); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.array.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "JsonListType{" + ++ "array=" + this.array + ++ ", compressed=" + this.compressed + ++ '}'; ++ } ++ ++ public JsonArray getJson() { ++ return this.array; ++ } ++ ++ @Override ++ public ListType copy() { ++ return new JsonListType(JsonTypeUtil.copyJson(this.array), this.compressed); ++ } ++ ++ @Override ++ public ObjectType getType() { ++ return ObjectType.UNDEFINED; ++ } ++ ++ @Override ++ public int size() { ++ return this.array.size(); ++ } ++ ++ @Override ++ public void remove(final int index) { ++ this.array.remove(index); ++ } ++ ++ @Override ++ public Number getNumber(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (this.compressed && primitive.isString()) { ++ try { ++ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public byte getByte(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.byteValue(); ++ } ++ ++ @Override ++ public void setByte(final int index, final byte to) { ++ this.array.set(index, new JsonPrimitive(Byte.valueOf(to))); ++ } ++ ++ @Override ++ public short getShort(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.shortValue(); ++ } ++ ++ @Override ++ public void setShort(final int index, final short to) { ++ this.array.set(index, new JsonPrimitive(Short.valueOf(to))); ++ } ++ ++ @Override ++ public int getInt(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.intValue(); ++ } ++ ++ @Override ++ public void setInt(final int index, final int to) { ++ this.array.set(index, new JsonPrimitive(Integer.valueOf(to))); ++ } ++ ++ @Override ++ public long getLong(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.longValue(); ++ } ++ ++ @Override ++ public void setLong(final int index, final long to) { ++ this.array.set(index, new JsonPrimitive(Long.valueOf(to))); ++ } ++ ++ @Override ++ public float getFloat(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.floatValue(); ++ } ++ ++ @Override ++ public void setFloat(final int index, final float to) { ++ this.array.set(index, new JsonPrimitive(Float.valueOf(to))); ++ } ++ ++ @Override ++ public double getDouble(final int index) { ++ final Number number = this.getNumber(index); ++ ++ return number == null ? 0 : number.doubleValue(); ++ } ++ ++ @Override ++ public void setDouble(final int index, final double to) { ++ this.array.set(index, new JsonPrimitive(Double.valueOf(to))); ++ } ++ ++ @Override ++ public byte[] getBytes(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setBytes(final int index, final byte[] to) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public short[] getShorts(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setShorts(final int index, final short[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setInts(final int index, final int[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public long[] getLongs(final int index) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setLongs(final int index, final long[] to) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public ListType getList(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonArray) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } ++ return null; ++ } ++ ++ @Override ++ public void setList(final int index, final ListType list) { ++ this.array.set(index, ((JsonListType)list).array); ++ } ++ ++ @Override ++ public MapType getMap(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonObject) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } ++ return null; ++ } ++ ++ @Override ++ public void setMap(final int index, final MapType to) { ++ this.array.set(index, ((JsonMapType)to).map); ++ } ++ ++ @Override ++ public String getString(final int index) { ++ final JsonElement element = this.array.get(index); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString() || (this.compressed && primitive.isNumber())) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public void setString(final int index, final String to) { ++ this.array.set(index, new JsonPrimitive(to)); ++ } ++ ++ @Override ++ public void addByte(final byte b) { ++ this.array.add(Byte.valueOf(b)); ++ } ++ ++ @Override ++ public void addByte(final int index, final byte b) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShort(final short s) { ++ this.array.add(Short.valueOf(s)); ++ } ++ ++ @Override ++ public void addShort(final int index, final short s) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addInt(final int i) { ++ this.array.add(Integer.valueOf(i)); ++ } ++ ++ @Override ++ public void addInt(final int index, final int i) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLong(final long l) { ++ this.array.add(Long.valueOf(l)); ++ } ++ ++ @Override ++ public void addLong(final int index, final long l) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addFloat(final float f) { ++ this.array.add(Float.valueOf(f)); ++ } ++ ++ @Override ++ public void addFloat(final int index, final float f) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addDouble(final double d) { ++ this.array.add(Double.valueOf(d)); ++ } ++ ++ @Override ++ public void addDouble(final int index, final double d) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addByteArray(final byte[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addByteArray(final int index, final byte[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final short[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final int index, final short[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int index, final int[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLongArray(final long[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addLongArray(final int index, final long[] arr) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addList(final ListType list) { ++ this.array.add(((JsonListType)list).array); ++ } ++ ++ @Override ++ public void addList(final int index, final ListType list) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addMap(final MapType map) { ++ this.array.add(((JsonMapType)map).map); ++ } ++ ++ @Override ++ public void addMap(final int index, final MapType map) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addString(final String string) { ++ this.array.add(string); ++ } ++ ++ @Override ++ public void addString(final int index, final String string) { ++ // doesn't implement any methods for adding at index... yee haw... ++ throw new UnsupportedOperationException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c89bcc4b9974dd65bad9b096cccf8a4369d47f4f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java +@@ -0,0 +1,450 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import java.util.LinkedHashSet; ++import java.util.Map; ++import java.util.Set; ++ ++public final class JsonMapType implements MapType { ++ ++ protected final JsonObject map; ++ protected final boolean compressed; ++ ++ public JsonMapType(final boolean compressed) { ++ this.map = new JsonObject(); ++ this.compressed = compressed; ++ } ++ ++ public JsonMapType(final JsonObject map, final boolean compressed) { ++ this.map = map; ++ this.compressed = compressed; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ ++ if (obj == null || obj.getClass() != JsonMapType.class) { ++ return false; ++ } ++ ++ return this.map.equals(((JsonMapType)obj).map); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "JsonMapType{" + ++ "map=" + this.map + ++ ", compressed=" + this.compressed + ++ '}'; ++ } ++ ++ public JsonObject getJson() { ++ return this.map; ++ } ++ ++ @Override ++ public int size() { ++ return this.map.entrySet().size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.map.entrySet().isEmpty(); ++ } ++ ++ @Override ++ public void clear() { ++ this.map.entrySet().clear(); ++ } ++ ++ @Override ++ public Set keys() { ++ // ah shit. no keyset method ++ final Set keys = new LinkedHashSet<>(); ++ ++ for (final Map.Entry entry : this.map.entrySet()) { ++ keys.add(entry.getKey()); ++ } ++ ++ return keys; ++ } ++ ++ @Override ++ public MapType copy() { ++ return new JsonMapType(JsonTypeUtil.copyJson(this.map), this.compressed); ++ } ++ ++ @Override ++ public boolean hasKey(final String key) { ++ return this.map.has(key); ++ } ++ ++ @Override ++ public boolean hasKey(final String key, final ObjectType type) { ++ final JsonElement element = this.map.get(key); ++ if (element == null) { ++ return false; ++ } ++ ++ if (type == ObjectType.UNDEFINED) { ++ return true; ++ } ++ ++ if (element.isJsonArray()) { ++ return type == ObjectType.LIST; ++ } else if (element.isJsonObject()) { ++ return type == ObjectType.MAP; ++ } else if (element.isJsonNull()) { ++ return false; ++ } ++ ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString()) { ++ return type == ObjectType.STRING || (this.compressed && type == ObjectType.NUMBER); ++ } else if (primitive.isBoolean()) { ++ return type.isNumber(); ++ } else { ++ // is number ++ final Number number = primitive.getAsNumber(); ++ if (number instanceof Byte) { ++ return type == ObjectType.BYTE || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Short) { ++ return type == ObjectType.SHORT || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Integer) { ++ return type == ObjectType.INT || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Long) { ++ return type == ObjectType.LONG || (this.compressed && type == ObjectType.STRING); ++ } else if (number instanceof Float) { ++ return type == ObjectType.FLOAT || (this.compressed && type == ObjectType.STRING); ++ } else { ++ return type == ObjectType.DOUBLE || (this.compressed && type == ObjectType.STRING); ++ } ++ } ++ } ++ ++ @Override ++ public void remove(final String key) { ++ this.map.remove(key); ++ } ++ ++ @Override ++ public Object getGeneric(final String key) { ++ final JsonElement element = this.map.get(key); ++ if (element == null || element.isJsonNull()) { ++ return null; ++ } else if (element.isJsonObject()) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } else if (element.isJsonArray()) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } else { ++ // primitive ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isString()) { ++ return primitive.getAsString(); ++ } else if (primitive.isBoolean()) { ++ return Boolean.valueOf(primitive.getAsBoolean()); ++ } else { ++ throw new IllegalStateException("Unknown json object " + element); ++ } ++ } ++ } ++ ++ @Override ++ public Number getNumber(final String key) { ++ return this.getNumber(key, null); ++ } ++ ++ @Override ++ public Number getNumber(final String key, final Number dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (this.compressed && primitive.isString()) { ++ try { ++ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); ++ } catch (final NumberFormatException ex) { ++ return null; ++ } ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key) { ++ return this.getBoolean(key, false); ++ } ++ ++ @Override ++ public boolean getBoolean(final String key, final boolean dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isNumber()) { ++ return primitive.getAsNumber().byteValue() != 0; ++ } else if (primitive.isBoolean()) { ++ return primitive.getAsBoolean(); ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setBoolean(final String key, final boolean val) { ++ this.map.addProperty(key, Boolean.valueOf(val)); ++ } ++ ++ @Override ++ public byte getByte(final String key) { ++ return this.getByte(key, (byte)0); ++ } ++ ++ @Override ++ public byte getByte(final String key, final byte dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.byteValue(); ++ } ++ ++ @Override ++ public void setByte(final String key, final byte val) { ++ this.map.addProperty(key, Byte.valueOf(val)); ++ } ++ ++ @Override ++ public short getShort(final String key) { ++ return this.getShort(key, (short)0); ++ } ++ ++ @Override ++ public short getShort(final String key, final short dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.shortValue(); ++ } ++ ++ @Override ++ public void setShort(final String key, final short val) { ++ this.map.addProperty(key, Short.valueOf(val)); ++ } ++ ++ @Override ++ public int getInt(final String key) { ++ return this.getInt(key, 0); ++ } ++ ++ @Override ++ public int getInt(final String key, final int dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.intValue(); ++ } ++ ++ @Override ++ public void setInt(final String key, final int val) { ++ this.map.addProperty(key, Integer.valueOf(val)); ++ } ++ ++ @Override ++ public long getLong(final String key) { ++ return this.getLong(key, 0L); ++ } ++ ++ @Override ++ public long getLong(final String key, final long dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.longValue(); ++ } ++ ++ @Override ++ public void setLong(final String key, final long val) { ++ this.map.addProperty(key, Long.valueOf(val)); ++ } ++ ++ @Override ++ public float getFloat(final String key) { ++ return this.getFloat(key, 0.0F); ++ } ++ ++ @Override ++ public float getFloat(final String key, final float dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.floatValue(); ++ } ++ ++ @Override ++ public void setFloat(final String key, final float val) { ++ this.map.addProperty(key, Float.valueOf(val)); ++ } ++ ++ @Override ++ public double getDouble(final String key) { ++ return this.getDouble(key, 0.0D); ++ } ++ ++ @Override ++ public double getDouble(final String key, final double dfl) { ++ final Number ret = this.getNumber(key, null); ++ return ret == null ? dfl : ret.doubleValue(); ++ } ++ ++ @Override ++ public void setDouble(final String key, final double val) { ++ this.map.addProperty(key, Double.valueOf(val)); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key) { ++ return this.getBytes(key, null); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key, final byte[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setBytes(final String key, final byte[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public short[] getShorts(final String key) { ++ return this.getShorts(key, null); ++ } ++ ++ @Override ++ public short[] getShorts(final String key, final short[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setShorts(final String key, final short[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final String key) { ++ return this.getInts(key, null); ++ } ++ ++ @Override ++ public int[] getInts(final String key, final int[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setInts(final String key, final int[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public long[] getLongs(final String key) { ++ return this.getLongs(key, null); ++ } ++ ++ @Override ++ public long[] getLongs(final String key, final long[] dfl) { ++ return dfl; ++ } ++ ++ @Override ++ public void setLongs(final String key, final long[] val) { ++ // JSON does not support raw primitive arrays ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key) { ++ return this.getListUnchecked(key, null); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key, final ListType dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonArray) { ++ return new JsonListType((JsonArray)element, this.compressed); ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setList(final String key, final ListType val) { ++ this.map.add(key, ((JsonListType)val).getJson()); ++ } ++ ++ @Override ++ public MapType getMap(final String key) { ++ return this.getMap(key, null); ++ } ++ ++ @Override ++ public MapType getMap(final String key, final MapType dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonObject) { ++ return new JsonMapType((JsonObject)element, this.compressed); ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setMap(final String key, final MapType val) { ++ this.map.add(key, ((JsonMapType)val).map); ++ } ++ ++ @Override ++ public String getString(final String key) { ++ return this.getString(key, null); ++ } ++ ++ @Override ++ public String getString(final String key, final String dfl) { ++ final JsonElement element = this.map.get(key); ++ if (element instanceof JsonPrimitive) { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isString()) { ++ return primitive.getAsString(); ++ } else if (this.compressed && primitive.isNumber()) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ return dfl; ++ } ++ ++ @Override ++ public void setString(final String key, final String val) { ++ this.map.addProperty(key, val); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++ ++public final class JsonTypeCompressedUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new JsonListType(true); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new JsonMapType(true); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java +@@ -0,0 +1,81 @@ ++package ca.spottedleaf.dataconverter.types.json; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.nbt.NBTListType; ++import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonNull; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonReader; ++import java.io.StringReader; ++import java.util.Map; ++ ++public final class JsonTypeUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new JsonListType(false); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new JsonMapType(false); ++ } ++ ++ public static T copyJson(final T from) { ++ // This is stupidly inefficient. However, deepCopy() is not exposed in this gson version. ++ final String out = from.toString(); ++ ++ return (T)Streams.parse(new JsonReader(new StringReader(out))); ++ } ++ ++ private static Object convertToGenericNBT(final JsonElement element, final boolean compressed) { ++ if (element instanceof JsonObject) { ++ return convertJsonToNBT(new JsonMapType((JsonObject)element, compressed)); ++ } else if (element instanceof JsonArray) { ++ return convertJsonToNBT(new JsonListType((JsonArray)element, compressed)); ++ } else if (element instanceof JsonNull) { ++ return null; ++ } else { ++ final JsonPrimitive primitive = (JsonPrimitive)element; ++ if (primitive.isBoolean()) { ++ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); ++ } else if (primitive.isNumber()) { ++ return primitive.getAsNumber(); ++ } else if (primitive.isString()) { ++ return primitive.getAsString(); ++ } ++ } ++ ++ throw new IllegalStateException("Unrecognized type " + element); ++ } ++ ++ public static NBTMapType convertJsonToNBT(final JsonMapType json) { ++ final NBTMapType ret = new NBTMapType(); ++ for (final Map.Entry entry : json.map.entrySet()) { ++ final Object obj = convertToGenericNBT(entry.getValue(), json.compressed); ++ if (obj == null) { ++ continue; ++ } ++ ++ ret.setGeneric(entry.getKey(), obj); ++ } ++ ++ return ret; ++ } ++ ++ public static NBTListType convertJsonToNBT(final JsonListType json) { ++ final NBTListType ret = new NBTListType(); ++ ++ for (int i = 0, len = json.size(); i < len; ++i) { ++ ret.addGeneric(convertToGenericNBT(json.array.get(i), json.compressed)); ++ } ++ ++ return ret; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.nbt.ByteArrayTag; ++import net.minecraft.nbt.ByteTag; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.DoubleTag; ++import net.minecraft.nbt.FloatTag; ++import net.minecraft.nbt.IntArrayTag; ++import net.minecraft.nbt.IntTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.LongArrayTag; ++import net.minecraft.nbt.LongTag; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.ShortTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++ ++public final class NBTListType implements ListType { ++ ++ private final ListTag list; ++ ++ public NBTListType() { ++ this.list = new ListTag(); ++ } ++ ++ public NBTListType(final ListTag tag) { ++ this.list = tag; ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return Types.NBT; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null || obj.getClass() != NBTListType.class) { ++ return false; ++ } ++ ++ return this.list.equals(((NBTListType)obj).list); ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.list.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "NBTListType{" + ++ "list=" + this.list + ++ '}'; ++ } ++ ++ public ListTag getTag() { ++ return this.list; ++ } ++ ++ @Override ++ public ListType copy() { ++ return new NBTListType(this.list.copy()); ++ } ++ ++ protected static ObjectType getType(final byte id) { ++ switch (id) { ++ case 0: // END ++ return ObjectType.NONE; ++ case 1: // BYTE ++ return ObjectType.BYTE; ++ case 2: // SHORT ++ return ObjectType.SHORT; ++ case 3: // INT ++ return ObjectType.INT; ++ case 4: // LONG ++ return ObjectType.LONG; ++ case 5: // FLOAT ++ return ObjectType.FLOAT; ++ case 6: // DOUBLE ++ return ObjectType.DOUBLE; ++ case 7: // BYTE_ARRAY ++ return ObjectType.BYTE_ARRAY; ++ case 8: // STRING ++ return ObjectType.STRING; ++ case 9: // LIST ++ return ObjectType.LIST; ++ case 10: // COMPOUND ++ return ObjectType.MAP; ++ case 11: // INT_ARRAY ++ return ObjectType.INT_ARRAY; ++ case 12: // LONG_ARRAY ++ return ObjectType.LONG_ARRAY; ++ default: ++ throw new IllegalStateException("Unknown type: " + id); ++ } ++ } ++ ++ @Override ++ public ObjectType getType() { ++ return getType(this.list.getElementType()); ++ } ++ ++ @Override ++ public int size() { ++ return this.list.size(); ++ } ++ ++ @Override ++ public void remove(final int index) { ++ this.list.remove(index); ++ } ++ ++ @Override ++ public Number getNumber(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsNumber(); ++ } ++ ++ @Override ++ public byte getByte(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsByte(); ++ } ++ ++ @Override ++ public void setByte(final int index, final byte to) { ++ this.list.set(index, ByteTag.valueOf(to)); ++ } ++ ++ @Override ++ public short getShort(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsShort(); ++ } ++ ++ @Override ++ public void setShort(final int index, final short to) { ++ this.list.set(index, ShortTag.valueOf(to)); ++ } ++ ++ @Override ++ public int getInt(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsInt(); ++ } ++ ++ @Override ++ public void setInt(final int index, final int to) { ++ this.list.set(index, IntTag.valueOf(to)); ++ } ++ ++ @Override ++ public long getLong(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsLong(); ++ } ++ ++ @Override ++ public void setLong(final int index, final long to) { ++ this.list.set(index, LongTag.valueOf(to)); ++ } ++ ++ @Override ++ public float getFloat(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ ++ @Override ++ public void setFloat(final int index, final float to) { ++ this.list.set(index, FloatTag.valueOf(to)); ++ } ++ ++ @Override ++ public double getDouble(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof NumericTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ ++ @Override ++ public void setDouble(final int index, final double to) { ++ this.list.set(index, DoubleTag.valueOf(to)); ++ } ++ ++ @Override ++ public byte[] getBytes(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof ByteArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ } ++ ++ @Override ++ public void setBytes(final int index, final byte[] to) { ++ this.list.set(index, new ByteArrayTag(to)); ++ } ++ ++ @Override ++ public short[] getShorts(final int index) { ++ // NBT does not support shorts ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setShorts(final int index, final short[] to) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof IntArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((IntArrayTag)tag).getAsIntArray(); ++ } ++ ++ @Override ++ public void setInts(final int index, final int[] to) { ++ this.list.set(index, new IntArrayTag(to)); ++ } ++ ++ @Override ++ public long[] getLongs(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof LongArrayTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ ++ @Override ++ public void setLongs(final int index, final long[] to) { ++ this.list.set(index, new LongArrayTag(to)); ++ } ++ ++ @Override ++ public ListType getList(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof ListTag)) { ++ throw new IllegalStateException(); ++ } ++ return new NBTListType((ListTag)tag); ++ } ++ ++ @Override ++ public void setList(final int index, final ListType list) { ++ this.list.set(index, ((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public MapType getMap(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof CompoundTag)) { ++ throw new IllegalStateException(); ++ } ++ return new NBTMapType((CompoundTag)tag); ++ } ++ ++ @Override ++ public void setMap(final int index, final MapType to) { ++ this.list.set(index, ((NBTMapType)to).getTag()); ++ } ++ ++ @Override ++ public String getString(final int index) { ++ final Tag tag = this.list.get(index); // does bound checking for us ++ if (!(tag instanceof StringTag)) { ++ throw new IllegalStateException(); ++ } ++ return ((StringTag)tag).getAsString(); ++ } ++ ++ @Override ++ public void setString(final int index, final String to) { ++ this.list.set(index, StringTag.valueOf(to)); ++ } ++ ++ @Override ++ public void addByte(final byte b) { ++ this.list.add(ByteTag.valueOf(b)); ++ } ++ ++ @Override ++ public void addByte(final int index, final byte b) { ++ this.list.add(index, ByteTag.valueOf(b)); ++ } ++ ++ @Override ++ public void addShort(final short s) { ++ this.list.add(ShortTag.valueOf(s)); ++ } ++ ++ @Override ++ public void addShort(final int index, final short s) { ++ this.list.add(index, ShortTag.valueOf(s)); ++ } ++ ++ @Override ++ public void addInt(final int i) { ++ this.list.add(IntTag.valueOf(i)); ++ } ++ ++ @Override ++ public void addInt(final int index, final int i) { ++ this.list.add(index, IntTag.valueOf(i)); ++ } ++ ++ @Override ++ public void addLong(final long l) { ++ this.list.add(LongTag.valueOf(l)); ++ } ++ ++ @Override ++ public void addLong(final int index, final long l) { ++ this.list.add(index, LongTag.valueOf(l)); ++ } ++ ++ @Override ++ public void addFloat(final float f) { ++ this.list.add(FloatTag.valueOf(f)); ++ } ++ ++ @Override ++ public void addFloat(final int index, final float f) { ++ this.list.add(index, FloatTag.valueOf(f)); ++ } ++ ++ @Override ++ public void addDouble(final double d) { ++ this.list.add(DoubleTag.valueOf(d)); ++ } ++ ++ @Override ++ public void addDouble(final int index, final double d) { ++ this.list.add(index, DoubleTag.valueOf(d)); ++ } ++ ++ @Override ++ public void addByteArray(final byte[] arr) { ++ this.list.add(new ByteArrayTag(arr)); ++ } ++ ++ @Override ++ public void addByteArray(final int index, final byte[] arr) { ++ this.list.add(index, new ByteArrayTag(arr)); ++ } ++ ++ @Override ++ public void addShortArray(final short[] arr) { ++ // NBT does not support short[] ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addShortArray(final int index, final short[] arr) { ++ // NBT does not support short[] ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void addIntArray(final int[] arr) { ++ this.list.add(new IntArrayTag(arr)); ++ } ++ ++ @Override ++ public void addIntArray(final int index, final int[] arr) { ++ this.list.add(index, new IntArrayTag(arr)); ++ } ++ ++ @Override ++ public void addLongArray(final long[] arr) { ++ this.list.add(new LongArrayTag(arr)); ++ } ++ ++ @Override ++ public void addLongArray(final int index, final long[] arr) { ++ this.list.add(index, new LongArrayTag(arr)); ++ } ++ ++ @Override ++ public void addList(final ListType list) { ++ this.list.add(((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public void addList(final int index, final ListType list) { ++ this.list.add(index, ((NBTListType)list).getTag()); ++ } ++ ++ @Override ++ public void addMap(final MapType map) { ++ this.list.add(((NBTMapType)map).getTag()); ++ } ++ ++ @Override ++ public void addMap(final int index, final MapType map) { ++ this.list.add(index, ((NBTMapType)map).getTag()); ++ } ++ ++ @Override ++ public void addString(final String string) { ++ this.list.add(StringTag.valueOf(string)); ++ } ++ ++ @Override ++ public void addString(final int index, final String string) { ++ this.list.add(index, StringTag.valueOf(string)); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..12880f93b53db1e60cbf13805e2eb08fee5fd203 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.ObjectType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++import ca.spottedleaf.dataconverter.types.Types; ++import net.minecraft.nbt.ByteArrayTag; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.IntArrayTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.nbt.LongArrayTag; ++import net.minecraft.nbt.NumericTag; ++import net.minecraft.nbt.StringTag; ++import net.minecraft.nbt.Tag; ++ ++import java.util.Set; ++ ++public final class NBTMapType implements MapType { ++ ++ private final CompoundTag map; ++ ++ public NBTMapType() { ++ this.map = new CompoundTag(); ++ } ++ ++ public NBTMapType(final CompoundTag tag) { ++ this.map = tag; ++ } ++ ++ @Override ++ public boolean equals(final Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (obj == null || obj.getClass() != NBTMapType.class) { ++ return false; ++ } ++ ++ return this.map.equals(((NBTMapType)obj).map); ++ } ++ ++ @Override ++ public TypeUtil getTypeUtil() { ++ return Types.NBT; ++ } ++ ++ @Override ++ public int hashCode() { ++ return this.map.hashCode(); ++ } ++ ++ @Override ++ public String toString() { ++ return "NBTMapType{" + ++ "map=" + this.map + ++ '}'; ++ } ++ ++ @Override ++ public int size() { ++ return this.map.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.map.isEmpty(); ++ } ++ ++ @Override ++ public void clear() { ++ this.map.getAllKeys().clear(); ++ } ++ ++ @Override ++ public Set keys() { ++ return this.map.getAllKeys(); ++ } ++ ++ public CompoundTag getTag() { ++ return this.map; ++ } ++ ++ @Override ++ public MapType copy() { ++ return new NBTMapType(this.map.copy()); ++ } ++ ++ @Override ++ public boolean hasKey(final String key) { ++ return this.map.get(key) != null; ++ } ++ ++ @Override ++ public boolean hasKey(final String key, final ObjectType type) { ++ final Tag tag = this.map.get(key); ++ if (tag == null) { ++ return false; ++ } ++ ++ final ObjectType valueType = NBTListType.getType(tag.getId()); ++ ++ return valueType == type || (type == ObjectType.NUMBER && valueType.isNumber()); ++ } ++ ++ @Override ++ public void remove(final String key) { ++ this.map.remove(key); ++ } ++ ++ @Override ++ public Object getGeneric(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag == null) { ++ return null; ++ } ++ ++ switch (NBTListType.getType(tag.getId())) { ++ case BYTE: ++ case SHORT: ++ case INT: ++ case LONG: ++ case FLOAT: ++ case DOUBLE: ++ return ((NumericTag)tag).getAsNumber(); ++ case MAP: ++ return new NBTMapType((CompoundTag)tag); ++ case LIST: ++ return new NBTListType((ListTag)tag); ++ case STRING: ++ return ((StringTag)tag).getAsString(); ++ case BYTE_ARRAY: ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ // Note: No short array tag! ++ case INT_ARRAY: ++ return ((IntArrayTag)tag).getAsIntArray(); ++ case LONG_ARRAY: ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ ++ throw new IllegalStateException("Unrecognized type " + tag); ++ } ++ ++ @Override ++ public Number getNumber(final String key) { ++ return this.getNumber(key, null); ++ } ++ ++ @Override ++ public Number getNumber(final String key, final Number dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsNumber(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key) { ++ return this.getByte(key) != 0; ++ } ++ ++ @Override ++ public boolean getBoolean(final String key, final boolean dfl) { ++ return this.getByte(key, dfl ? (byte)1 : (byte)0) != 0; ++ } ++ ++ @Override ++ public void setBoolean(final String key, final boolean val) { ++ this.setByte(key, val ? (byte)1 : (byte)0); ++ } ++ ++ @Override ++ public byte getByte(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsByte(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public byte getByte(final String key, final byte dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsByte(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setByte(final String key, final byte val) { ++ this.map.putByte(key, val); ++ } ++ ++ @Override ++ public short getShort(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsShort(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public short getShort(final String key, final short dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsShort(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setShort(final String key, final short val) { ++ this.map.putShort(key, val); ++ } ++ ++ @Override ++ public int getInt(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsInt(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public int getInt(final String key, final int dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsInt(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setInt(final String key, final int val) { ++ this.map.putInt(key, val); ++ } ++ ++ @Override ++ public long getLong(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsLong(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public long getLong(final String key, final long dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsLong(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setLong(final String key, final long val) { ++ this.map.putLong(key, val); ++ } ++ ++ @Override ++ public float getFloat(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public float getFloat(final String key, final float dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsFloat(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setFloat(final String key, final float val) { ++ this.map.putFloat(key, val); ++ } ++ ++ @Override ++ public double getDouble(final String key) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ return 0; ++ } ++ ++ @Override ++ public double getDouble(final String key, final double dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof NumericTag) { ++ return ((NumericTag)tag).getAsDouble(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setDouble(final String key, final double val) { ++ this.map.putDouble(key, val); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key) { ++ return this.getBytes(key, null); ++ } ++ ++ @Override ++ public byte[] getBytes(final String key, final byte[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof ByteArrayTag) { ++ return ((ByteArrayTag)tag).getAsByteArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setBytes(final String key, final byte[] val) { ++ this.map.putByteArray(key, val); ++ } ++ ++ @Override ++ public short[] getShorts(final String key) { ++ return this.getShorts(key, null); ++ } ++ ++ @Override ++ public short[] getShorts(final String key, final short[] dfl) { ++ // NBT does not support short array ++ return dfl; ++ } ++ ++ @Override ++ public void setShorts(final String key, final short[] val) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int[] getInts(final String key) { ++ return this.getInts(key, null); ++ } ++ ++ @Override ++ public int[] getInts(final String key, final int[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof IntArrayTag) { ++ return ((IntArrayTag)tag).getAsIntArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setInts(final String key, final int[] val) { ++ this.map.putIntArray(key, val); ++ } ++ ++ @Override ++ public long[] getLongs(final String key) { ++ return this.getLongs(key, null); ++ } ++ ++ @Override ++ public long[] getLongs(final String key, final long[] dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof LongArrayTag) { ++ return ((LongArrayTag)tag).getAsLongArray(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setLongs(final String key, final long[] val) { ++ this.map.putLongArray(key, val); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key) { ++ return this.getListUnchecked(key, null); ++ } ++ ++ @Override ++ public ListType getListUnchecked(final String key, final ListType dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof ListTag) { ++ return new NBTListType((ListTag)tag); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setList(final String key, final ListType val) { ++ this.map.put(key, ((NBTListType)val).getTag()); ++ } ++ ++ @Override ++ public MapType getMap(final String key) { ++ return this.getMap(key, null); ++ } ++ ++ @Override ++ public MapType getMap(final String key, final MapType dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof CompoundTag) { ++ return new NBTMapType((CompoundTag)tag); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setMap(final String key, final MapType val) { ++ this.map.put(key, ((NBTMapType)val).getTag()); ++ } ++ ++ @Override ++ public String getString(final String key) { ++ return this.getString(key, null); ++ } ++ ++ @Override ++ public String getString(final String key, final String dfl) { ++ final Tag tag = this.map.get(key); ++ if (tag instanceof StringTag) { ++ return ((StringTag)tag).getAsString(); ++ } ++ return dfl; ++ } ++ ++ @Override ++ public void setString(final String key, final String val) { ++ this.map.putString(key, val); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java +@@ -0,0 +1,18 @@ ++package ca.spottedleaf.dataconverter.types.nbt; ++ ++import ca.spottedleaf.dataconverter.types.ListType; ++import ca.spottedleaf.dataconverter.types.MapType; ++import ca.spottedleaf.dataconverter.types.TypeUtil; ++ ++public final class NBTTypeUtil implements TypeUtil { ++ ++ @Override ++ public ListType createEmptyList() { ++ return new NBTListType(); ++ } ++ ++ @Override ++ public MapType createEmptyMap() { ++ return new NBTMapType(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java +@@ -0,0 +1,77 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import it.unimi.dsi.fastutil.ints.Int2IntFunction; ++ ++import java.util.Arrays; ++ ++public class Int2IntArraySortedMap { ++ ++ protected int[] key; ++ protected int[] val; ++ protected int size; ++ ++ public Int2IntArraySortedMap() { ++ this.key = new int[8]; ++ this.val = new int[8]; ++ } ++ ++ public int put(final int key, final int value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final int current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return 0; ++ } ++ ++ public int computeIfAbsent(final int key, final Int2IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public int get(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return 0; ++ } ++ return this.val[index]; ++ } ++ ++ public int getFloor(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? 0 : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java +@@ -0,0 +1,74 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import java.util.Arrays; ++import java.util.function.IntFunction; ++ ++public class Int2ObjectArraySortedMap { ++ ++ protected int[] key; ++ protected V[] val; ++ protected int size; ++ ++ public Int2ObjectArraySortedMap() { ++ this.key = new int[8]; ++ this.val = (V[])new Object[8]; ++ } ++ ++ public V put(final int key, final V value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final V current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return null; ++ } ++ ++ public V computeIfAbsent(final int key, final IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public V get(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return null; ++ } ++ return this.val[index]; ++ } ++ ++ public V getFloor(final int key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1); ++ return this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b5ebf6bbe1a3711111cf045ee8b46c934c7e4563 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java +@@ -0,0 +1,223 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++public final class IntegerUtil { ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static int trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ public static long getDivisorNumbers(final int d) { ++ final int ad = Math.abs(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 - (int)((t & mask)%ad); ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p - 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/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import it.unimi.dsi.fastutil.longs.Long2IntFunction; ++import java.util.Arrays; ++ ++public class Long2IntArraySortedMap { ++ ++ protected long[] key; ++ protected int[] val; ++ protected int size; ++ ++ public Long2IntArraySortedMap() { ++ this.key = new long[8]; ++ this.val = new int[8]; ++ } ++ ++ public int put(final long key, final int value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final int current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return 0; ++ } ++ ++ public int computeIfAbsent(final long key, final Long2IntFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public int get(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return 0; ++ } ++ return this.val[index]; ++ } ++ ++ public int getFloor(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? 0 : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java +@@ -0,0 +1,76 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import java.util.Arrays; ++import java.util.function.LongFunction; ++ ++public class Long2ObjectArraySortedMap { ++ ++ protected long[] key; ++ protected V[] val; ++ protected int size; ++ ++ public Long2ObjectArraySortedMap() { ++ this.key = new long[8]; ++ this.val = (V[])new Object[8]; ++ } ++ ++ public V put(final long key, final V value) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ final V current = this.val[index]; ++ this.val[index] = value; ++ return current; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ this.val[insert] = value; ++ ++ return null; ++ } ++ ++ public V computeIfAbsent(final long key, final LongFunction producer) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index >= 0) { ++ return this.val[index]; ++ } ++ final int insert = -(index + 1); ++ // shift entries down ++ if (this.size >= this.val.length) { ++ this.key = Arrays.copyOf(this.key, this.key.length * 2); ++ this.val = Arrays.copyOf(this.val, this.val.length * 2); ++ } ++ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); ++ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); ++ ++this.size; ++ ++ this.key[insert] = key; ++ ++ return this.val[insert] = producer.apply(key); ++ } ++ ++ public V get(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ return null; ++ } ++ return this.val[index]; ++ } ++ ++ public V getFloor(final long key) { ++ final int index = Arrays.binarySearch(this.key, 0, this.size, key); ++ if (index < 0) { ++ final int insert = -(index + 1) - 1; ++ return insert < 0 ? null : this.val[insert]; ++ } ++ return this.val[index]; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..967ad1186cbc81a76a4958ea99d4eff37c15b48f +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java +@@ -0,0 +1,24 @@ ++package ca.spottedleaf.dataconverter.util; ++ ++import net.minecraft.resources.ResourceLocation; ++ ++public final class NamespaceUtil { ++ ++ private NamespaceUtil() {} ++ ++ public static String correctNamespace(final String value) { ++ if (value == null) { ++ return null; ++ } ++ final ResourceLocation resourceLocation = ResourceLocation.tryParse(value); ++ return resourceLocation != null ? resourceLocation.toString() : value; ++ } ++ ++ public static String correctNamespaceOrNull(final String value) { ++ if (value == null) { ++ return null; ++ } ++ final String correct = correctNamespace(value); ++ return correct.equals(value) ? null : correct; ++ } ++} +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +index fee9a8e74bfcc94942991b56799debf67b551f43..b230a3d475357d2ffd340f9a89934ea7227e69d0 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java +@@ -87,7 +87,7 @@ public class ChunkStorage implements AutoCloseable { + int i = ChunkStorage.getVersion(nbttagcompound); + + // CraftBukkit start +- if (i < 1466) { ++ if (false && i < 1466) { // Paper - no longer needed, data converter system handles it now + CompoundTag level = nbttagcompound.getCompound("Level"); + if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { + ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); +@@ -99,7 +99,7 @@ public class ChunkStorage implements AutoCloseable { + // CraftBukkit end + + if (i < 1493) { +- nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, i, 1493); ++ ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter + if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { + synchronized (this.persistentDataLock) { // Paper - Async chunk loading + LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); +@@ -119,7 +119,7 @@ public class ChunkStorage implements AutoCloseable { + // Spigot end + + ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); +- nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, Math.max(1493, i)); ++ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - replace chunk converter + if (i < SharedConstants.getCurrentVersion().getWorldVersion()) { + nbttagcompound.putInt("DataVersion", SharedConstants.getCurrentVersion().getWorldVersion()); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +index de7afc737b1ab099edc29a4ef94baa76329c2947..2bc0384728f89b7c64a8beec78a1b77dc063d37b 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +@@ -128,7 +128,7 @@ public class EntityStorage implements EntityPersistentStorage { + + private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { + int i = getVersion(chunkNbt); +- return NbtUtils.update(this.fixerUpper, DataFixTypes.ENTITY_CHUNK, chunkNbt, i); ++ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - route to new converter system + } + + public static int getVersion(CompoundTag chunkNbt) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 9b2cec7528936a5d53a926c91063cb6e9ed7da1b..47cda78efcce597d3d7ba8fc93a2865e10cdc237 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -148,7 +148,14 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl + int j = getVersion(dynamic); + int k = SharedConstants.getCurrentVersion().getWorldVersion(); + boolean bl = j != k; +- Dynamic dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k); ++ // Paper start - route to new converter system ++ Dynamic dynamic2; ++ if (this.type.getType() == net.minecraft.util.datafix.fixes.References.POI_CHUNK) { ++ dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k)); ++ } else { ++ dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k); ++ } ++ // Paper end - route to new converter system + OptionalDynamic optionalDynamic = dynamic2.get("Sections"); + + for(int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); ++l) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +index 963ad3ce1ef83888ae1537ff01accdbb5b04ffa1..a7cba5b828a586d7435bda4d512af68684244682 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java +@@ -115,7 +115,7 @@ public class StructureCheck { + + CompoundTag compoundTag2; + try { +- compoundTag2 = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, compoundTag, i); ++ compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - replace chunk converter + } catch (Exception var12) { + LOGGER.warn("Failed to partially datafix chunk {}", pos, var12); + return StructureCheckResult.CHUNK_LOAD_NEEDED; +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index d785efd61caa2237e05d9ce3dbf84d86076ff047..601f8099f74e81c17600566b3c9b7a6dd39c9bcb 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -93,7 +93,7 @@ public class PlayerDataStorage { + // CraftBukkit end + int i = nbttagcompound.contains("DataVersion", 3) ? nbttagcompound.getInt("DataVersion") : -1; + +- player.load(NbtUtils.update(this.fixerUpper, DataFixTypes.PLAYER, nbttagcompound, i)); ++ player.load(ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getWorldVersion())); // Paper - replace player converter + } + + return nbttagcompound; diff --git a/patches/server/0761-Rewrite-dataconverter-system.patch b/patches/server/0761-Rewrite-dataconverter-system.patch deleted file mode 100644 index e892c4fc27..0000000000 --- a/patches/server/0761-Rewrite-dataconverter-system.patch +++ /dev/null @@ -1,22747 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 19 Jun 2021 10:43:01 -0700 -Subject: [PATCH] Rewrite dataconverter system - -Please see https://github.com/PaperMC/DataConverter -for details. - -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1863c606be715683d53863a0c9293525d199c9cf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/DataConverter.java -@@ -0,0 +1,54 @@ -+package ca.spottedleaf.dataconverter.converters; -+ -+import java.util.Comparator; -+ -+public abstract class DataConverter { -+ -+ public static final Comparator> LOWEST_VERSION_COMPARATOR = (x, y) -> { -+ return Long.compare(x.getEncodedVersion(), y.getEncodedVersion()); -+ }; -+ -+ protected final int toVersion; -+ protected final int versionStep; -+ -+ public DataConverter(final int toVersion) { -+ this.toVersion = toVersion; -+ this.versionStep = 0; -+ } -+ -+ public DataConverter(final int toVersion, final int versionStep) { -+ this.toVersion = toVersion; -+ this.versionStep = versionStep; -+ } -+ -+ public final int getToVersion() { -+ return this.toVersion; -+ } -+ -+ public final int getVersionStep() { -+ return this.versionStep; -+ } -+ -+ public final long getEncodedVersion() { -+ return encodeVersions(this.toVersion, this.versionStep); -+ } -+ -+ public abstract R convert(final T data, final long sourceVersion, final long toVersion); -+ -+ // step must be in the lower bits, so that encodeVersions(version, step) < encodeVersions(version, step + 1) -+ public static long encodeVersions(final int version, final int step) { -+ return ((long)version << 32) | (step & 0xFFFFFFFFL); -+ } -+ -+ public static int getVersion(final long encoded) { -+ return (int)(encoded >>> 32); -+ } -+ -+ public static int getStep(final long encoded) { -+ return (int)encoded; -+ } -+ -+ public static String encodedToString(final long encoded) { -+ return getVersion(encoded) + "." + getStep(encoded); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b92c5c66ad3a5198873f98287a5ced71c231d09 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataHook.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+public interface DataHook { -+ -+ public R preHook(final T data, final long fromVersion, final long toVersion); -+ -+ public R postHook(final T data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b56a7f9ace3b947fed49101b6e9936721fb99ea5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataType.java -@@ -0,0 +1,7 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+public abstract class DataType { -+ -+ public abstract R convert(final T data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf9fae4451ead4860343b915fb70e3a7cdf0de31 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/converters/datatypes/DataWalker.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.converters.datatypes; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public interface DataWalker { -+ -+ public MapType walk(final MapType data, final long fromVersion, final long toVersion); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dde9d36bf6212196caa18f3c9c535aec330a33ed ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCDataConverter.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; -+import ca.spottedleaf.dataconverter.types.json.JsonMapType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.gson.JsonObject; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import net.minecraft.nbt.CompoundTag; -+ -+public final class MCDataConverter { -+ -+ private static final LongArrayList BREAKPOINTS = MCVersionRegistry.getBreakpoints(); -+ -+ public static T copy(final T type) { -+ if (type instanceof CompoundTag) { -+ return (T)((CompoundTag)type).copy(); -+ } else if (type instanceof JsonObject) { -+ return (T)((JsonObject)type).deepCopy(); -+ } -+ -+ return type; -+ } -+ -+ public static CompoundTag convertTag(final MCDataType type, final CompoundTag data, final int fromVersion, final int toVersion) { -+ final NBTMapType wrapped = new NBTMapType(data); -+ -+ final NBTMapType replaced = (NBTMapType)convert(type, wrapped, fromVersion, toVersion); -+ -+ return replaced == null ? wrapped.getTag() : replaced.getTag(); -+ } -+ -+ public static JsonObject convertJson(final MCDataType type, final JsonObject data, final boolean compressed, final int fromVersion, final int toVersion) { -+ final JsonMapType wrapped = new JsonMapType(data, compressed); -+ -+ final JsonMapType replaced = (JsonMapType)convert(type, wrapped, fromVersion, toVersion); -+ -+ return replaced == null ? wrapped.getJson() : replaced.getJson(); -+ } -+ -+ public static R convert(final DataType type, final T data, int fromVersion, final int toVersion) { -+ Object ret = data; -+ -+ long currentVersion = DataConverter.encodeVersions(fromVersion < 99 ? 99 : fromVersion, Integer.MAX_VALUE); -+ final long nextVersion = DataConverter.encodeVersions(toVersion, Integer.MAX_VALUE); -+ -+ for (int i = 0, len = BREAKPOINTS.size(); i < len; ++i) { -+ final long breakpoint = BREAKPOINTS.getLong(i); -+ -+ if (currentVersion >= breakpoint) { -+ continue; -+ } -+ -+ final Object converted = type.convert((T)ret, currentVersion, Math.min(nextVersion, breakpoint - 1)); -+ if (converted != null) { -+ ret = converted; -+ } -+ -+ currentVersion = Math.min(nextVersion, breakpoint - 1); -+ -+ if (currentVersion == nextVersion) { -+ break; -+ } -+ } -+ -+ if (currentVersion != nextVersion) { -+ final Object converted = type.convert((T)ret, currentVersion, nextVersion); -+ if (converted != null) { -+ ret = converted; -+ } -+ } -+ -+ return (R)ret; -+ } -+ -+ private MCDataConverter() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6817eef740b4dd0ebc00857085f7f01e6d2b7aa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersionRegistry.java -@@ -0,0 +1,363 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.ints.IntRBTreeSet; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import org.slf4j.Logger; -+import java.lang.reflect.Field; -+import java.util.Arrays; -+import java.util.Locale; -+ -+public final class MCVersionRegistry { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final Int2ObjectLinkedOpenHashMap VERSION_NAMES = new Int2ObjectLinkedOpenHashMap<>(); -+ protected static final IntArrayList VERSION_LIST; -+ protected static final LongArrayList DATA_VERSION_LIST; -+ -+ protected static final IntArrayList DATACONVERTER_VERSIONS_LIST; -+ protected static final IntLinkedOpenHashSet DATACONVERTER_VERSIONS_MAJOR = new IntLinkedOpenHashSet(); -+ protected static final LongLinkedOpenHashSet DATACONVERTER_VERSIONS = new LongLinkedOpenHashSet(); -+ protected static final Int2ObjectLinkedOpenHashMap SUBVERSIONS = new Int2ObjectLinkedOpenHashMap<>(); -+ protected static final LongArrayList BREAKPOINTS = new LongArrayList(); -+ static { -+ // Note: Some of these are nameless. -+ // Unless a data version is specified here, it will NOT have converters ran for it. Please add them on update! -+ final int[] converterVersions = new int[] { -+ 99, -+ 100, -+ 101, -+ 102, -+ 105, -+ 106, -+ 107, -+ 108, -+ 109, -+ 110, -+ 111, -+ 113, -+ 135, -+ 143, -+ 147, -+ 165, -+ 501, -+ 502, -+ 505, -+ 700, -+ 701, -+ 702, -+ 703, -+ 704, -+ 705, -+ 804, -+ 806, -+ 808, -+ 808, -+ 813, -+ 816, -+ 820, -+ 1022, -+ 1125, -+ 1344, -+ 1446, -+ 1450, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1451, -+ 1456, -+ 1458, -+ 1460, -+ 1466, -+ 1470, -+ 1474, -+ 1475, -+ 1480, -+ 1481, -+ 1483, -+ 1484, -+ 1486, -+ 1487, -+ 1488, -+ 1490, -+ 1492, -+ 1494, -+ 1496, -+ 1500, -+ 1501, -+ 1502, -+ 1506, -+ 1510, -+ 1514, -+ 1515, -+ 1624, -+ 1800, -+ 1801, -+ 1802, -+ 1803, -+ 1904, -+ 1905, -+ 1906, -+ 1909, -+ 1911, -+ 1914, -+ 1917, -+ 1918, -+ 1920, -+ 1925, -+ 1928, -+ 1929, -+ 1931, -+ 1936, -+ 1946, -+ 1948, -+ 1953, -+ 1955, -+ 1961, -+ 1963, -+ 2100, -+ 2202, -+ 2209, -+ 2211, -+ 2218, -+ 2501, -+ 2502, -+ 2503, -+ 2505, -+ 2508, -+ 2509, -+ 2511, -+ 2514, -+ 2516, -+ 2518, -+ 2519, -+ 2522, -+ 2523, -+ 2527, -+ 2528, -+ 2529, -+ 2531, -+ 2533, -+ 2535, -+ 2550, -+ 2551, -+ 2552, -+ 2553, -+ 2558, -+ 2568, -+ 2671, -+ 2679, -+ 2680, -+ 2684, -+ 2686, -+ 2688, -+ 2690, -+ 2691, -+ 2693, -+ 2696, -+ 2700, -+ 2701, -+ 2702, -+ 2704, -+ 2707, -+ 2710, -+ 2717, -+ 2825, -+ 2831, -+ 2832, -+ 2833, -+ 2838, -+ 2841, -+ 2842, -+ 2843, -+ 2846, -+ 2852, -+ 2967, -+ 2970, -+ 3077, -+ 3078, -+ 3081, -+ 3082, -+ 3083, -+ 3084, -+ 3086, -+ 3087, -+ 3088, -+ 3090, -+ 3093, -+ 3094, -+ 3097, -+ 3108 -+ // All up to 1.19.1-pre2 -+ }; -+ Arrays.sort(converterVersions); -+ -+ DATACONVERTER_VERSIONS_MAJOR.addAll(DATACONVERTER_VERSIONS_LIST = new IntArrayList(converterVersions)); -+ -+ // add sub versions -+ registerSubVersion(MCVersions.V16W38A + 1, 1); -+ -+ registerSubVersion(MCVersions.V17W47A, 1); -+ registerSubVersion(MCVersions.V17W47A, 2); -+ registerSubVersion(MCVersions.V17W47A, 3); -+ registerSubVersion(MCVersions.V17W47A, 4); -+ registerSubVersion(MCVersions.V17W47A, 5); -+ registerSubVersion(MCVersions.V17W47A, 6); -+ registerSubVersion(MCVersions.V17W47A, 7); -+ -+ // register breakpoints here -+ // for all major releases after 1.16, add them. this reduces the work required to determine if a breakpoint -+ // is needed for new converters -+ -+ // Too much changed in this version. -+ registerBreakpoint(MCVersions.V17W47A); -+ registerBreakpoint(MCVersions.V17W47A, Integer.MAX_VALUE); -+ -+ // final release of major version -+ registerBreakpoint(MCVersions.V1_17_1, Integer.MAX_VALUE); -+ -+ // final release of major version -+ registerBreakpoint(MCVersions.V1_18_2, Integer.MAX_VALUE); -+ -+ -+ } -+ -+ static { -+ final Field[] fields = MCVersions.class.getDeclaredFields(); -+ for (final Field field : fields) { -+ final String name = field.getName(); -+ final int value; -+ try { -+ value = field.getInt(null); -+ } catch (final Exception ex) { -+ throw new RuntimeException(ex); -+ } -+ -+ if (VERSION_NAMES.containsKey(value) && value != MCVersions.V15W33B) { // Mojang registered 15w33a and 15w33b under the same id. -+ LOGGER.warn("Error registering version \"" + name + "\", version number '" + value + "' is already associated with \"" + VERSION_NAMES.get(value) + "\""); -+ } -+ -+ VERSION_NAMES.put(value, name.substring(1).replace("_PRE", "-PRE").replace("_RC", "-RC").replace('_', '.').toLowerCase(Locale.ROOT)); -+ } -+ -+ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { -+ if (VERSION_NAMES.containsKey(version)) { -+ continue; -+ } -+ -+ // find closest greatest version above this one -+ int closest = Integer.MAX_VALUE; -+ String closestName = null; -+ for (final int v : VERSION_NAMES.keySet()) { -+ if (v > version && v < closest) { -+ closest = v; -+ closestName = VERSION_NAMES.get(v); -+ } -+ } -+ -+ if (closestName == null) { -+ VERSION_NAMES.put(version, "unregistered_v" + version); -+ } else { -+ VERSION_NAMES.put(version, closestName + "-dev" + (closest - version)); -+ } -+ } -+ -+ // Explicit override for V99, as 99 is very special. -+ VERSION_NAMES.put(99, "pre_converter"); -+ -+ VERSION_LIST = new IntArrayList(new IntRBTreeSet(VERSION_NAMES.keySet())); -+ -+ DATA_VERSION_LIST = new LongArrayList(); -+ for (final int version : VERSION_LIST) { -+ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, 0)); -+ -+ final IntArrayList subVersions = SUBVERSIONS.get(version); -+ if (subVersions == null) { -+ continue; -+ } -+ -+ for (final int step : subVersions) { -+ DATA_VERSION_LIST.add(DataConverter.encodeVersions(version, step)); -+ } -+ } -+ -+ DATA_VERSION_LIST.sort((LongComparator)null); -+ -+ for (final int version : DATACONVERTER_VERSIONS_MAJOR) { -+ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, 0)); -+ -+ final IntArrayList subVersions = SUBVERSIONS.get(version); -+ if (subVersions == null) { -+ continue; -+ } -+ -+ for (final int step : subVersions) { -+ DATACONVERTER_VERSIONS.add(DataConverter.encodeVersions(version, step)); -+ } -+ } -+ } -+ -+ private static void registerSubVersion(final int version, final int step) { -+ if (DATA_VERSION_LIST != null) { -+ throw new IllegalStateException("Added too late!"); -+ } -+ SUBVERSIONS.computeIfAbsent(version, (final int keyInMap) -> { -+ return new IntArrayList(); -+ }).add(step); -+ } -+ -+ private static void registerBreakpoint(final int version) { -+ registerBreakpoint(version, 0); -+ } -+ -+ private static void registerBreakpoint(final int version, final int step) { -+ BREAKPOINTS.add(DataConverter.encodeVersions(version, step)); -+ } -+ -+ // returns only versions that have dataconverters -+ public static boolean hasDataConverters(final int version) { -+ return DATACONVERTER_VERSIONS_MAJOR.contains(version); -+ } -+ -+ public String getVersionName(final int version) { -+ return VERSION_NAMES.get(version); -+ } -+ -+ public boolean isRegisteredVersion(final int version) { -+ return VERSION_NAMES.containsKey(version); -+ } -+ -+ public static IntArrayList getVersionList() { -+ return VERSION_LIST; -+ } -+ -+ public static LongArrayList getDataVersionList() { -+ return DATA_VERSION_LIST; -+ } -+ -+ public static int getMaxVersion() { -+ return VERSION_LIST.getInt(VERSION_LIST.size() - 1); -+ } -+ -+ public static LongArrayList getBreakpoints() { -+ return BREAKPOINTS; -+ } -+ -+ public static void checkVersion(final long version) { -+ if (!DATACONVERTER_VERSIONS.contains(version)) { -+ throw new IllegalStateException("Version " + DataConverter.encodedToString(version) + " is not registered to have dataconverters, yet has a dataconverter"); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..51f869ba58b3f113017a3c32f68896b6d572dc7b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/MCVersions.java -@@ -0,0 +1,423 @@ -+package ca.spottedleaf.dataconverter.minecraft; -+ -+@SuppressWarnings("unused") -+public final class MCVersions { -+ -+ /* https://minecraft.fandom.com/wiki/Data_version */ -+ -+ public static final int V15W32A = 100; -+ public static final int V15W32B = 103; -+ public static final int V15W32C = 104; -+ public static final int V15W33A = 111; -+ public static final int V15W33B = 111; -+ public static final int V15W33C = 112; -+ public static final int V15W34A = 114; -+ public static final int V15W34B = 115; -+ public static final int V15W34C = 116; -+ public static final int V15W34D = 117; -+ public static final int V15W35A = 118; -+ public static final int V15W35B = 119; -+ public static final int V15W35C = 120; -+ public static final int V15W35D = 121; -+ public static final int V15W35E = 122; -+ public static final int V15W36A = 123; -+ public static final int V15W36B = 124; -+ public static final int V15W36C = 125; -+ public static final int V15W36D = 126; -+ public static final int V15W37A = 127; -+ public static final int V15W38A = 128; -+ public static final int V15W38B = 129; -+ public static final int V15W39A = 130; -+ public static final int V15W39B = 131; -+ public static final int V15W39C = 132; -+ public static final int V15W40A = 133; -+ public static final int V15W40B = 134; -+ public static final int V15W41A = 136; -+ public static final int V15W41B = 137; -+ public static final int V15W42A = 138; -+ public static final int V15W43A = 139; -+ public static final int V15W43B = 140; -+ public static final int V15W43C = 141; -+ public static final int V15W44A = 142; -+ public static final int V15W44B = 143; -+ public static final int V15W45A = 145; -+ public static final int V15W46A = 146; -+ public static final int V15W47A = 148; -+ public static final int V15W47B = 149; -+ public static final int V15W47C = 150; -+ public static final int V15W49A = 151; -+ public static final int V15W49B = 152; -+ public static final int V15W50A = 153; -+ public static final int V15W51A = 154; -+ public static final int V15W51B = 155; -+ public static final int V16W02A = 156; -+ public static final int V16W03A = 157; -+ public static final int V16W04A = 158; -+ public static final int V16W05A = 159; -+ public static final int V16W05B = 160; -+ public static final int V16W06A = 161; -+ public static final int V16W07A = 162; -+ public static final int V16W07B = 163; -+ public static final int V1_9_PRE1 = 164; -+ public static final int V1_9_PRE2 = 165; -+ public static final int V1_9_PRE3 = 167; -+ public static final int V1_9_PRE4 = 168; -+ public static final int V1_9 = 169; -+ public static final int V1_9_1_PRE1 = 170; -+ public static final int V1_9_1_PRE2 = 171; -+ public static final int V1_9_1_PRE3 = 172; -+ public static final int V1_9_1 = 175; -+ public static final int V1_9_2 = 176; -+ public static final int V16W14A = 177; -+ public static final int V16W15A = 178; -+ public static final int V16W15B = 179; -+ public static final int V1_9_3_PRE1 = 180; -+ public static final int V1_9_3_PRE2 = 181; -+ public static final int V1_9_3_PRE3 = 182; -+ public static final int V1_9_3 = 183; -+ public static final int V1_9_4 = 184; -+ public static final int V16W20A = 501; -+ public static final int V16W21A = 503; -+ public static final int V16W21B = 504; -+ public static final int V1_10_PRE1 = 506; -+ public static final int V1_10_PRE2 = 507; -+ public static final int V1_10 = 510; -+ public static final int V1_10_1 = 511; -+ public static final int V1_10_2 = 512; -+ public static final int V16W32A = 800; -+ public static final int V16W32B = 801; -+ public static final int V16W33A = 802; -+ public static final int V16W35A = 803; -+ public static final int V16W36A = 805; -+ public static final int V16W38A = 807; -+ public static final int V16W39A = 809; -+ public static final int V16W39B = 811; -+ public static final int V16W39C = 812; -+ public static final int V16W40A = 813; -+ public static final int V16W41A = 814; -+ public static final int V16W42A = 815; -+ public static final int V16W43A = 816; -+ public static final int V16W44A = 817; -+ public static final int V1_11_PRE1 = 818; -+ public static final int V1_11 = 819; -+ public static final int V16W50A = 920; -+ public static final int V1_11_1 = 921; -+ public static final int V1_11_2 = 922; -+ public static final int V17W06A = 1022; -+ public static final int V17W13A = 1122; -+ public static final int V17W13B = 1123; -+ public static final int V17W14A = 1124; -+ public static final int V17W15A = 1125; -+ public static final int V17W16A = 1126; -+ public static final int V17W16B = 1127; -+ public static final int V17W17A = 1128; -+ public static final int V17W17B = 1129; -+ public static final int V17W18A = 1130; -+ public static final int V17W18B = 1131; -+ public static final int V1_12_PRE1 = 1132; -+ public static final int V1_12_PRE2 = 1133; -+ public static final int V1_12_PRE3 = 1134; -+ public static final int V1_12_PRE4 = 1135; -+ public static final int V1_12_PRE5 = 1136; -+ public static final int V1_12_PRE6 = 1137; -+ public static final int V1_12_PRE7 = 1138; -+ public static final int V1_12 = 1139; -+ public static final int V17W31A = 1239; -+ public static final int V1_12_1_PRE1 = 1240; -+ public static final int V1_12_1 = 1241; -+ public static final int V1_12_2_PRE1 = 1341; -+ public static final int V1_12_2_PRE2 = 1342; -+ public static final int V1_12_2 = 1343; -+ public static final int V17W43A = 1444; -+ public static final int V17W43B = 1445; -+ public static final int V17W45A = 1447; -+ public static final int V17W45B = 1448; -+ public static final int V17W46A = 1449; -+ public static final int V17W47A = 1451; -+ public static final int V17W47B = 1452; -+ public static final int V17W48A = 1453; -+ public static final int V17W49A = 1454; -+ public static final int V17W49B = 1455; -+ public static final int V17W50A = 1457; -+ public static final int V18W01A = 1459; -+ public static final int V18W02A = 1461; -+ public static final int V18W03A = 1462; -+ public static final int V18W03B = 1463; -+ public static final int V18W05A = 1464; -+ public static final int V18W06A = 1466; -+ public static final int V18W07A = 1467; -+ public static final int V18W07B = 1468; -+ public static final int V18W07C = 1469; -+ public static final int V18W08A = 1470; -+ public static final int V18W08B = 1471; -+ public static final int V18W09A = 1472; -+ public static final int V18W10A = 1473; -+ public static final int V18W10B = 1474; -+ public static final int V18W10C = 1476; -+ public static final int V18W10D = 1477; -+ public static final int V18W11A = 1478; -+ public static final int V18W14A = 1479; -+ public static final int V18W14B = 1481; -+ public static final int V18W15A = 1482; -+ public static final int V18W16A = 1483; -+ public static final int V18W19A = 1484; -+ public static final int V18W19B = 1485; -+ public static final int V18W20A = 1489; -+ public static final int V18W20B = 1491; -+ public static final int V18W20C = 1493; -+ public static final int V18W21A = 1495; -+ public static final int V18W21B = 1496; -+ public static final int V18W22A = 1497; -+ public static final int V18W22B = 1498; -+ public static final int V18W22C = 1499; -+ public static final int V1_13_PRE1 = 1501; -+ public static final int V1_13_PRE2 = 1502; -+ public static final int V1_13_PRE3 = 1503; -+ public static final int V1_13_PRE4 = 1504; -+ public static final int V1_13_PRE5 = 1511; -+ public static final int V1_13_PRE6 = 1512; -+ public static final int V1_13_PRE7 = 1513; -+ public static final int V1_13_PRE8 = 1516; -+ public static final int V1_13_PRE9 = 1517; -+ public static final int V1_13_PRE10 = 1518; -+ public static final int V1_13 = 1519; -+ public static final int V18W30A = 1620; -+ public static final int V18W30B = 1621; -+ public static final int V18W31A = 1622; -+ public static final int V18W32A = 1623; -+ public static final int V18W33A = 1625; -+ public static final int V1_13_1_PRE1 = 1626; -+ public static final int V1_13_1_PRE2 = 1627; -+ public static final int V1_13_1 = 1628; -+ public static final int V1_13_2_PRE1 = 1629; -+ public static final int V1_13_2_PRE2 = 1630; -+ public static final int V1_13_2 = 1631; -+ public static final int V18W43A = 1901; -+ public static final int V18W43B = 1902; -+ public static final int V18W43C = 1903; -+ public static final int V18W44A = 1907; -+ public static final int V18W45A = 1908; -+ public static final int V18W46A = 1910; -+ public static final int V18W47A = 1912; -+ public static final int V18W47B = 1913; -+ public static final int V18W48A = 1914; -+ public static final int V18W48B = 1915; -+ public static final int V18W49A = 1916; -+ public static final int V18W50A = 1919; -+ public static final int V19W02A = 1921; -+ public static final int V19W03A = 1922; -+ public static final int V19W03B = 1923; -+ public static final int V19W03C = 1924; -+ public static final int V19W04A = 1926; -+ public static final int V19W04B = 1927; -+ public static final int V19W05A = 1930; -+ public static final int V19W06A = 1931; -+ public static final int V19W07A = 1932; -+ public static final int V19W08A = 1933; -+ public static final int V19W08B = 1934; -+ public static final int V19W09A = 1935; -+ public static final int V19W11A = 1937; -+ public static final int V19W11B = 1938; -+ public static final int V19W12A = 1940; -+ public static final int V19W12B = 1941; -+ public static final int V19W13A = 1942; -+ public static final int V19W13B = 1943; -+ public static final int V19W14A = 1944; -+ public static final int V19W14B = 1945; -+ public static final int V1_14_PRE1 = 1947; -+ public static final int V1_14_PRE2 = 1948; -+ public static final int V1_14_PRE3 = 1949; -+ public static final int V1_14_PRE4 = 1950; -+ public static final int V1_14_PRE5 = 1951; -+ public static final int V1_14 = 1952; -+ public static final int V1_14_1_PRE1 = 1955; -+ public static final int V1_14_1_PRE2 = 1956; -+ public static final int V1_14_1 = 1957; -+ public static final int V1_14_2_PRE1 = 1958; -+ public static final int V1_14_2_PRE2 = 1959; -+ public static final int V1_14_2_PRE3 = 1960; -+ public static final int V1_14_2_PRE4 = 1962; -+ public static final int V1_14_2 = 1963; -+ public static final int V1_14_3_PRE1 = 1964; -+ public static final int V1_14_3_PRE2 = 1965; -+ public static final int V1_14_3_PRE3 = 1966; -+ public static final int V1_14_3_PRE4 = 1967; -+ public static final int V1_14_3 = 1968; -+ public static final int V1_14_4_PRE1 = 1969; -+ public static final int V1_14_4_PRE2 = 1970; -+ public static final int V1_14_4_PRE3 = 1971; -+ public static final int V1_14_4_PRE4 = 1972; -+ public static final int V1_14_4_PRE5 = 1973; -+ public static final int V1_14_4_PRE6 = 1974; -+ public static final int V1_14_4_PRE7 = 1975; -+ public static final int V1_14_4 = 1976; -+ public static final int V19W34A = 2200; -+ public static final int V19W35A = 2201; -+ public static final int V19W36A = 2203; -+ public static final int V19W37A = 2204; -+ public static final int V19W38A = 2205; -+ public static final int V19W38B = 2206; -+ public static final int V19W39A = 2207; -+ public static final int V19W40A = 2208; -+ public static final int V19W41A = 2210; -+ public static final int V19W42A = 2212; -+ public static final int V19W44A = 2213; -+ public static final int V19W45A = 2214; -+ public static final int V19W45B = 2215; -+ public static final int V19W46A = 2216; -+ public static final int V19W46B = 2217; -+ public static final int V1_15_PRE1 = 2218; -+ public static final int V1_15_PRE2 = 2219; -+ public static final int V1_15_PRE3 = 2220; -+ public static final int V1_15_PRE4 = 2221; -+ public static final int V1_15_PRE5 = 2222; -+ public static final int V1_15_PRE6 = 2223; -+ public static final int V1_15_PRE7 = 2224; -+ public static final int V1_15 = 2225; -+ public static final int V1_15_1_PRE1 = 2226; -+ public static final int V1_15_1 = 2227; -+ public static final int V1_15_2_PRE1 = 2228; -+ public static final int V1_15_2_PRE2 = 2229; -+ public static final int V1_15_2 = 2230; -+ public static final int V20W06A = 2504; -+ public static final int V20W07A = 2506; -+ public static final int V20W08A = 2507; -+ public static final int V20W09A = 2510; -+ public static final int V20W10A = 2512; -+ public static final int V20W11A = 2513; -+ public static final int V20W12A = 2515; -+ public static final int V20W13A = 2520; -+ public static final int V20W13B = 2521; -+ public static final int V20W14A = 2524; -+ public static final int V20W15A = 2525; -+ public static final int V20W16A = 2526; -+ public static final int V20W17A = 2529; -+ public static final int V20W18A = 2532; -+ public static final int V20W19A = 2534; -+ public static final int V20W20A = 2536; -+ public static final int V20W20B = 2537; -+ public static final int V20W21A = 2554; -+ public static final int V20W22A = 2555; -+ public static final int V1_16_PRE1 = 2556; -+ public static final int V1_16_PRE2 = 2557; -+ public static final int V1_16_PRE3 = 2559; -+ public static final int V1_16_PRE4 = 2560; -+ public static final int V1_16_PRE5 = 2561; -+ public static final int V1_16_PRE6 = 2562; -+ public static final int V1_16_PRE7 = 2563; -+ public static final int V1_16_PRE8 = 2564; -+ public static final int V1_16_RC1 = 2565; -+ public static final int V1_16 = 2566; -+ public static final int V1_16_1 = 2567; -+ public static final int V20W27A = 2569; -+ public static final int V20W28A = 2570; -+ public static final int V20W29A = 2571; -+ public static final int V20W30A = 2572; -+ public static final int V1_16_2_PRE1 = 2573; -+ public static final int V1_16_2_PRE2 = 2574; -+ public static final int V1_16_2_PRE3 = 2575; -+ public static final int V1_16_2_RC1 = 2576; -+ public static final int V1_16_2_RC2 = 2577; -+ public static final int V1_16_2 = 2578; -+ public static final int V1_16_3_RC1 = 2579; -+ public static final int V1_16_3 = 2580; -+ public static final int V1_16_4_PRE1 = 2581; -+ public static final int V1_16_4_PRE2 = 2582; -+ public static final int V1_16_4_RC1 = 2583; -+ public static final int V1_16_4 = 2584; -+ public static final int V1_16_5_RC1 = 2585; -+ public static final int V1_16_5 = 2586; -+ public static final int V20W45A = 2681; -+ public static final int V20W46A = 2682; -+ public static final int V20W48A = 2683; -+ public static final int V20W49A = 2685; -+ public static final int V20W51A = 2687; -+ public static final int V21W03A = 2689; -+ public static final int V21W05A = 2690; -+ public static final int V21W05B = 2692; -+ public static final int V21W06A = 2694; -+ public static final int V21W07A = 2695; -+ public static final int V21W08A = 2697; -+ public static final int V21W08B = 2698; -+ public static final int V21W10A = 2699; -+ public static final int V21W11A = 2703; -+ public static final int V21W13A = 2705; -+ public static final int V21W14A = 2706; -+ public static final int V21W15A = 2709; -+ public static final int V21W16A = 2711; -+ public static final int V21W17A = 2712; -+ public static final int V21W18A = 2713; -+ public static final int V21W19A = 2714; -+ public static final int V21W20A = 2715; -+ public static final int V1_17_PRE1 = 2716; -+ public static final int V1_17_PRE2 = 2718; -+ public static final int V1_17_PRE3 = 2719; -+ public static final int V1_17_PRE4 = 2720; -+ public static final int V1_17_PRE5 = 2721; -+ public static final int V1_17_RC1 = 2722; -+ public static final int V1_17_RC2 = 2723; -+ public static final int V1_17 = 2724; -+ public static final int V1_17_1_PRE1 = 2725; -+ public static final int V1_17_1_PRE2 = 2726; -+ public static final int V1_17_1_PRE3 = 2727; -+ public static final int V1_17_1_RC1 = 2728; -+ public static final int V1_17_1_RC2 = 2729; -+ public static final int V1_17_1 = 2730; -+ public static final int V21W37A = 2834; -+ public static final int V21W38A = 2835; -+ public static final int V21W39A = 2836; -+ public static final int V21W40A = 2838; -+ public static final int V21W41A = 2839; -+ public static final int V21W42A = 2840; -+ public static final int V21W43A = 2844; -+ public static final int V21W44A = 2845; -+ public static final int V1_18_PRE1 = 2847; -+ public static final int V1_18_PRE2 = 2848; -+ public static final int V1_18_PRE3 = 2849; -+ public static final int V1_18_PRE4 = 2850; -+ public static final int V1_18_PRE5 = 2851; -+ public static final int V1_18_PRE6 = 2853; -+ public static final int V1_18_PRE7 = 2854; -+ public static final int V1_18_PRE8 = 2855; -+ public static final int V1_18_RC1 = 2856; -+ public static final int V1_18_RC2 = 2857; -+ public static final int V1_18_RC3 = 2858; -+ public static final int V1_18_RC4 = 2859; -+ public static final int V1_18 = 2860; -+ public static final int V1_18_1_PRE1 = 2861; -+ public static final int V1_18_1_RC1 = 2862; -+ public static final int V1_18_1_RC2 = 2863; -+ public static final int V1_18_1_RC3 = 2864; -+ public static final int V1_18_1 = 2865; -+ public static final int V22W03A = 2966; -+ public static final int V22W05A = 2967; -+ public static final int V22W06A = 2968; -+ public static final int V22W07A = 2969; -+ public static final int V1_18_2_PRE1 = 2971; -+ public static final int V1_18_2_PRE2 = 2972; -+ public static final int V1_18_2_PRE3 = 2973; -+ public static final int V1_18_2_RC1 = 2974; -+ public static final int V1_18_2 = 2975; -+ public static final int V22W11A = 3080; -+ public static final int V22W12A = 3082; -+ public static final int V22W13A = 3085; -+ public static final int V22W14A = 3088; -+ public static final int V22W15A = 3089; -+ public static final int V22W16A = 3091; -+ public static final int V22W16B = 3092; -+ public static final int V22W17A = 3093; -+ public static final int V22W18A = 3095; -+ public static final int V22W19A = 3096; -+ public static final int V1_19_PRE1 = 3098; -+ public static final int V1_19_PRE2 = 3099; -+ public static final int V1_19_PRE3 = 3100; -+ public static final int V1_19_PRE4 = 3101; -+ public static final int V1_19_PRE5 = 3102; -+ public static final int V1_19_RC1 = 3103; -+ public static final int V1_19_RC2 = 3104; -+ public static final int V1_19 = 3105; -+ public static final int V22W24A = 3106; -+ public static final int V1_19_1_PRE1 = 3107; -+ public static final int V1_19_1_RC1 = 3109; -+ public static final int V1_19_1_PRE2 = 3110; -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ae3aed21c1fccb688e9a1665e2d317a77508d157 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterAbstractAdvancementsRename.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.advancements; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractAdvancementsRename { -+ -+ private ConverterAbstractAdvancementsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameKeys(data, renamer); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2a4d16e6a2f9d71dbfa692922671581c2bec136 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/advancements/ConverterCriteriaRename.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.advancements; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterCriteriaRename extends DataConverter, MapType> { -+ -+ public final String path; -+ public final Function conversion; -+ -+ public ConverterCriteriaRename(final int toVersion, final String path, final Function conversion) { -+ super(toVersion); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ public ConverterCriteriaRename(final int toVersion, final int versionStep, final String path, final Function conversion) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType advancement = data.getMap(this.path); -+ if (advancement == null) { -+ return null; -+ } -+ -+ final MapType criteria = advancement.getMap("criteria"); -+ if (criteria == null) { -+ return null; -+ } -+ -+ RenameHelper.renameKeys(criteria, this.conversion); -+ -+ return null; -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ba9daaab1abd53a3fbdebd78e05ba363251188c6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/blockname/ConverterAbstractBlockRename.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.blockname; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractBlockRename { -+ -+ private ConverterAbstractBlockRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.BLOCK_NAME, renamer); -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String name = data.getString("Name"); -+ if (name != null) { -+ final String converted = renamer.apply(name); -+ if (converted != null) { -+ data.setString("Name", converted); -+ } -+ } -+ return null; -+ } -+ }); -+ } -+ -+ public static void registerAndFixJigsaw(final int version, final Function renamer) { -+ registerAndFixJigsaw(version, 0, renamer); -+ } -+ -+ public static void registerAndFixJigsaw(final int version, final int subVersion, final Function renamer) { -+ register(version, subVersion, renamer); -+ // TODO check on update, minecraft:jigsaw can change -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String finalState = data.getString("final_state"); -+ if (finalState == null || finalState.isEmpty()) { -+ return null; -+ } -+ -+ final int nbtStart1 = finalState.indexOf('['); -+ final int nbtStart2 = finalState.indexOf('{'); -+ int stateNameEnd = finalState.length(); -+ if (nbtStart1 > 0) { -+ stateNameEnd = Math.min(stateNameEnd, nbtStart1); -+ } -+ -+ if (nbtStart2 > 0) { -+ stateNameEnd = Math.min(stateNameEnd, nbtStart2); -+ } -+ -+ final String blockStateName = finalState.substring(0, stateNameEnd); -+ final String converted = renamer.apply(blockStateName); -+ if (converted == null) { -+ return null; -+ } -+ -+ final String convertedState = converted.concat(finalState.substring(stateNameEnd)); -+ data.setString("final_state", convertedState); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java -new file mode 100644 -index 0000000000000000000000000000000000000000..300c2d14818b1e0cfe7341aba573ec75d0581b26 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/chunk/ConverterFlattenChunk.java -@@ -0,0 +1,1016 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.chunk; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.datafixers.DataFixUtils; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntArrayList; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import net.minecraft.util.datafix.PackedBitStorage; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.BitSet; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.Map; -+import java.util.Objects; -+ -+import static it.unimi.dsi.fastutil.HashCommon.arraySize; -+ -+public final class ConverterFlattenChunk extends DataConverter, MapType> { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ static final BitSet VIRTUAL_SET = new BitSet(256); -+ static final BitSet IDS_NEEDING_FIX_SET = new BitSet(256); -+ -+ static { -+ IDS_NEEDING_FIX_SET.set(2); -+ IDS_NEEDING_FIX_SET.set(3); -+ IDS_NEEDING_FIX_SET.set(110); -+ IDS_NEEDING_FIX_SET.set(140); -+ IDS_NEEDING_FIX_SET.set(144); -+ IDS_NEEDING_FIX_SET.set(25); -+ IDS_NEEDING_FIX_SET.set(86); -+ IDS_NEEDING_FIX_SET.set(26); -+ IDS_NEEDING_FIX_SET.set(176); -+ IDS_NEEDING_FIX_SET.set(177); -+ IDS_NEEDING_FIX_SET.set(175); -+ IDS_NEEDING_FIX_SET.set(64); -+ IDS_NEEDING_FIX_SET.set(71); -+ IDS_NEEDING_FIX_SET.set(193); -+ IDS_NEEDING_FIX_SET.set(194); -+ IDS_NEEDING_FIX_SET.set(195); -+ IDS_NEEDING_FIX_SET.set(196); -+ IDS_NEEDING_FIX_SET.set(197); -+ -+ VIRTUAL_SET.set(54); -+ VIRTUAL_SET.set(146); -+ VIRTUAL_SET.set(25); -+ VIRTUAL_SET.set(26); -+ VIRTUAL_SET.set(51); -+ VIRTUAL_SET.set(53); -+ VIRTUAL_SET.set(67); -+ VIRTUAL_SET.set(108); -+ VIRTUAL_SET.set(109); -+ VIRTUAL_SET.set(114); -+ VIRTUAL_SET.set(128); -+ VIRTUAL_SET.set(134); -+ VIRTUAL_SET.set(135); -+ VIRTUAL_SET.set(136); -+ VIRTUAL_SET.set(156); -+ VIRTUAL_SET.set(163); -+ VIRTUAL_SET.set(164); -+ VIRTUAL_SET.set(180); -+ VIRTUAL_SET.set(203); -+ VIRTUAL_SET.set(55); -+ VIRTUAL_SET.set(85); -+ VIRTUAL_SET.set(113); -+ VIRTUAL_SET.set(188); -+ VIRTUAL_SET.set(189); -+ VIRTUAL_SET.set(190); -+ VIRTUAL_SET.set(191); -+ VIRTUAL_SET.set(192); -+ VIRTUAL_SET.set(93); -+ VIRTUAL_SET.set(94); -+ VIRTUAL_SET.set(101); -+ VIRTUAL_SET.set(102); -+ VIRTUAL_SET.set(160); -+ VIRTUAL_SET.set(106); -+ VIRTUAL_SET.set(107); -+ VIRTUAL_SET.set(183); -+ VIRTUAL_SET.set(184); -+ VIRTUAL_SET.set(185); -+ VIRTUAL_SET.set(186); -+ VIRTUAL_SET.set(187); -+ VIRTUAL_SET.set(132); -+ VIRTUAL_SET.set(139); -+ VIRTUAL_SET.set(199); -+ } -+ -+ static final boolean[] VIRTUAL = toBooleanArray(VIRTUAL_SET); -+ static final boolean[] IDS_NEEDING_FIX = toBooleanArray(IDS_NEEDING_FIX_SET); -+ -+ private static boolean[] toBooleanArray(final BitSet set) { -+ final boolean[] ret = new boolean[4096]; -+ for (int i = 0; i < 4096; ++i) { -+ ret[i] = set.get(i); -+ } -+ -+ return ret; -+ } -+ -+ static final MapType PUMPKIN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:pumpkin'}"); -+ static final MapType SNOWY_PODZOL = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:podzol',Properties:{snowy:'true'}}"); -+ static final MapType SNOWY_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:grass_block',Properties:{snowy:'true'}}"); -+ static final MapType SNOWY_MYCELIUM = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); -+ static final MapType UPPER_SUNFLOWER = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:sunflower',Properties:{half:'upper'}}"); -+ static final MapType UPPER_LILAC = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:lilac',Properties:{half:'upper'}}"); -+ static final MapType UPPER_TALL_GRASS = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:tall_grass',Properties:{half:'upper'}}"); -+ static final MapType UPPER_LARGE_FERN = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:large_fern',Properties:{half:'upper'}}"); -+ static final MapType UPPER_ROSE_BUSH = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:rose_bush',Properties:{half:'upper'}}"); -+ static final MapType UPPER_PEONY = HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:peony',Properties:{half:'upper'}}"); -+ -+ static final Map> FLOWER_POT_MAP = new HashMap<>(); -+ static { -+ FLOWER_POT_MAP.put("minecraft:air0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:flower_pot'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_poppy'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_blue_orchid'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_allium'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_azure_bluet'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_orange_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower6", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_white_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower7", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_pink_tulip'}")); -+ FLOWER_POT_MAP.put("minecraft:red_flower8", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oxeye_daisy'}")); -+ FLOWER_POT_MAP.put("minecraft:yellow_flower0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dandelion'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_oak_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling1", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_spruce_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_birch_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling3", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_jungle_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling4", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_acacia_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:sapling5", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dark_oak_sapling'}")); -+ FLOWER_POT_MAP.put("minecraft:red_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_red_mushroom'}")); -+ FLOWER_POT_MAP.put("minecraft:brown_mushroom0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_brown_mushroom'}")); -+ FLOWER_POT_MAP.put("minecraft:deadbush0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_dead_bush'}")); -+ FLOWER_POT_MAP.put("minecraft:tallgrass2", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_fern'}")); -+ FLOWER_POT_MAP.put("minecraft:cactus0", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:potted_cactus'}")); // we change default to empty -+ } -+ -+ static final Map> SKULL_MAP = new HashMap<>(); -+ static { -+ mapSkull(SKULL_MAP, 0, "skeleton", "skull"); -+ mapSkull(SKULL_MAP, 1, "wither_skeleton", "skull"); -+ mapSkull(SKULL_MAP, 2, "zombie", "head"); -+ mapSkull(SKULL_MAP, 3, "player", "head"); -+ mapSkull(SKULL_MAP, 4, "creeper", "head"); -+ mapSkull(SKULL_MAP, 5, "dragon", "head"); -+ }; -+ -+ private static void mapSkull(final Map> into, final int oldId, final String newId, final String skullType) { -+ into.put(oldId + "north", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'north'}}")); -+ into.put(oldId + "east", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'east'}}")); -+ into.put(oldId + "south", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'south'}}")); -+ into.put(oldId + "west", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_wall_" + skullType + "',Properties:{facing:'west'}}")); -+ -+ for (int rotation = 0; rotation < 16; ++rotation) { -+ into.put(oldId + "" + rotation, -+ HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + newId + "_" + skullType + "',Properties:{rotation:'" + rotation + "'}}")); -+ } -+ } -+ -+ static final Map> DOOR_MAP = new HashMap<>(); -+ static { -+ mapDoor(DOOR_MAP, "oak_door", 1024); -+ mapDoor(DOOR_MAP, "iron_door", 1136); -+ mapDoor(DOOR_MAP, "spruce_door", 3088); -+ mapDoor(DOOR_MAP, "birch_door", 3104); -+ mapDoor(DOOR_MAP, "jungle_door", 3120); -+ mapDoor(DOOR_MAP, "acacia_door", 3136); -+ mapDoor(DOOR_MAP, "dark_oak_door", 3152); -+ }; -+ -+ private static void mapDoor(final Map> into, final String type, final int oldId) { -+ into.put("minecraft:" + type + "eastlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId))); -+ into.put("minecraft:" + type + "eastlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 4))); -+ into.put("minecraft:" + type + "eastlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastupperleftfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 8))); -+ into.put("minecraft:" + type + "eastupperleftfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 10))); -+ into.put("minecraft:" + type + "eastupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "eastupperrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 9))); -+ into.put("minecraft:" + type + "eastupperrightfalsetrue", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 11))); -+ into.put("minecraft:" + type + "eastupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "eastupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 3))); -+ into.put("minecraft:" + type + "northlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 7))); -+ into.put("minecraft:" + type + "northlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "northupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "northupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 1))); -+ into.put("minecraft:" + type + "southlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 5))); -+ into.put("minecraft:" + type + "southlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "southupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "southupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westlowerleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westlowerlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerrightfalsefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 2))); -+ into.put("minecraft:" + type + "westlowerrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westlowerrighttruefalse", Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(oldId + 6))); -+ into.put("minecraft:" + type + "westlowerrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperleftfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperleftfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperlefttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperlefttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperrightfalsefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperrightfalsetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}")); -+ into.put("minecraft:" + type + "westupperrighttruefalse", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}")); -+ into.put("minecraft:" + type + "westupperrighttruetrue", HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + type + "',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}")); -+ } -+ -+ static final Map> NOTE_BLOCK_MAP = new HashMap<>(); -+ static { -+ for(int note = 0; note < 26; ++note) { -+ NOTE_BLOCK_MAP.put("true" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'true',note:'" + note + "'}}")); -+ NOTE_BLOCK_MAP.put("false" + note, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:note_block',Properties:{powered:'false',note:'" + note + "'}}")); -+ } -+ } -+ -+ static final Int2ObjectOpenHashMap DYE_COLOR_MAP = new Int2ObjectOpenHashMap<>(); -+ static { -+ DYE_COLOR_MAP.put(0, "white"); -+ DYE_COLOR_MAP.put(1, "orange"); -+ DYE_COLOR_MAP.put(2, "magenta"); -+ DYE_COLOR_MAP.put(3, "light_blue"); -+ DYE_COLOR_MAP.put(4, "yellow"); -+ DYE_COLOR_MAP.put(5, "lime"); -+ DYE_COLOR_MAP.put(6, "pink"); -+ DYE_COLOR_MAP.put(7, "gray"); -+ DYE_COLOR_MAP.put(8, "light_gray"); -+ DYE_COLOR_MAP.put(9, "cyan"); -+ DYE_COLOR_MAP.put(10, "purple"); -+ DYE_COLOR_MAP.put(11, "blue"); -+ DYE_COLOR_MAP.put(12, "brown"); -+ DYE_COLOR_MAP.put(13, "green"); -+ DYE_COLOR_MAP.put(14, "red"); -+ DYE_COLOR_MAP.put(15, "black"); -+ } -+ -+ static final Map> BED_BLOCK_MAP = new HashMap<>(); -+ -+ static { -+ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { -+ if (!Objects.equals(entry.getValue(), "red")) { -+ addBeds(BED_BLOCK_MAP, entry.getIntKey(), entry.getValue()); -+ } -+ } -+ } -+ -+ private static void addBeds(final Map> into, final int colourId, final String colourName) { -+ into.put("southfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}")); -+ into.put("westfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}")); -+ into.put("northfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}")); -+ into.put("eastfalsefoot" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}")); -+ into.put("southfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'false',part:'head'}}")); -+ into.put("westfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'false',part:'head'}}")); -+ into.put("northfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'false',part:'head'}}")); -+ into.put("eastfalsehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'false',part:'head'}}")); -+ into.put("southtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'south',occupied:'true',part:'head'}}")); -+ into.put("westtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'west',occupied:'true',part:'head'}}")); -+ into.put("northtruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'north',occupied:'true',part:'head'}}")); -+ into.put("easttruehead" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_bed',Properties:{facing:'east',occupied:'true',part:'head'}}")); -+ } -+ -+ static final Map> BANNER_BLOCK_MAP = new HashMap<>(); -+ -+ static { -+ for (final Int2ObjectMap.Entry entry : DYE_COLOR_MAP.int2ObjectEntrySet()) { -+ if (!Objects.equals(entry.getValue(), "white")) { -+ addBanners(BANNER_BLOCK_MAP, 15 - entry.getIntKey(), entry.getValue()); -+ } -+ } -+ } -+ -+ private static void addBanners(final Map> into, final int colourId, final String colourName) { -+ for(int rotation = 0; rotation < 16; ++rotation) { -+ into.put("" + rotation + "_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_banner',Properties:{rotation:'" + rotation + "'}}")); -+ } -+ -+ into.put("north_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'north'}}")); -+ into.put("south_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'south'}}")); -+ into.put("west_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'west'}}")); -+ into.put("east_" + colourId, HelperBlockFlatteningV1450.parseTag("{Name:'minecraft:" + colourName + "_wall_banner',Properties:{facing:'east'}}")); -+ } -+ -+ static final MapType AIR = Objects.requireNonNull(HelperBlockFlatteningV1450.getNBTForId(0)); -+ -+ public ConverterFlattenChunk() { -+ super(MCVersions.V17W47A, 1); -+ } -+ -+ static String getName(final MapType blockState) { -+ return blockState.getString("Name"); -+ } -+ -+ static String getProperty(final MapType blockState, final String propertyName) { -+ final MapType properties = blockState.getMap("Properties"); -+ if (properties == null) { -+ return ""; -+ } -+ -+ return properties.getString(propertyName, ""); -+ } -+ -+ static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { -+ if (noBack) { -+ if (noRight) { -+ return 2; -+ } else if (noLeft) { -+ return 128; -+ } else { -+ return 1; -+ } -+ } else if (noForward) { -+ if (noLeft) { -+ return 32; -+ } else if (noRight) { -+ return 8; -+ } else { -+ return 16; -+ } -+ } else if (noRight) { -+ return 4; -+ } else if (noLeft) { -+ return 64; -+ } else { -+ return 0; -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ if (!level.hasKey("Sections", ObjectType.LIST)) { -+ return null; -+ } -+ -+ data.setMap("Level", new UpgradeChunk(level).writeBackToLevel()); -+ -+ return null; -+ } -+ -+ static enum Direction { -+ DOWN(AxisDirection.NEGATIVE, Axis.Y), -+ UP(AxisDirection.POSITIVE, Axis.Y), -+ NORTH(AxisDirection.NEGATIVE, Axis.Z), -+ SOUTH(AxisDirection.POSITIVE, Axis.Z), -+ WEST(AxisDirection.NEGATIVE, Axis.X), -+ EAST(AxisDirection.POSITIVE, Axis.X); -+ -+ private final Axis axis; -+ private final AxisDirection axisDirection; -+ -+ private Direction(final AxisDirection axisDirection, final Axis axis) { -+ this.axis = axis; -+ this.axisDirection = axisDirection; -+ } -+ -+ public AxisDirection getAxisDirection() { -+ return this.axisDirection; -+ } -+ -+ public Axis getAxis() { -+ return this.axis; -+ } -+ -+ public static enum AxisDirection { -+ POSITIVE(1), -+ NEGATIVE(-1); -+ -+ private final int step; -+ -+ private AxisDirection(final int step) { -+ this.step = step; -+ } -+ -+ public int getStep() { -+ return this.step; -+ } -+ } -+ -+ public static enum Axis { -+ X, Y, Z; -+ } -+ } -+ -+ static class DataLayer { -+ private final byte[] data; -+ -+ public DataLayer() { -+ this.data = new byte[2048]; -+ } -+ -+ public DataLayer(final byte[] data) { -+ this.data = data; -+ if (data.length != 2048) { -+ throw new IllegalArgumentException("ChunkNibbleArrays should be 2048 bytes not: " + data.length); -+ } -+ } -+ -+ public static DataLayer getOrNull(final byte[] data) { -+ return data == null ? null : new DataLayer(data); -+ } -+ -+ public static DataLayer getOrCreate(final byte[] data) { -+ return data == null ? new DataLayer() : new DataLayer(data); -+ } -+ -+ public int get(final int index) { -+ final byte value = this.data[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ public int get(final int x, final int y, final int z) { -+ final int index = y << 8 | z << 4 | x; -+ final byte value = this.data[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ } -+ -+ static final class UpgradeChunk { -+ int sides; -+ -+ final Section[] sections = new Section[16]; -+ final MapType level; -+ final int blockX; -+ final int blockZ; -+ final Int2ObjectLinkedOpenHashMap> tileEntities = new Int2ObjectLinkedOpenHashMap<>(16); -+ -+ public UpgradeChunk(final MapType level) { -+ this.level = level; -+ this.blockX = level.getInt("xPos") << 4; -+ this.blockZ = level.getInt("zPos") << 4; -+ -+ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ if (tileEntities != null) { -+ for (int i = 0, len = tileEntities.size(); i < len; ++i) { -+ final MapType tileEntity = tileEntities.getMap(i); -+ -+ final int x = (tileEntity.getInt("x") - this.blockX) & 15; -+ final int y = tileEntity.getInt("y"); -+ final int z = (tileEntity.getInt("z") - this.blockZ) & 15; -+ final int index = (y << 8) | (z << 4) | x; -+ if (this.tileEntities.put(index, tileEntity) != null) { -+ LOGGER.warn("In chunk: {}x{} found a duplicate block entity at position (ConverterFlattenChunk): [{}, {}, {}]", this.blockX, this.blockZ, x, y, z); -+ } -+ } -+ } -+ -+ final boolean convertedFromAlphaFormat = level.getBoolean("convertedFromAlphaFormat"); -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType sectionData = sections.getMap(i); -+ final Section section = new Section(sectionData); -+ -+ if (section.y < 0 || section.y > 15) { -+ LOGGER.warn("In chunk: {}x{} found an invalid chunk section y (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); -+ continue; -+ } -+ -+ if (this.sections[section.y] != null) { -+ LOGGER.warn("In chunk: {}x{} found a duplicate chunk section (ConverterFlattenChunk): {}", this.blockX, this.blockZ, section.y); -+ } -+ -+ this.sides = section.upgrade(this.sides); -+ this.sections[section.y] = section; -+ } -+ } -+ -+ for (final Section section : this.sections) { -+ if (section == null) { -+ continue; -+ } -+ -+ final int yIndex = section.y << (8 + 4); -+ -+ for (final Iterator> iterator = section.toFix.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Int2ObjectMap.Entry fixEntry = iterator.next(); -+ final IntIterator positionIterator = fixEntry.getValue().iterator(); -+ switch (fixEntry.getIntKey()) { -+ case 2: { // grass block -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"minecraft:grass_block".equals(getName(blockState))) { -+ continue; -+ } -+ -+ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { -+ this.setBlock(position, SNOWY_GRASS); -+ } -+ } -+ break; -+ } -+ case 3: { // dirt -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"minecraft:podzol".equals(getName(blockState))) { -+ continue; -+ } -+ -+ final String blockAbove = getName(getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(blockAbove) || "minecraft:snow_layer".equals(blockAbove)) { -+ this.setBlock(position, SNOWY_PODZOL); -+ } -+ } -+ break; -+ } -+ case 25: { // note block -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.removeBlockEntity(position); -+ if (tile != null) { -+ final String state = Boolean.toString(tile.getBoolean("powered")) + (byte) Math.min(Math.max(tile.getInt("note"), 0), 24); -+ this.setBlock(position, NOTE_BLOCK_MAP.getOrDefault(state, NOTE_BLOCK_MAP.get("false0"))); -+ } -+ } -+ break; -+ } -+ case 26: { // bed -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ -+ if (tile == null) { -+ continue; -+ } -+ -+ final MapType blockState = this.getBlock(position); -+ -+ final int colour = tile.getInt("color"); -+ if (colour != 14 && colour >= 0 && colour < 16) { -+ final String state = getProperty(blockState, "facing") + getProperty(blockState, "occupied") + getProperty(blockState, "part") + colour; -+ -+ final MapType update = BED_BLOCK_MAP.get(state); -+ if (update != null) { -+ this.setBlock(position, update); -+ } -+ } -+ } -+ break; -+ } -+ case 64: // oak door -+ case 71: // iron door -+ case 193: // spruce door -+ case 194: // birch door -+ case 195: // jungle door -+ case 196: // acacia door -+ case 197: { // dark oak door -+ // aka the door updater -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!getName(blockState).endsWith("_door")) { -+ continue; -+ } -+ -+ if (!"lower".equals(getProperty(blockState, "half"))) { -+ continue; -+ } -+ -+ final int positionAbove = relative(position, Direction.UP); -+ final MapType blockStateAbove = this.getBlock(positionAbove); -+ -+ final String name = getName(blockState); -+ if (name.equals(getName(blockStateAbove))) { -+ final String facingBelow = getProperty(blockState, "facing"); -+ final String openBelow = getProperty(blockState, "open"); -+ final String hingeAbove = convertedFromAlphaFormat ? "left" : getProperty(blockStateAbove, "hinge"); -+ final String poweredAbove = convertedFromAlphaFormat ? "false" : getProperty(blockStateAbove, "powered"); -+ -+ this.setBlock(position, DOOR_MAP.get(name + facingBelow + "lower" + hingeAbove + openBelow + poweredAbove)); -+ this.setBlock(positionAbove, DOOR_MAP.get(name + facingBelow + "upper" + hingeAbove + openBelow + poweredAbove)); -+ } -+ } -+ break; -+ } -+ case 86: { // pumpkin -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ -+ // I guess this is some terrible hack to convert carved pumpkins from world gen into -+ // regular pumpkins? -+ -+ if ("minecraft:carved_pumpkin".equals(getName(blockState))) { -+ final String downName = getName(this.getBlock(relative(position, Direction.DOWN))); -+ if ("minecraft:grass_block".equals(downName) || "minecraft:dirt".equals(downName)) { -+ this.setBlock(position, PUMPKIN); -+ } -+ } -+ } -+ break; -+ } -+ case 110: { // mycelium -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if ("minecraft:mycelium".equals(getName(blockState))) { -+ final String nameAbove = getName(this.getBlock(relative(position, Direction.UP))); -+ if ("minecraft:snow".equals(nameAbove) || "minecraft:snow_layer".equals(nameAbove)) { -+ this.setBlock(position, SNOWY_MYCELIUM); -+ } -+ } -+ } -+ break; -+ } -+ case 140: { // flower pot -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.removeBlockEntity(position); -+ if (tile == null) { -+ continue; -+ } -+ -+ final String item; -+ if (tile.hasKey("Item", ObjectType.NUMBER)) { -+ // the item name converter should have migrated to number, however no legacy converter -+ // ever did this. so we can get data with versions above v102 (old worlds, converted prior to DFU) -+ // that didn't convert. so just do it here. -+ item = HelperItemNameV102.getNameFromId(tile.getInt("Item")); -+ } else { -+ item = tile.getString("Item", ""); -+ } -+ -+ final String state = item + tile.getInt("Data"); -+ this.setBlock(position, FLOWER_POT_MAP.getOrDefault(state, FLOWER_POT_MAP.get("minecraft:air0"))); -+ } -+ break; -+ } -+ case 144: { // mob head -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ if (tile == null) { -+ continue; -+ } -+ -+ final String typeString = Integer.toString(tile.getInt("SkullType")); -+ final String facing = getProperty(this.getBlock(position), "facing"); -+ final String state; -+ if (!"up".equals(facing) && !"down".equals(facing)) { -+ state = typeString + facing; -+ } else { -+ state = typeString + tile.getInt("Rot"); -+ } -+ -+ tile.remove("SkullType"); -+ tile.remove("facing"); -+ tile.remove("Rot"); -+ -+ this.setBlock(position, SKULL_MAP.getOrDefault(state, SKULL_MAP.get("0north"))); -+ } -+ break; -+ } -+ case 175: { // sunflower -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType blockState = this.getBlock(position); -+ if (!"upper".equals(getProperty(blockState, "half"))) { -+ continue; -+ } -+ -+ final MapType blockStateBelow = this.getBlock(relative(position, Direction.DOWN)); -+ final String nameBelow = getName(blockStateBelow); -+ switch (nameBelow) { -+ case "minecraft:sunflower": -+ this.setBlock(position, UPPER_SUNFLOWER); -+ break; -+ case "minecraft:lilac": -+ this.setBlock(position, UPPER_LILAC); -+ break; -+ case "minecraft:tall_grass": -+ this.setBlock(position, UPPER_TALL_GRASS); -+ break; -+ case "minecraft:large_fern": -+ this.setBlock(position, UPPER_LARGE_FERN); -+ break; -+ case "minecraft:rose_bush": -+ this.setBlock(position, UPPER_ROSE_BUSH); -+ break; -+ case "minecraft:peony": -+ this.setBlock(position, UPPER_PEONY); -+ break; -+ } -+ } -+ break; -+ } -+ case 176: // free standing banner -+ case 177: { // wall mounted banner -+ while (positionIterator.hasNext()) { -+ final int position = positionIterator.nextInt() | yIndex; -+ final MapType tile = this.getBlockEntity(position); -+ -+ if (tile == null) { -+ continue; -+ } -+ -+ final MapType blockState = this.getBlock(position); -+ -+ final int base = tile.getInt("Base"); -+ if (base != 15 && base >= 0 && base < 16) { -+ final String state = getProperty(blockState, fixEntry.getIntKey() == 176 ? "rotation" : "facing") + "_" + base; -+ final MapType update = BANNER_BLOCK_MAP.get(state); -+ if (update != null) { -+ this.setBlock(position, update); -+ } -+ } -+ } -+ break; -+ } -+ } -+ } -+ } -+ } -+ -+ private MapType getBlockEntity(final int index) { -+ return this.tileEntities.get(index); -+ } -+ -+ private MapType removeBlockEntity(final int index) { -+ return this.tileEntities.remove(index); -+ } -+ -+ public static int relative(final int index, final Direction direction) { -+ switch (direction.getAxis()) { -+ case X: -+ int j = (index & 15) + direction.getAxisDirection().getStep(); -+ return j >= 0 && j <= 15 ? index & -16 | j : -1; -+ case Y: -+ int k = (index >> 8) + direction.getAxisDirection().getStep(); -+ return k >= 0 && k <= 255 ? index & 255 | k << 8 : -1; -+ case Z: -+ int l = (index >> 4 & 15) + direction.getAxisDirection().getStep(); -+ return l >= 0 && l <= 15 ? index & -241 | l << 4 : -1; -+ default: -+ return -1; -+ } -+ } -+ -+ private void setBlock(final int index, final MapType blockState) { -+ if (index >= 0 && index <= 65535) { -+ final Section section = this.getSection(index); -+ if (section != null) { -+ section.setBlock(index & 4095, blockState); -+ } -+ } -+ } -+ -+ private Section getSection(final int index) { -+ final int y = index >> 12; -+ return y < this.sections.length ? this.sections[y] : null; -+ } -+ -+ public MapType getBlock(int i) { -+ if (i >= 0 && i <= 65535) { -+ final Section section = this.getSection(i); -+ return section == null ? AIR : section.getBlock(i & 4095); -+ } else { -+ return AIR; -+ } -+ } -+ -+ public MapType writeBackToLevel() { -+ if (this.tileEntities.isEmpty()) { -+ this.level.remove("TileEntities"); -+ } else { -+ final ListType tileEntities = Types.NBT.createEmptyList(); -+ this.tileEntities.values().forEach(tileEntities::addMap); -+ this.level.setList("TileEntities", tileEntities); -+ } -+ -+ final MapType indices = Types.NBT.createEmptyMap(); -+ final ListType sections = Types.NBT.createEmptyList(); -+ for (final Section section : this.sections) { -+ if (section == null) { -+ continue; -+ } -+ -+ sections.addMap(section.writeBackToSection()); -+ indices.setInts(Integer.toString(section.y), Arrays.copyOf(section.update.elements(), section.update.size())); -+ } -+ -+ this.level.setList("Sections", sections); -+ -+ final MapType upgradeData = Types.NBT.createEmptyMap(); -+ upgradeData.setByte("Sides", (byte)this.sides); -+ upgradeData.setMap("Indices", indices); -+ -+ this.level.setMap("UpgradeData", upgradeData); -+ -+ return this.level; -+ } -+ } -+ -+ static class Section { -+ final Palette palette = new Palette(); -+ -+ static final class Palette extends Reference2IntOpenHashMap> { -+ -+ final ListType paletteStates = Types.NBT.createEmptyList(); -+ -+ private int find(final MapType k) { -+ if (((k) == (null))) -+ return containsNullKey ? n : -(n + 1); -+ MapType curr; -+ final Object[] key = this.key; -+ int pos; -+ // The starting point. -+ if (((curr = (MapType)key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(k))) & mask]) == (null))) -+ return -(pos + 1); -+ if (((k) == (curr))) -+ return pos; -+ // There's always an unused entry. -+ while (true) { -+ if (((curr = (MapType)key[pos = (pos + 1) & mask]) == (null))) -+ return -(pos + 1); -+ if (((k) == (curr))) -+ return pos; -+ } -+ } -+ -+ private void insert(final int pos, final MapType k, final int v) { -+ if (pos == n) -+ containsNullKey = true; -+ ((Object[])key)[pos] = k; -+ value[pos] = v; -+ if (size++ >= maxFill) -+ rehash(arraySize(size + 1, f)); -+ } -+ -+ private MapType[] byId = new MapType[4]; -+ private MapType last = null; -+ -+ public int getOrCreateId(final MapType k) { -+ if (k == this.last) { -+ return this.size - 1; -+ } -+ final int pos = find(k); -+ if (pos >= 0) { -+ return this.value[pos]; -+ } -+ -+ final int insert = this.size; -+ MapType inPalette = k; -+ -+ if ("%%FILTER_ME%%".equals(getName(k))) { -+ inPalette = AIR; -+ } -+ -+ if (insert >= this.byId.length) { -+ this.byId = Arrays.copyOf(this.byId, this.byId.length * 2); -+ this.byId[insert] = k; -+ } else { -+ this.byId[insert] = k; -+ } -+ this.paletteStates.addMap(inPalette); -+ -+ this.last = k; -+ -+ this.insert(-pos - 1, k, insert); -+ -+ return insert; -+ } -+ -+ } -+ -+ final MapType section; -+ final boolean hasData; -+ final Int2ObjectLinkedOpenHashMap toFix = new Int2ObjectLinkedOpenHashMap<>(); -+ final IntArrayList update = new IntArrayList(); -+ final int y; -+ final int[] buffer = new int[4096]; -+ -+ public Section(final MapType section) { -+ this.section = section; -+ this.y = section.getInt("Y"); -+ this.hasData = section.hasKey("Blocks", ObjectType.BYTE_ARRAY); -+ } -+ -+ public MapType getBlock(final int index) { -+ if (index >= 0 && index <= 4095) { -+ final MapType state = this.palette.byId[this.buffer[index]]; -+ return state == null ? AIR : state; -+ } else { -+ return AIR; -+ } -+ } -+ -+ public void setBlock(final int index, final MapType blockState) { -+ this.buffer[index] = this.palette.getOrCreateId(blockState); -+ } -+ -+ public int upgrade(int sides) { -+ if (!this.hasData) { -+ return sides; -+ } -+ -+ final byte[] blocks = this.section.getBytes("Blocks"); -+ final DataLayer data = DataLayer.getOrNull(this.section.getBytes("Data")); -+ final DataLayer add = DataLayer.getOrNull(this.section.getBytes("Add")); -+ -+ this.palette.getOrCreateId(AIR); -+ -+ for (int index = 0; index < 4096; ++index) { -+ final int x = index & 15; -+ final int z = index >> 4 & 15; -+ -+ int blockStateId = (blocks[index] & 255) << 4; -+ if (data != null) { -+ blockStateId |= data.get(index); -+ } -+ if (add != null) { -+ blockStateId |= add.get(index) << 12; -+ } -+ if (IDS_NEEDING_FIX[blockStateId >>> 4]) { -+ this.addFix(blockStateId >>> 4, index); -+ } -+ -+ if (VIRTUAL[blockStateId >>> 4]) { -+ final int additionalSides = getSideMask(x == 0, x == 15, z == 0, z == 15); -+ if (additionalSides == 0) { -+ this.update.add(index); -+ } else { -+ sides |= additionalSides; -+ } -+ } -+ -+ this.setBlock(index, HelperBlockFlatteningV1450.getNBTForId(blockStateId)); -+ } -+ -+ return sides; -+ } -+ -+ private void addFix(final int block, final int index) { -+ this.toFix.computeIfAbsent(block, (final int keyInMap) -> { -+ return new IntArrayList(); -+ }).add(index); -+ } -+ -+ // Note: modifies the current section and returns it. -+ public MapType writeBackToSection() { -+ if (!this.hasData) { -+ return this.section; -+ } -+ -+ this.section.setList("Palette", this.palette.paletteStates.copy()); // deep copy to ensure palette compound tags are NOT shared -+ -+ final int bitSize = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); -+ final PackedBitStorage packedIds = new PackedBitStorage(bitSize, 4096); -+ -+ for(int index = 0; index < this.buffer.length; ++index) { -+ packedIds.set(index, this.buffer[index]); -+ } -+ -+ this.section.setLongs("BlockStates", packedIds.getRaw()); -+ -+ this.section.remove("Blocks"); -+ this.section.remove("Data"); -+ this.section.remove("Add"); -+ -+ return this.section; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..06c075e643415d98b73734b749d9043091ebf9e5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterAbstractEntityRename.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractEntityRename { -+ -+ private ConverterAbstractEntityRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(version) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ final String converted = renamer.apply(id); -+ -+ if (converted != null) { -+ data.setString("id", converted); -+ } -+ -+ return null; -+ } -+ }); -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ENTITY_NAME, renamer); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java -new file mode 100644 -index 0000000000000000000000000000000000000000..985af815e3c23ad7c8b774eac46a7202d3020234 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityToVariant.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.IntFunction; -+ -+public final class ConverterEntityToVariant extends DataConverter, MapType> { -+ -+ public final String path; -+ public final IntFunction conversion; -+ -+ public ConverterEntityToVariant(final int toVersion, final String path, final IntFunction conversion) { -+ super(toVersion); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ public ConverterEntityToVariant(final int toVersion, final int versionStep, final String path, final IntFunction conversion) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.conversion = conversion; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number value = data.getNumber(this.path); -+ if (value == null) { -+ // nothing to do, DFU does the same -+ return null; -+ } -+ -+ final String converted = this.conversion.apply(value.intValue()); -+ -+ if (converted == null) { -+ throw new NullPointerException("Conversion " + this.conversion + " cannot return null value!"); -+ } -+ -+ // DFU doesn't appear to remove the old field, so why should we? -+ -+ data.setString("variant", converted); -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ed5dcf6f8160742c07e23e98c85409209350a7d4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterEntityVariantRename.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.function.Function; -+ -+public final class ConverterEntityVariantRename extends DataConverter, MapType> { -+ -+ private final Function renamer; -+ -+ public ConverterEntityVariantRename(final int toVersion, final Function renamer) { -+ super(toVersion); -+ this.renamer = renamer; -+ } -+ -+ public ConverterEntityVariantRename(final int toVersion, final int versionStep, final Function renamer) { -+ super(toVersion, versionStep); -+ this.renamer = renamer; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String variant = data.getString("variant"); -+ -+ if (variant == null) { -+ return null; -+ } -+ -+ final String rename = this.renamer.apply(variant); -+ -+ if (rename != null) { -+ data.setString("variant", rename); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java -new file mode 100644 -index 0000000000000000000000000000000000000000..afad2d92f78d4727ff4440ad2778f018d5a2a609 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/entity/ConverterFlattenEntity.java -@@ -0,0 +1,371 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.entity; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class ConverterFlattenEntity extends DataConverter, MapType> { -+ -+ private static final Map BLOCK_NAME_TO_ID = new HashMap<>(); -+ static { -+ BLOCK_NAME_TO_ID.put("minecraft:air", 0); -+ BLOCK_NAME_TO_ID.put("minecraft:stone", 1); -+ BLOCK_NAME_TO_ID.put("minecraft:grass", 2); -+ BLOCK_NAME_TO_ID.put("minecraft:dirt", 3); -+ BLOCK_NAME_TO_ID.put("minecraft:cobblestone", 4); -+ BLOCK_NAME_TO_ID.put("minecraft:planks", 5); -+ BLOCK_NAME_TO_ID.put("minecraft:sapling", 6); -+ BLOCK_NAME_TO_ID.put("minecraft:bedrock", 7); -+ BLOCK_NAME_TO_ID.put("minecraft:flowing_water", 8); -+ BLOCK_NAME_TO_ID.put("minecraft:water", 9); -+ BLOCK_NAME_TO_ID.put("minecraft:flowing_lava", 10); -+ BLOCK_NAME_TO_ID.put("minecraft:lava", 11); -+ BLOCK_NAME_TO_ID.put("minecraft:sand", 12); -+ BLOCK_NAME_TO_ID.put("minecraft:gravel", 13); -+ BLOCK_NAME_TO_ID.put("minecraft:gold_ore", 14); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_ore", 15); -+ BLOCK_NAME_TO_ID.put("minecraft:coal_ore", 16); -+ BLOCK_NAME_TO_ID.put("minecraft:log", 17); -+ BLOCK_NAME_TO_ID.put("minecraft:leaves", 18); -+ BLOCK_NAME_TO_ID.put("minecraft:sponge", 19); -+ BLOCK_NAME_TO_ID.put("minecraft:glass", 20); -+ BLOCK_NAME_TO_ID.put("minecraft:lapis_ore", 21); -+ BLOCK_NAME_TO_ID.put("minecraft:lapis_block", 22); -+ BLOCK_NAME_TO_ID.put("minecraft:dispenser", 23); -+ BLOCK_NAME_TO_ID.put("minecraft:sandstone", 24); -+ BLOCK_NAME_TO_ID.put("minecraft:noteblock", 25); -+ BLOCK_NAME_TO_ID.put("minecraft:bed", 26); -+ BLOCK_NAME_TO_ID.put("minecraft:golden_rail", 27); -+ BLOCK_NAME_TO_ID.put("minecraft:detector_rail", 28); -+ BLOCK_NAME_TO_ID.put("minecraft:sticky_piston", 29); -+ BLOCK_NAME_TO_ID.put("minecraft:web", 30); -+ BLOCK_NAME_TO_ID.put("minecraft:tallgrass", 31); -+ BLOCK_NAME_TO_ID.put("minecraft:deadbush", 32); -+ BLOCK_NAME_TO_ID.put("minecraft:piston", 33); -+ BLOCK_NAME_TO_ID.put("minecraft:piston_head", 34); -+ BLOCK_NAME_TO_ID.put("minecraft:wool", 35); -+ BLOCK_NAME_TO_ID.put("minecraft:piston_extension", 36); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_flower", 37); -+ BLOCK_NAME_TO_ID.put("minecraft:red_flower", 38); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom", 39); -+ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom", 40); -+ BLOCK_NAME_TO_ID.put("minecraft:gold_block", 41); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_block", 42); -+ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab", 43); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_slab", 44); -+ BLOCK_NAME_TO_ID.put("minecraft:brick_block", 45); -+ BLOCK_NAME_TO_ID.put("minecraft:tnt", 46); -+ BLOCK_NAME_TO_ID.put("minecraft:bookshelf", 47); -+ BLOCK_NAME_TO_ID.put("minecraft:mossy_cobblestone", 48); -+ BLOCK_NAME_TO_ID.put("minecraft:obsidian", 49); -+ BLOCK_NAME_TO_ID.put("minecraft:torch", 50); -+ BLOCK_NAME_TO_ID.put("minecraft:fire", 51); -+ BLOCK_NAME_TO_ID.put("minecraft:mob_spawner", 52); -+ BLOCK_NAME_TO_ID.put("minecraft:oak_stairs", 53); -+ BLOCK_NAME_TO_ID.put("minecraft:chest", 54); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_wire", 55); -+ BLOCK_NAME_TO_ID.put("minecraft:diamond_ore", 56); -+ BLOCK_NAME_TO_ID.put("minecraft:diamond_block", 57); -+ BLOCK_NAME_TO_ID.put("minecraft:crafting_table", 58); -+ BLOCK_NAME_TO_ID.put("minecraft:wheat", 59); -+ BLOCK_NAME_TO_ID.put("minecraft:farmland", 60); -+ BLOCK_NAME_TO_ID.put("minecraft:furnace", 61); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_furnace", 62); -+ BLOCK_NAME_TO_ID.put("minecraft:standing_sign", 63); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_door", 64); -+ BLOCK_NAME_TO_ID.put("minecraft:ladder", 65); -+ BLOCK_NAME_TO_ID.put("minecraft:rail", 66); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_stairs", 67); -+ BLOCK_NAME_TO_ID.put("minecraft:wall_sign", 68); -+ BLOCK_NAME_TO_ID.put("minecraft:lever", 69); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_pressure_plate", 70); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_door", 71); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_pressure_plate", 72); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_ore", 73); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_ore", 74); -+ BLOCK_NAME_TO_ID.put("minecraft:unlit_redstone_torch", 75); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_torch", 76); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_button", 77); -+ BLOCK_NAME_TO_ID.put("minecraft:snow_layer", 78); -+ BLOCK_NAME_TO_ID.put("minecraft:ice", 79); -+ BLOCK_NAME_TO_ID.put("minecraft:snow", 80); -+ BLOCK_NAME_TO_ID.put("minecraft:cactus", 81); -+ BLOCK_NAME_TO_ID.put("minecraft:clay", 82); -+ BLOCK_NAME_TO_ID.put("minecraft:reeds", 83); -+ BLOCK_NAME_TO_ID.put("minecraft:jukebox", 84); -+ BLOCK_NAME_TO_ID.put("minecraft:fence", 85); -+ BLOCK_NAME_TO_ID.put("minecraft:pumpkin", 86); -+ BLOCK_NAME_TO_ID.put("minecraft:netherrack", 87); -+ BLOCK_NAME_TO_ID.put("minecraft:soul_sand", 88); -+ BLOCK_NAME_TO_ID.put("minecraft:glowstone", 89); -+ BLOCK_NAME_TO_ID.put("minecraft:portal", 90); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_pumpkin", 91); -+ BLOCK_NAME_TO_ID.put("minecraft:cake", 92); -+ BLOCK_NAME_TO_ID.put("minecraft:unpowered_repeater", 93); -+ BLOCK_NAME_TO_ID.put("minecraft:powered_repeater", 94); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_glass", 95); -+ BLOCK_NAME_TO_ID.put("minecraft:trapdoor", 96); -+ BLOCK_NAME_TO_ID.put("minecraft:monster_egg", 97); -+ BLOCK_NAME_TO_ID.put("minecraft:stonebrick", 98); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_mushroom_block", 99); -+ BLOCK_NAME_TO_ID.put("minecraft:red_mushroom_block", 100); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_bars", 101); -+ BLOCK_NAME_TO_ID.put("minecraft:glass_pane", 102); -+ BLOCK_NAME_TO_ID.put("minecraft:melon_block", 103); -+ BLOCK_NAME_TO_ID.put("minecraft:pumpkin_stem", 104); -+ BLOCK_NAME_TO_ID.put("minecraft:melon_stem", 105); -+ BLOCK_NAME_TO_ID.put("minecraft:vine", 106); -+ BLOCK_NAME_TO_ID.put("minecraft:fence_gate", 107); -+ BLOCK_NAME_TO_ID.put("minecraft:brick_stairs", 108); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_brick_stairs", 109); -+ BLOCK_NAME_TO_ID.put("minecraft:mycelium", 110); -+ BLOCK_NAME_TO_ID.put("minecraft:waterlily", 111); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick", 112); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_fence", 113); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_brick_stairs", 114); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_wart", 115); -+ BLOCK_NAME_TO_ID.put("minecraft:enchanting_table", 116); -+ BLOCK_NAME_TO_ID.put("minecraft:brewing_stand", 117); -+ BLOCK_NAME_TO_ID.put("minecraft:cauldron", 118); -+ BLOCK_NAME_TO_ID.put("minecraft:end_portal", 119); -+ BLOCK_NAME_TO_ID.put("minecraft:end_portal_frame", 120); -+ BLOCK_NAME_TO_ID.put("minecraft:end_stone", 121); -+ BLOCK_NAME_TO_ID.put("minecraft:dragon_egg", 122); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_lamp", 123); -+ BLOCK_NAME_TO_ID.put("minecraft:lit_redstone_lamp", 124); -+ BLOCK_NAME_TO_ID.put("minecraft:double_wooden_slab", 125); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_slab", 126); -+ BLOCK_NAME_TO_ID.put("minecraft:cocoa", 127); -+ BLOCK_NAME_TO_ID.put("minecraft:sandstone_stairs", 128); -+ BLOCK_NAME_TO_ID.put("minecraft:emerald_ore", 129); -+ BLOCK_NAME_TO_ID.put("minecraft:ender_chest", 130); -+ BLOCK_NAME_TO_ID.put("minecraft:tripwire_hook", 131); -+ BLOCK_NAME_TO_ID.put("minecraft:tripwire", 132); -+ BLOCK_NAME_TO_ID.put("minecraft:emerald_block", 133); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_stairs", 134); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_stairs", 135); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_stairs", 136); -+ BLOCK_NAME_TO_ID.put("minecraft:command_block", 137); -+ BLOCK_NAME_TO_ID.put("minecraft:beacon", 138); -+ BLOCK_NAME_TO_ID.put("minecraft:cobblestone_wall", 139); -+ BLOCK_NAME_TO_ID.put("minecraft:flower_pot", 140); -+ BLOCK_NAME_TO_ID.put("minecraft:carrots", 141); -+ BLOCK_NAME_TO_ID.put("minecraft:potatoes", 142); -+ BLOCK_NAME_TO_ID.put("minecraft:wooden_button", 143); -+ BLOCK_NAME_TO_ID.put("minecraft:skull", 144); -+ BLOCK_NAME_TO_ID.put("minecraft:anvil", 145); -+ BLOCK_NAME_TO_ID.put("minecraft:trapped_chest", 146); -+ BLOCK_NAME_TO_ID.put("minecraft:light_weighted_pressure_plate", 147); -+ BLOCK_NAME_TO_ID.put("minecraft:heavy_weighted_pressure_plate", 148); -+ BLOCK_NAME_TO_ID.put("minecraft:unpowered_comparator", 149); -+ BLOCK_NAME_TO_ID.put("minecraft:powered_comparator", 150); -+ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector", 151); -+ BLOCK_NAME_TO_ID.put("minecraft:redstone_block", 152); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_ore", 153); -+ BLOCK_NAME_TO_ID.put("minecraft:hopper", 154); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_block", 155); -+ BLOCK_NAME_TO_ID.put("minecraft:quartz_stairs", 156); -+ BLOCK_NAME_TO_ID.put("minecraft:activator_rail", 157); -+ BLOCK_NAME_TO_ID.put("minecraft:dropper", 158); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_hardened_clay", 159); -+ BLOCK_NAME_TO_ID.put("minecraft:stained_glass_pane", 160); -+ BLOCK_NAME_TO_ID.put("minecraft:leaves2", 161); -+ BLOCK_NAME_TO_ID.put("minecraft:log2", 162); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_stairs", 163); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_stairs", 164); -+ BLOCK_NAME_TO_ID.put("minecraft:slime", 165); -+ BLOCK_NAME_TO_ID.put("minecraft:barrier", 166); -+ BLOCK_NAME_TO_ID.put("minecraft:iron_trapdoor", 167); -+ BLOCK_NAME_TO_ID.put("minecraft:prismarine", 168); -+ BLOCK_NAME_TO_ID.put("minecraft:sea_lantern", 169); -+ BLOCK_NAME_TO_ID.put("minecraft:hay_block", 170); -+ BLOCK_NAME_TO_ID.put("minecraft:carpet", 171); -+ BLOCK_NAME_TO_ID.put("minecraft:hardened_clay", 172); -+ BLOCK_NAME_TO_ID.put("minecraft:coal_block", 173); -+ BLOCK_NAME_TO_ID.put("minecraft:packed_ice", 174); -+ BLOCK_NAME_TO_ID.put("minecraft:double_plant", 175); -+ BLOCK_NAME_TO_ID.put("minecraft:standing_banner", 176); -+ BLOCK_NAME_TO_ID.put("minecraft:wall_banner", 177); -+ BLOCK_NAME_TO_ID.put("minecraft:daylight_detector_inverted", 178); -+ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone", 179); -+ BLOCK_NAME_TO_ID.put("minecraft:red_sandstone_stairs", 180); -+ BLOCK_NAME_TO_ID.put("minecraft:double_stone_slab2", 181); -+ BLOCK_NAME_TO_ID.put("minecraft:stone_slab2", 182); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence_gate", 183); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_fence_gate", 184); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence_gate", 185); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence_gate", 186); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence_gate", 187); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_fence", 188); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_fence", 189); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_fence", 190); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_fence", 191); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_fence", 192); -+ BLOCK_NAME_TO_ID.put("minecraft:spruce_door", 193); -+ BLOCK_NAME_TO_ID.put("minecraft:birch_door", 194); -+ BLOCK_NAME_TO_ID.put("minecraft:jungle_door", 195); -+ BLOCK_NAME_TO_ID.put("minecraft:acacia_door", 196); -+ BLOCK_NAME_TO_ID.put("minecraft:dark_oak_door", 197); -+ BLOCK_NAME_TO_ID.put("minecraft:end_rod", 198); -+ BLOCK_NAME_TO_ID.put("minecraft:chorus_plant", 199); -+ BLOCK_NAME_TO_ID.put("minecraft:chorus_flower", 200); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_block", 201); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_pillar", 202); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_stairs", 203); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_double_slab", 204); -+ BLOCK_NAME_TO_ID.put("minecraft:purpur_slab", 205); -+ BLOCK_NAME_TO_ID.put("minecraft:end_bricks", 206); -+ BLOCK_NAME_TO_ID.put("minecraft:beetroots", 207); -+ BLOCK_NAME_TO_ID.put("minecraft:grass_path", 208); -+ BLOCK_NAME_TO_ID.put("minecraft:end_gateway", 209); -+ BLOCK_NAME_TO_ID.put("minecraft:repeating_command_block", 210); -+ BLOCK_NAME_TO_ID.put("minecraft:chain_command_block", 211); -+ BLOCK_NAME_TO_ID.put("minecraft:frosted_ice", 212); -+ BLOCK_NAME_TO_ID.put("minecraft:magma", 213); -+ BLOCK_NAME_TO_ID.put("minecraft:nether_wart_block", 214); -+ BLOCK_NAME_TO_ID.put("minecraft:red_nether_brick", 215); -+ BLOCK_NAME_TO_ID.put("minecraft:bone_block", 216); -+ BLOCK_NAME_TO_ID.put("minecraft:structure_void", 217); -+ BLOCK_NAME_TO_ID.put("minecraft:observer", 218); -+ BLOCK_NAME_TO_ID.put("minecraft:white_shulker_box", 219); -+ BLOCK_NAME_TO_ID.put("minecraft:orange_shulker_box", 220); -+ BLOCK_NAME_TO_ID.put("minecraft:magenta_shulker_box", 221); -+ BLOCK_NAME_TO_ID.put("minecraft:light_blue_shulker_box", 222); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_shulker_box", 223); -+ BLOCK_NAME_TO_ID.put("minecraft:lime_shulker_box", 224); -+ BLOCK_NAME_TO_ID.put("minecraft:pink_shulker_box", 225); -+ BLOCK_NAME_TO_ID.put("minecraft:gray_shulker_box", 226); -+ BLOCK_NAME_TO_ID.put("minecraft:silver_shulker_box", 227); -+ BLOCK_NAME_TO_ID.put("minecraft:cyan_shulker_box", 228); -+ BLOCK_NAME_TO_ID.put("minecraft:purple_shulker_box", 229); -+ BLOCK_NAME_TO_ID.put("minecraft:blue_shulker_box", 230); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_shulker_box", 231); -+ BLOCK_NAME_TO_ID.put("minecraft:green_shulker_box", 232); -+ BLOCK_NAME_TO_ID.put("minecraft:red_shulker_box", 233); -+ BLOCK_NAME_TO_ID.put("minecraft:black_shulker_box", 234); -+ BLOCK_NAME_TO_ID.put("minecraft:white_glazed_terracotta", 235); -+ BLOCK_NAME_TO_ID.put("minecraft:orange_glazed_terracotta", 236); -+ BLOCK_NAME_TO_ID.put("minecraft:magenta_glazed_terracotta", 237); -+ BLOCK_NAME_TO_ID.put("minecraft:light_blue_glazed_terracotta", 238); -+ BLOCK_NAME_TO_ID.put("minecraft:yellow_glazed_terracotta", 239); -+ BLOCK_NAME_TO_ID.put("minecraft:lime_glazed_terracotta", 240); -+ BLOCK_NAME_TO_ID.put("minecraft:pink_glazed_terracotta", 241); -+ BLOCK_NAME_TO_ID.put("minecraft:gray_glazed_terracotta", 242); -+ BLOCK_NAME_TO_ID.put("minecraft:silver_glazed_terracotta", 243); -+ BLOCK_NAME_TO_ID.put("minecraft:cyan_glazed_terracotta", 244); -+ BLOCK_NAME_TO_ID.put("minecraft:purple_glazed_terracotta", 245); -+ BLOCK_NAME_TO_ID.put("minecraft:blue_glazed_terracotta", 246); -+ BLOCK_NAME_TO_ID.put("minecraft:brown_glazed_terracotta", 247); -+ BLOCK_NAME_TO_ID.put("minecraft:green_glazed_terracotta", 248); -+ BLOCK_NAME_TO_ID.put("minecraft:red_glazed_terracotta", 249); -+ BLOCK_NAME_TO_ID.put("minecraft:black_glazed_terracotta", 250); -+ BLOCK_NAME_TO_ID.put("minecraft:concrete", 251); -+ BLOCK_NAME_TO_ID.put("minecraft:concrete_powder", 252); -+ BLOCK_NAME_TO_ID.put("minecraft:structure_block", 255); -+ } -+ -+ protected static final int VERSION = MCVersions.V17W47A; -+ -+ protected final String[] paths; -+ -+ public ConverterFlattenEntity(final String... paths) { -+ super(VERSION, 3); -+ this.paths = paths; -+ } -+ -+ private static void register(final String id, final String... paths) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, new ConverterFlattenEntity(paths)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:falling_block", new DataConverter<>(VERSION, 3) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int blockId; -+ if (data.hasKey("Block")) { -+ final Number id = data.getNumber("Block"); -+ if (id != null) { -+ blockId = id.intValue(); -+ } else { -+ blockId = getBlockId(data.getString("Block")); -+ } -+ } else { -+ final Number tileId = data.getNumber("TileID"); -+ if (tileId != null) { -+ blockId = tileId.intValue(); -+ } else { -+ blockId = data.getByte("Tile") & 255; -+ } -+ } -+ -+ final int blockData = data.getInt("Data") & 15; -+ -+ data.remove("Block"); // from type update -+ data.remove("Data"); -+ data.remove("TileID"); -+ data.remove("Tile"); -+ -+ // key is from type update -+ data.setMap("BlockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+ }); -+ register("minecraft:enderman", "carried", "carriedData", "carriedBlockState"); -+ register("minecraft:arrow", "inTile", "inData", "inBlockState"); -+ register("minecraft:spectral_arrow", "inTile", "inData", "inBlockState"); -+ register("minecraft:egg", "inTile"); -+ register("minecraft:ender_pearl", "inTile"); -+ register("minecraft:fireball", "inTile"); -+ register("minecraft:potion", "inTile"); -+ register("minecraft:small_fireball", "inTile"); -+ register("minecraft:snowball", "inTile"); -+ register("minecraft:wither_skull", "inTile"); -+ register("minecraft:xp_bottle", "inTile"); -+ register("minecraft:commandblock_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:chest_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:furnace_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:tnt_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:hopper_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ register("minecraft:spawner_minecart", "DisplayTile", "DisplayData", "DisplayState"); -+ } -+ -+ public static int getBlockId(final String block) { -+ final Integer ret = BLOCK_NAME_TO_ID.get(block); -+ return ret == null ? 0 : ret.intValue(); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (this.paths.length == 1) { -+ data.remove(this.paths[0]); -+ return null; -+ } -+ final String idPath = this.paths[0]; -+ final String dataPath = this.paths[1]; -+ final String outputStatePath = this.paths[2]; -+ -+ final int blockId; -+ if (data.hasKey(idPath, ObjectType.NUMBER)) { -+ blockId = data.getInt(idPath); -+ } else { -+ blockId = getBlockId(data.getString(idPath)); -+ } -+ -+ final int blockData = data.getInt(dataPath) & 15; -+ -+ data.remove(idPath); // from type update -+ data.remove(dataPath); -+ -+ data.setMap(outputStatePath, HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ab607f946782cc483535564e86fa9753dd7897a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/AddFlagIfAbsent.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class AddFlagIfAbsent extends DataConverter, MapType> { -+ -+ public final String path; -+ public final boolean dfl; -+ -+ public AddFlagIfAbsent(final int toVersion, final String path, final boolean dfl) { -+ super(toVersion); -+ this.path = path; -+ this.dfl = dfl; -+ } -+ -+ public AddFlagIfAbsent(final int toVersion, final int versionStep, final String path, final boolean dfl) { -+ super(toVersion, versionStep); -+ this.path = path; -+ this.dfl = dfl; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey(this.path)) { -+ data.setBoolean(this.path, this.dfl); -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bc79670f47aaa413ea3e96ef6a32e14099ad8a58 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/ConverterAbstractStringValueTypeRename.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractStringValueTypeRename { -+ -+ private ConverterAbstractStringValueTypeRename() {} -+ -+ public static void register(final int version, final MCValueType type, final Function renamer) { -+ register(version, 0, type, renamer); -+ } -+ public static void register(final int version, final int subVersion, final MCValueType type, final Function renamer) { -+ type.addConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ final String ret = (data instanceof String) ? renamer.apply((String)data) : null; -+ return ret == data ? null : ret; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java -new file mode 100644 -index 0000000000000000000000000000000000000000..02ee521dc0f61f3f01d443c46c1066d1ecbeea7f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperBlockFlatteningV1450.java -@@ -0,0 +1,1830 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import net.minecraft.nbt.TagParser; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class HelperBlockFlatteningV1450 { -+ -+ protected static final MapType[] FLATTENED_BY_ID = new MapType[4096]; -+ protected static final MapType[] BLOCK_DEFAULTS = new MapType[4096]; -+ -+ private static final Object2IntOpenHashMap> ID_BY_OLD_NBT = new Object2IntOpenHashMap>(64, 0.7f) { -+ @Override -+ public int put(final MapType o, final int v) { -+ if (this.containsKey(o)) { -+ throw new RuntimeException("Already contains mapping for " + o); -+ } -+ -+ return super.put(o, v); -+ } -+ }; -+ static { -+ ID_BY_OLD_NBT.defaultReturnValue(-1); -+ } -+ -+ private static final Object2IntOpenHashMap ID_BY_OLD_NAME = new Object2IntOpenHashMap(64, 0.7f) { -+ @Override -+ public int put(final String o, final int v) { -+ if (this.containsKey(o)) { -+ throw new RuntimeException("Already contains mapping for " + o); -+ } -+ -+ return super.put(o, v); -+ } -+ }; -+ static { -+ ID_BY_OLD_NAME.defaultReturnValue(-1); -+ } -+ -+ // map used to ensure that each parsed block state contains no duplicates -+ protected static final Map, MapType> IDENTITY_ENSURE = new HashMap<>(); -+ -+ public static MapType parseTag(final String blockstate) { -+ try { -+ final MapType ret = new NBTMapType(TagParser.parseTag(blockstate.replace('\'', '"'))); -+ -+ synchronized (IDENTITY_ENSURE) { -+ final MapType identity = IDENTITY_ENSURE.putIfAbsent(ret, ret); -+ -+ return identity == null ? ret : identity; -+ } -+ -+ } catch (final Exception ex) { -+ throw new RuntimeException("Exception parsing " + blockstate, ex); -+ } -+ } -+ -+ private static void register(final int id, final String flattened, final String... preFlattenings) { -+ final MapType flattenedNBT = parseTag(flattened); -+ if (FLATTENED_BY_ID[id] != null) { -+ throw new RuntimeException("Mapping already exists for id " + id); -+ } -+ FLATTENED_BY_ID[id] = flattenedNBT; -+ -+ // it's important that we register ids from smallest to largest, so that -+ // the default is going to be correct -+ final int block = id >> 4; -+ if (BLOCK_DEFAULTS[block] == null) { -+ BLOCK_DEFAULTS[block] = flattenedNBT; -+ } -+ -+ for (final String preFlattening : preFlattenings) { -+ final MapType preFlatteningNBT = parseTag(preFlattening); -+ final String name = preFlatteningNBT.getString("Name"); -+ if (name == null) { -+ throw new RuntimeException("Name does not exist for pre flattenings for id " + id); -+ } -+ -+ // putIfAbsent so we default to the lowest id, which is going to be the block default -+ ID_BY_OLD_NAME.putIfAbsent(name, id); -+ ID_BY_OLD_NBT.put(preFlatteningNBT, id); -+ } -+ } -+ -+ private static void finalizeMaps() { -+ for(int i = 0; i < FLATTENED_BY_ID.length; ++i) { -+ if (FLATTENED_BY_ID[i] == null) { -+ FLATTENED_BY_ID[i] = BLOCK_DEFAULTS[i >> 4]; -+ } -+ } -+ } -+ -+ public static MapType flattenNBT(final MapType old) { -+ final int id = ID_BY_OLD_NBT.getInt(old); -+ final MapType ret = getNBTForIdRaw(id); -+ -+ return ret == null ? old : ret; -+ } -+ -+ public static String getNewBlockName(final String old) { -+ final int id = ID_BY_OLD_NAME.getInt(old); -+ final MapType ret = getNBTForIdRaw(id); -+ return ret == null ? old : ret.getString("Name"); -+ } -+ -+ public static String getNameForId(final int block) { -+ final MapType nbt = getNBTForIdRaw(block); -+ return nbt == null ? "minecraft:air" : nbt.getString("Name"); -+ } -+ -+ protected static MapType getNBTForIdRaw(final int block) { -+ return block >= 0 && block < FLATTENED_BY_ID.length ? FLATTENED_BY_ID[block] : null; -+ } -+ -+ public static MapType getNBTForId(final int block) { -+ MapType ret = getNBTForIdRaw(block); -+ return ret == null ? FLATTENED_BY_ID[0] : ret; -+ } -+ -+ private HelperBlockFlatteningV1450() {} -+ -+ static { -+ ID_BY_OLD_NBT.defaultReturnValue(-1); -+ register(0, "{Name:'minecraft:air'}", "{Name:'minecraft:air'}"); -+ register(16, "{Name:'minecraft:stone'}", "{Name:'minecraft:stone',Properties:{variant:'stone'}}"); -+ register(17, "{Name:'minecraft:granite'}", "{Name:'minecraft:stone',Properties:{variant:'granite'}}"); -+ register(18, "{Name:'minecraft:polished_granite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_granite'}}"); -+ register(19, "{Name:'minecraft:diorite'}", "{Name:'minecraft:stone',Properties:{variant:'diorite'}}"); -+ register(20, "{Name:'minecraft:polished_diorite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_diorite'}}"); -+ register(21, "{Name:'minecraft:andesite'}", "{Name:'minecraft:stone',Properties:{variant:'andesite'}}"); -+ register(22, "{Name:'minecraft:polished_andesite'}", "{Name:'minecraft:stone',Properties:{variant:'smooth_andesite'}}"); -+ register(32, "{Name:'minecraft:grass_block',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'false'}}", "{Name:'minecraft:grass',Properties:{snowy:'true'}}"); -+ register(48, "{Name:'minecraft:dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'dirt'}}"); -+ register(49, "{Name:'minecraft:coarse_dirt'}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'coarse_dirt'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'coarse_dirt'}}"); -+ register(50, "{Name:'minecraft:podzol',Properties:{snowy:'false'}}", "{Name:'minecraft:dirt',Properties:{snowy:'false',variant:'podzol'}}", "{Name:'minecraft:dirt',Properties:{snowy:'true',variant:'podzol'}}"); -+ register(64, "{Name:'minecraft:cobblestone'}", "{Name:'minecraft:cobblestone'}"); -+ register(80, "{Name:'minecraft:oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'oak'}}"); -+ register(81, "{Name:'minecraft:spruce_planks'}", "{Name:'minecraft:planks',Properties:{variant:'spruce'}}"); -+ register(82, "{Name:'minecraft:birch_planks'}", "{Name:'minecraft:planks',Properties:{variant:'birch'}}"); -+ register(83, "{Name:'minecraft:jungle_planks'}", "{Name:'minecraft:planks',Properties:{variant:'jungle'}}"); -+ register(84, "{Name:'minecraft:acacia_planks'}", "{Name:'minecraft:planks',Properties:{variant:'acacia'}}"); -+ register(85, "{Name:'minecraft:dark_oak_planks'}", "{Name:'minecraft:planks',Properties:{variant:'dark_oak'}}"); -+ register(96, "{Name:'minecraft:oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'oak'}}"); -+ register(97, "{Name:'minecraft:spruce_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'spruce'}}"); -+ register(98, "{Name:'minecraft:birch_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'birch'}}"); -+ register(99, "{Name:'minecraft:jungle_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'jungle'}}"); -+ register(100, "{Name:'minecraft:acacia_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'acacia'}}"); -+ register(101, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'0'}}", "{Name:'minecraft:sapling',Properties:{stage:'0',type:'dark_oak'}}"); -+ register(104, "{Name:'minecraft:oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'oak'}}"); -+ register(105, "{Name:'minecraft:spruce_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'spruce'}}"); -+ register(106, "{Name:'minecraft:birch_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'birch'}}"); -+ register(107, "{Name:'minecraft:jungle_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'jungle'}}"); -+ register(108, "{Name:'minecraft:acacia_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'acacia'}}"); -+ register(109, "{Name:'minecraft:dark_oak_sapling',Properties:{stage:'1'}}", "{Name:'minecraft:sapling',Properties:{stage:'1',type:'dark_oak'}}"); -+ register(112, "{Name:'minecraft:bedrock'}", "{Name:'minecraft:bedrock'}"); -+ register(128, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:flowing_water',Properties:{level:'0'}}"); -+ register(129, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:flowing_water',Properties:{level:'1'}}"); -+ register(130, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:flowing_water',Properties:{level:'2'}}"); -+ register(131, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:flowing_water',Properties:{level:'3'}}"); -+ register(132, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:flowing_water',Properties:{level:'4'}}"); -+ register(133, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:flowing_water',Properties:{level:'5'}}"); -+ register(134, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:flowing_water',Properties:{level:'6'}}"); -+ register(135, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:flowing_water',Properties:{level:'7'}}"); -+ register(136, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:flowing_water',Properties:{level:'8'}}"); -+ register(137, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:flowing_water',Properties:{level:'9'}}"); -+ register(138, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:flowing_water',Properties:{level:'10'}}"); -+ register(139, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:flowing_water',Properties:{level:'11'}}"); -+ register(140, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:flowing_water',Properties:{level:'12'}}"); -+ register(141, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:flowing_water',Properties:{level:'13'}}"); -+ register(142, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:flowing_water',Properties:{level:'14'}}"); -+ register(143, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:flowing_water',Properties:{level:'15'}}"); -+ register(144, "{Name:'minecraft:water',Properties:{level:'0'}}", "{Name:'minecraft:water',Properties:{level:'0'}}"); -+ register(145, "{Name:'minecraft:water',Properties:{level:'1'}}", "{Name:'minecraft:water',Properties:{level:'1'}}"); -+ register(146, "{Name:'minecraft:water',Properties:{level:'2'}}", "{Name:'minecraft:water',Properties:{level:'2'}}"); -+ register(147, "{Name:'minecraft:water',Properties:{level:'3'}}", "{Name:'minecraft:water',Properties:{level:'3'}}"); -+ register(148, "{Name:'minecraft:water',Properties:{level:'4'}}", "{Name:'minecraft:water',Properties:{level:'4'}}"); -+ register(149, "{Name:'minecraft:water',Properties:{level:'5'}}", "{Name:'minecraft:water',Properties:{level:'5'}}"); -+ register(150, "{Name:'minecraft:water',Properties:{level:'6'}}", "{Name:'minecraft:water',Properties:{level:'6'}}"); -+ register(151, "{Name:'minecraft:water',Properties:{level:'7'}}", "{Name:'minecraft:water',Properties:{level:'7'}}"); -+ register(152, "{Name:'minecraft:water',Properties:{level:'8'}}", "{Name:'minecraft:water',Properties:{level:'8'}}"); -+ register(153, "{Name:'minecraft:water',Properties:{level:'9'}}", "{Name:'minecraft:water',Properties:{level:'9'}}"); -+ register(154, "{Name:'minecraft:water',Properties:{level:'10'}}", "{Name:'minecraft:water',Properties:{level:'10'}}"); -+ register(155, "{Name:'minecraft:water',Properties:{level:'11'}}", "{Name:'minecraft:water',Properties:{level:'11'}}"); -+ register(156, "{Name:'minecraft:water',Properties:{level:'12'}}", "{Name:'minecraft:water',Properties:{level:'12'}}"); -+ register(157, "{Name:'minecraft:water',Properties:{level:'13'}}", "{Name:'minecraft:water',Properties:{level:'13'}}"); -+ register(158, "{Name:'minecraft:water',Properties:{level:'14'}}", "{Name:'minecraft:water',Properties:{level:'14'}}"); -+ register(159, "{Name:'minecraft:water',Properties:{level:'15'}}", "{Name:'minecraft:water',Properties:{level:'15'}}"); -+ register(160, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'0'}}"); -+ register(161, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'1'}}"); -+ register(162, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'2'}}"); -+ register(163, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'3'}}"); -+ register(164, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'4'}}"); -+ register(165, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'5'}}"); -+ register(166, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'6'}}"); -+ register(167, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'7'}}"); -+ register(168, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'8'}}"); -+ register(169, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'9'}}"); -+ register(170, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'10'}}"); -+ register(171, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'11'}}"); -+ register(172, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'12'}}"); -+ register(173, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'13'}}"); -+ register(174, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'14'}}"); -+ register(175, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:flowing_lava',Properties:{level:'15'}}"); -+ register(176, "{Name:'minecraft:lava',Properties:{level:'0'}}", "{Name:'minecraft:lava',Properties:{level:'0'}}"); -+ register(177, "{Name:'minecraft:lava',Properties:{level:'1'}}", "{Name:'minecraft:lava',Properties:{level:'1'}}"); -+ register(178, "{Name:'minecraft:lava',Properties:{level:'2'}}", "{Name:'minecraft:lava',Properties:{level:'2'}}"); -+ register(179, "{Name:'minecraft:lava',Properties:{level:'3'}}", "{Name:'minecraft:lava',Properties:{level:'3'}}"); -+ register(180, "{Name:'minecraft:lava',Properties:{level:'4'}}", "{Name:'minecraft:lava',Properties:{level:'4'}}"); -+ register(181, "{Name:'minecraft:lava',Properties:{level:'5'}}", "{Name:'minecraft:lava',Properties:{level:'5'}}"); -+ register(182, "{Name:'minecraft:lava',Properties:{level:'6'}}", "{Name:'minecraft:lava',Properties:{level:'6'}}"); -+ register(183, "{Name:'minecraft:lava',Properties:{level:'7'}}", "{Name:'minecraft:lava',Properties:{level:'7'}}"); -+ register(184, "{Name:'minecraft:lava',Properties:{level:'8'}}", "{Name:'minecraft:lava',Properties:{level:'8'}}"); -+ register(185, "{Name:'minecraft:lava',Properties:{level:'9'}}", "{Name:'minecraft:lava',Properties:{level:'9'}}"); -+ register(186, "{Name:'minecraft:lava',Properties:{level:'10'}}", "{Name:'minecraft:lava',Properties:{level:'10'}}"); -+ register(187, "{Name:'minecraft:lava',Properties:{level:'11'}}", "{Name:'minecraft:lava',Properties:{level:'11'}}"); -+ register(188, "{Name:'minecraft:lava',Properties:{level:'12'}}", "{Name:'minecraft:lava',Properties:{level:'12'}}"); -+ register(189, "{Name:'minecraft:lava',Properties:{level:'13'}}", "{Name:'minecraft:lava',Properties:{level:'13'}}"); -+ register(190, "{Name:'minecraft:lava',Properties:{level:'14'}}", "{Name:'minecraft:lava',Properties:{level:'14'}}"); -+ register(191, "{Name:'minecraft:lava',Properties:{level:'15'}}", "{Name:'minecraft:lava',Properties:{level:'15'}}"); -+ register(192, "{Name:'minecraft:sand'}", "{Name:'minecraft:sand',Properties:{variant:'sand'}}"); -+ register(193, "{Name:'minecraft:red_sand'}", "{Name:'minecraft:sand',Properties:{variant:'red_sand'}}"); -+ register(208, "{Name:'minecraft:gravel'}", "{Name:'minecraft:gravel'}"); -+ register(224, "{Name:'minecraft:gold_ore'}", "{Name:'minecraft:gold_ore'}"); -+ register(240, "{Name:'minecraft:iron_ore'}", "{Name:'minecraft:iron_ore'}"); -+ register(256, "{Name:'minecraft:coal_ore'}", "{Name:'minecraft:coal_ore'}"); -+ register(272, "{Name:'minecraft:oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'oak'}}"); -+ register(273, "{Name:'minecraft:spruce_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'spruce'}}"); -+ register(274, "{Name:'minecraft:birch_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'birch'}}"); -+ register(275, "{Name:'minecraft:jungle_log',Properties:{axis:'y'}}", "{Name:'minecraft:log',Properties:{axis:'y',variant:'jungle'}}"); -+ register(276, "{Name:'minecraft:oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'oak'}}"); -+ register(277, "{Name:'minecraft:spruce_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'spruce'}}"); -+ register(278, "{Name:'minecraft:birch_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'birch'}}"); -+ register(279, "{Name:'minecraft:jungle_log',Properties:{axis:'x'}}", "{Name:'minecraft:log',Properties:{axis:'x',variant:'jungle'}}"); -+ register(280, "{Name:'minecraft:oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'oak'}}"); -+ register(281, "{Name:'minecraft:spruce_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'spruce'}}"); -+ register(282, "{Name:'minecraft:birch_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'birch'}}"); -+ register(283, "{Name:'minecraft:jungle_log',Properties:{axis:'z'}}", "{Name:'minecraft:log',Properties:{axis:'z',variant:'jungle'}}"); -+ register(284, "{Name:'minecraft:oak_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'oak'}}"); -+ register(285, "{Name:'minecraft:spruce_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'spruce'}}"); -+ register(286, "{Name:'minecraft:birch_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'birch'}}"); -+ register(287, "{Name:'minecraft:jungle_bark'}", "{Name:'minecraft:log',Properties:{axis:'none',variant:'jungle'}}"); -+ register(288, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'oak'}}"); -+ register(289, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'spruce'}}"); -+ register(290, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'birch'}}"); -+ register(291, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'true',variant:'jungle'}}"); -+ register(292, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'oak'}}"); -+ register(293, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'spruce'}}"); -+ register(294, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'birch'}}"); -+ register(295, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'false',decayable:'false',variant:'jungle'}}"); -+ register(296, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'oak'}}"); -+ register(297, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'spruce'}}"); -+ register(298, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'birch'}}"); -+ register(299, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'true',variant:'jungle'}}"); -+ register(300, "{Name:'minecraft:oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'oak'}}"); -+ register(301, "{Name:'minecraft:spruce_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'spruce'}}"); -+ register(302, "{Name:'minecraft:birch_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'birch'}}"); -+ register(303, "{Name:'minecraft:jungle_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves',Properties:{check_decay:'true',decayable:'false',variant:'jungle'}}"); -+ register(304, "{Name:'minecraft:sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'false'}}"); -+ register(305, "{Name:'minecraft:wet_sponge'}", "{Name:'minecraft:sponge',Properties:{wet:'true'}}"); -+ register(320, "{Name:'minecraft:glass'}", "{Name:'minecraft:glass'}"); -+ register(336, "{Name:'minecraft:lapis_ore'}", "{Name:'minecraft:lapis_ore'}"); -+ register(352, "{Name:'minecraft:lapis_block'}", "{Name:'minecraft:lapis_block'}"); -+ register(368, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'false'}}"); -+ register(369, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'false'}}"); -+ register(370, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'false'}}"); -+ register(371, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'false'}}"); -+ register(372, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'false'}}"); -+ register(373, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'false'}}"); -+ register(376, "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'down',triggered:'true'}}"); -+ register(377, "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'up',triggered:'true'}}"); -+ register(378, "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'north',triggered:'true'}}"); -+ register(379, "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'south',triggered:'true'}}"); -+ register(380, "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'west',triggered:'true'}}"); -+ register(381, "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dispenser',Properties:{facing:'east',triggered:'true'}}"); -+ register(384, "{Name:'minecraft:sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'sandstone'}}"); -+ register(385, "{Name:'minecraft:chiseled_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'chiseled_sandstone'}}"); -+ register(386, "{Name:'minecraft:cut_sandstone'}", "{Name:'minecraft:sandstone',Properties:{type:'smooth_sandstone'}}"); -+ register(400, "{Name:'minecraft:note_block'}", "{Name:'minecraft:noteblock'}"); -+ register(416, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'foot'}}"); -+ register(417, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'foot'}}"); -+ register(418, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'foot'}}"); -+ register(419, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'foot'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'foot'}}"); -+ register(424, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'false',part:'head'}}"); -+ register(425, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'false',part:'head'}}"); -+ register(426, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'false',part:'head'}}"); -+ register(427, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'false',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'false',part:'head'}}"); -+ register(428, "{Name:'minecraft:red_bed',Properties:{facing:'south',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'south',occupied:'true',part:'head'}}"); -+ register(429, "{Name:'minecraft:red_bed',Properties:{facing:'west',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'west',occupied:'true',part:'head'}}"); -+ register(430, "{Name:'minecraft:red_bed',Properties:{facing:'north',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'north',occupied:'true',part:'head'}}"); -+ register(431, "{Name:'minecraft:red_bed',Properties:{facing:'east',occupied:'true',part:'head'}}", "{Name:'minecraft:bed',Properties:{facing:'east',occupied:'true',part:'head'}}"); -+ register(432, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(433, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(434, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(435, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(436, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(437, "{Name:'minecraft:powered_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(440, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(441, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(442, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(443, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(444, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(445, "{Name:'minecraft:powered_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:golden_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(448, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(449, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(450, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(451, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(452, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(453, "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(456, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(457, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(458, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(459, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(460, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(461, "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:detector_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(464, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'down'}}"); -+ register(465, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'up'}}"); -+ register(466, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'north'}}"); -+ register(467, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'south'}}"); -+ register(468, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'west'}}"); -+ register(469, "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'false',facing:'east'}}"); -+ register(472, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'down'}}"); -+ register(473, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'up'}}"); -+ register(474, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'north'}}"); -+ register(475, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'south'}}"); -+ register(476, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'west'}}"); -+ register(477, "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:sticky_piston',Properties:{extended:'true',facing:'east'}}"); -+ register(480, "{Name:'minecraft:cobweb'}", "{Name:'minecraft:web'}"); -+ register(496, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:tallgrass',Properties:{type:'dead_bush'}}"); -+ register(497, "{Name:'minecraft:grass'}", "{Name:'minecraft:tallgrass',Properties:{type:'tall_grass'}}"); -+ register(498, "{Name:'minecraft:fern'}", "{Name:'minecraft:tallgrass',Properties:{type:'fern'}}"); -+ register(512, "{Name:'minecraft:dead_bush'}", "{Name:'minecraft:deadbush'}"); -+ register(528, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'down'}}"); -+ register(529, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'up'}}"); -+ register(530, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'north'}}"); -+ register(531, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'south'}}"); -+ register(532, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'west'}}"); -+ register(533, "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'false',facing:'east'}}"); -+ register(536, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'down'}}"); -+ register(537, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'up'}}"); -+ register(538, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'north'}}"); -+ register(539, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'south'}}"); -+ register(540, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'west'}}"); -+ register(541, "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}", "{Name:'minecraft:piston',Properties:{extended:'true',facing:'east'}}"); -+ register(544, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'normal'}}"); -+ register(545, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'normal'}}"); -+ register(546, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'normal'}}"); -+ register(547, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'normal'}}"); -+ register(548, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'normal'}}"); -+ register(549, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'normal'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'normal'}}"); -+ register(552, "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'down',short:'true',type:'sticky'}}"); -+ register(553, "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'up',short:'true',type:'sticky'}}"); -+ register(554, "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'north',short:'true',type:'sticky'}}"); -+ register(555, "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'south',short:'true',type:'sticky'}}"); -+ register(556, "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'west',short:'true',type:'sticky'}}"); -+ register(557, "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'false',type:'sticky'}}", "{Name:'minecraft:piston_head',Properties:{facing:'east',short:'true',type:'sticky'}}"); -+ register(560, "{Name:'minecraft:white_wool'}", "{Name:'minecraft:wool',Properties:{color:'white'}}"); -+ register(561, "{Name:'minecraft:orange_wool'}", "{Name:'minecraft:wool',Properties:{color:'orange'}}"); -+ register(562, "{Name:'minecraft:magenta_wool'}", "{Name:'minecraft:wool',Properties:{color:'magenta'}}"); -+ register(563, "{Name:'minecraft:light_blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'light_blue'}}"); -+ register(564, "{Name:'minecraft:yellow_wool'}", "{Name:'minecraft:wool',Properties:{color:'yellow'}}"); -+ register(565, "{Name:'minecraft:lime_wool'}", "{Name:'minecraft:wool',Properties:{color:'lime'}}"); -+ register(566, "{Name:'minecraft:pink_wool'}", "{Name:'minecraft:wool',Properties:{color:'pink'}}"); -+ register(567, "{Name:'minecraft:gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'gray'}}"); -+ register(568, "{Name:'minecraft:light_gray_wool'}", "{Name:'minecraft:wool',Properties:{color:'silver'}}"); -+ register(569, "{Name:'minecraft:cyan_wool'}", "{Name:'minecraft:wool',Properties:{color:'cyan'}}"); -+ register(570, "{Name:'minecraft:purple_wool'}", "{Name:'minecraft:wool',Properties:{color:'purple'}}"); -+ register(571, "{Name:'minecraft:blue_wool'}", "{Name:'minecraft:wool',Properties:{color:'blue'}}"); -+ register(572, "{Name:'minecraft:brown_wool'}", "{Name:'minecraft:wool',Properties:{color:'brown'}}"); -+ register(573, "{Name:'minecraft:green_wool'}", "{Name:'minecraft:wool',Properties:{color:'green'}}"); -+ register(574, "{Name:'minecraft:red_wool'}", "{Name:'minecraft:wool',Properties:{color:'red'}}"); -+ register(575, "{Name:'minecraft:black_wool'}", "{Name:'minecraft:wool',Properties:{color:'black'}}"); -+ register(576, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'normal'}}"); -+ register(577, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'normal'}}"); -+ register(578, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'normal'}}"); -+ register(579, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'normal'}}"); -+ register(580, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'normal'}}"); -+ register(581, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'normal'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'normal'}}"); -+ register(584, "{Name:'minecraft:moving_piston',Properties:{facing:'down',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'down',type:'sticky'}}"); -+ register(585, "{Name:'minecraft:moving_piston',Properties:{facing:'up',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'up',type:'sticky'}}"); -+ register(586, "{Name:'minecraft:moving_piston',Properties:{facing:'north',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'north',type:'sticky'}}"); -+ register(587, "{Name:'minecraft:moving_piston',Properties:{facing:'south',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'south',type:'sticky'}}"); -+ register(588, "{Name:'minecraft:moving_piston',Properties:{facing:'west',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'west',type:'sticky'}}"); -+ register(589, "{Name:'minecraft:moving_piston',Properties:{facing:'east',type:'sticky'}}", "{Name:'minecraft:piston_extension',Properties:{facing:'east',type:'sticky'}}"); -+ register(592, "{Name:'minecraft:dandelion'}", "{Name:'minecraft:yellow_flower',Properties:{type:'dandelion'}}"); -+ register(608, "{Name:'minecraft:poppy'}", "{Name:'minecraft:red_flower',Properties:{type:'poppy'}}"); -+ register(609, "{Name:'minecraft:blue_orchid'}", "{Name:'minecraft:red_flower',Properties:{type:'blue_orchid'}}"); -+ register(610, "{Name:'minecraft:allium'}", "{Name:'minecraft:red_flower',Properties:{type:'allium'}}"); -+ register(611, "{Name:'minecraft:azure_bluet'}", "{Name:'minecraft:red_flower',Properties:{type:'houstonia'}}"); -+ register(612, "{Name:'minecraft:red_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'red_tulip'}}"); -+ register(613, "{Name:'minecraft:orange_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'orange_tulip'}}"); -+ register(614, "{Name:'minecraft:white_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'white_tulip'}}"); -+ register(615, "{Name:'minecraft:pink_tulip'}", "{Name:'minecraft:red_flower',Properties:{type:'pink_tulip'}}"); -+ register(616, "{Name:'minecraft:oxeye_daisy'}", "{Name:'minecraft:red_flower',Properties:{type:'oxeye_daisy'}}"); -+ register(624, "{Name:'minecraft:brown_mushroom'}", "{Name:'minecraft:brown_mushroom'}"); -+ register(640, "{Name:'minecraft:red_mushroom'}", "{Name:'minecraft:red_mushroom'}"); -+ register(656, "{Name:'minecraft:gold_block'}", "{Name:'minecraft:gold_block'}"); -+ register(672, "{Name:'minecraft:iron_block'}", "{Name:'minecraft:iron_block'}"); -+ register(688, "{Name:'minecraft:stone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone'}}"); -+ register(689, "{Name:'minecraft:sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'sandstone'}}"); -+ register(690, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'wood_old'}}"); -+ register(691, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'cobblestone'}}"); -+ register(692, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'brick'}}"); -+ register(693, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'stone_brick'}}"); -+ register(694, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'nether_brick'}}"); -+ register(695, "{Name:'minecraft:quartz_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'false',variant:'quartz'}}"); -+ register(696, "{Name:'minecraft:smooth_stone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone'}}"); -+ register(697, "{Name:'minecraft:smooth_sandstone'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'sandstone'}}"); -+ register(698, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'wood_old'}}"); -+ register(699, "{Name:'minecraft:cobblestone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'cobblestone'}}"); -+ register(700, "{Name:'minecraft:brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'brick'}}"); -+ register(701, "{Name:'minecraft:stone_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'stone_brick'}}"); -+ register(702, "{Name:'minecraft:nether_brick_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'nether_brick'}}"); -+ register(703, "{Name:'minecraft:smooth_quartz'}", "{Name:'minecraft:double_stone_slab',Properties:{seamless:'true',variant:'quartz'}}"); -+ register(704, "{Name:'minecraft:stone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone'}}"); -+ register(705, "{Name:'minecraft:sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'sandstone'}}"); -+ register(706, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'wood_old'}}"); -+ register(707, "{Name:'minecraft:cobblestone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'cobblestone'}}"); -+ register(708, "{Name:'minecraft:brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'brick'}}"); -+ register(709, "{Name:'minecraft:stone_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'stone_brick'}}"); -+ register(710, "{Name:'minecraft:nether_brick_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'nether_brick'}}"); -+ register(711, "{Name:'minecraft:quartz_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab',Properties:{half:'bottom',variant:'quartz'}}"); -+ register(712, "{Name:'minecraft:stone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone'}}"); -+ register(713, "{Name:'minecraft:sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'sandstone'}}"); -+ register(714, "{Name:'minecraft:petrified_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'wood_old'}}"); -+ register(715, "{Name:'minecraft:cobblestone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'cobblestone'}}"); -+ register(716, "{Name:'minecraft:brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'brick'}}"); -+ register(717, "{Name:'minecraft:stone_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'stone_brick'}}"); -+ register(718, "{Name:'minecraft:nether_brick_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'nether_brick'}}"); -+ register(719, "{Name:'minecraft:quartz_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab',Properties:{half:'top',variant:'quartz'}}"); -+ register(720, "{Name:'minecraft:bricks'}", "{Name:'minecraft:brick_block'}"); -+ register(736, "{Name:'minecraft:tnt',Properties:{unstable:'false'}}", "{Name:'minecraft:tnt',Properties:{explode:'false'}}"); -+ register(737, "{Name:'minecraft:tnt',Properties:{unstable:'true'}}", "{Name:'minecraft:tnt',Properties:{explode:'true'}}"); -+ register(752, "{Name:'minecraft:bookshelf'}", "{Name:'minecraft:bookshelf'}"); -+ register(768, "{Name:'minecraft:mossy_cobblestone'}", "{Name:'minecraft:mossy_cobblestone'}"); -+ register(784, "{Name:'minecraft:obsidian'}", "{Name:'minecraft:obsidian'}"); -+ register(801, "{Name:'minecraft:wall_torch',Properties:{facing:'east'}}", "{Name:'minecraft:torch',Properties:{facing:'east'}}"); -+ register(802, "{Name:'minecraft:wall_torch',Properties:{facing:'west'}}", "{Name:'minecraft:torch',Properties:{facing:'west'}}"); -+ register(803, "{Name:'minecraft:wall_torch',Properties:{facing:'south'}}", "{Name:'minecraft:torch',Properties:{facing:'south'}}"); -+ register(804, "{Name:'minecraft:wall_torch',Properties:{facing:'north'}}", "{Name:'minecraft:torch',Properties:{facing:'north'}}"); -+ register(805, "{Name:'minecraft:torch'}", "{Name:'minecraft:torch',Properties:{facing:'up'}}"); -+ register(816, "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'0',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(817, "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'1',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(818, "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'2',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(819, "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'3',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(820, "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'4',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(821, "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'5',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(822, "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'6',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(823, "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'7',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(824, "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'8',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(825, "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'9',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(826, "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'10',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(827, "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'11',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(828, "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'12',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(829, "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'13',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(830, "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'14',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(831, "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:fire',Properties:{age:'15',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(832, "{Name:'minecraft:mob_spawner'}", "{Name:'minecraft:mob_spawner'}"); -+ register(848, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(849, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(850, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(851, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(852, "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(853, "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(854, "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(855, "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(866, "{Name:'minecraft:chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'north'}}"); -+ register(867, "{Name:'minecraft:chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'south'}}"); -+ register(868, "{Name:'minecraft:chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'west'}}"); -+ register(869, "{Name:'minecraft:chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:chest',Properties:{facing:'east'}}"); -+ register(880, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'0',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'0',south:'up',west:'up'}}"); -+ register(881, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'1',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'1',south:'up',west:'up'}}"); -+ register(882, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'2',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'2',south:'up',west:'up'}}"); -+ register(883, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'3',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'3',south:'up',west:'up'}}"); -+ register(884, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'4',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'4',south:'up',west:'up'}}"); -+ register(885, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'5',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'5',south:'up',west:'up'}}"); -+ register(886, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'6',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'6',south:'up',west:'up'}}"); -+ register(887, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'7',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'7',south:'up',west:'up'}}"); -+ register(888, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'8',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'8',south:'up',west:'up'}}"); -+ register(889, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'9',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'9',south:'up',west:'up'}}"); -+ register(890, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'10',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'10',south:'up',west:'up'}}"); -+ register(891, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'11',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'11',south:'up',west:'up'}}"); -+ register(892, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'12',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'12',south:'up',west:'up'}}"); -+ register(893, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'13',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'13',south:'up',west:'up'}}"); -+ register(894, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'14',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'14',south:'up',west:'up'}}"); -+ register(895, "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'none',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'side',north:'up',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'none',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'side',power:'15',south:'up',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'none',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'side',west:'up'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'none'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'side'}}", "{Name:'minecraft:redstone_wire',Properties:{east:'up',north:'up',power:'15',south:'up',west:'up'}}"); -+ register(896, "{Name:'minecraft:diamond_ore'}", "{Name:'minecraft:diamond_ore'}"); -+ register(912, "{Name:'minecraft:diamond_block'}", "{Name:'minecraft:diamond_block'}"); -+ register(928, "{Name:'minecraft:crafting_table'}", "{Name:'minecraft:crafting_table'}"); -+ register(944, "{Name:'minecraft:wheat',Properties:{age:'0'}}", "{Name:'minecraft:wheat',Properties:{age:'0'}}"); -+ register(945, "{Name:'minecraft:wheat',Properties:{age:'1'}}", "{Name:'minecraft:wheat',Properties:{age:'1'}}"); -+ register(946, "{Name:'minecraft:wheat',Properties:{age:'2'}}", "{Name:'minecraft:wheat',Properties:{age:'2'}}"); -+ register(947, "{Name:'minecraft:wheat',Properties:{age:'3'}}", "{Name:'minecraft:wheat',Properties:{age:'3'}}"); -+ register(948, "{Name:'minecraft:wheat',Properties:{age:'4'}}", "{Name:'minecraft:wheat',Properties:{age:'4'}}"); -+ register(949, "{Name:'minecraft:wheat',Properties:{age:'5'}}", "{Name:'minecraft:wheat',Properties:{age:'5'}}"); -+ register(950, "{Name:'minecraft:wheat',Properties:{age:'6'}}", "{Name:'minecraft:wheat',Properties:{age:'6'}}"); -+ register(951, "{Name:'minecraft:wheat',Properties:{age:'7'}}", "{Name:'minecraft:wheat',Properties:{age:'7'}}"); -+ register(960, "{Name:'minecraft:farmland',Properties:{moisture:'0'}}", "{Name:'minecraft:farmland',Properties:{moisture:'0'}}"); -+ register(961, "{Name:'minecraft:farmland',Properties:{moisture:'1'}}", "{Name:'minecraft:farmland',Properties:{moisture:'1'}}"); -+ register(962, "{Name:'minecraft:farmland',Properties:{moisture:'2'}}", "{Name:'minecraft:farmland',Properties:{moisture:'2'}}"); -+ register(963, "{Name:'minecraft:farmland',Properties:{moisture:'3'}}", "{Name:'minecraft:farmland',Properties:{moisture:'3'}}"); -+ register(964, "{Name:'minecraft:farmland',Properties:{moisture:'4'}}", "{Name:'minecraft:farmland',Properties:{moisture:'4'}}"); -+ register(965, "{Name:'minecraft:farmland',Properties:{moisture:'5'}}", "{Name:'minecraft:farmland',Properties:{moisture:'5'}}"); -+ register(966, "{Name:'minecraft:farmland',Properties:{moisture:'6'}}", "{Name:'minecraft:farmland',Properties:{moisture:'6'}}"); -+ register(967, "{Name:'minecraft:farmland',Properties:{moisture:'7'}}", "{Name:'minecraft:farmland',Properties:{moisture:'7'}}"); -+ register(978, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'north'}}"); -+ register(979, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'south'}}"); -+ register(980, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'west'}}"); -+ register(981, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:furnace',Properties:{facing:'east'}}"); -+ register(994, "{Name:'minecraft:furnace',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'north'}}"); -+ register(995, "{Name:'minecraft:furnace',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'south'}}"); -+ register(996, "{Name:'minecraft:furnace',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'west'}}"); -+ register(997, "{Name:'minecraft:furnace',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:lit_furnace',Properties:{facing:'east'}}"); -+ register(1008, "{Name:'minecraft:sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}"); -+ register(1009, "{Name:'minecraft:sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}"); -+ register(1010, "{Name:'minecraft:sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}"); -+ register(1011, "{Name:'minecraft:sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}"); -+ register(1012, "{Name:'minecraft:sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}"); -+ register(1013, "{Name:'minecraft:sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}"); -+ register(1014, "{Name:'minecraft:sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}"); -+ register(1015, "{Name:'minecraft:sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}"); -+ register(1016, "{Name:'minecraft:sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}"); -+ register(1017, "{Name:'minecraft:sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}"); -+ register(1018, "{Name:'minecraft:sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}"); -+ register(1019, "{Name:'minecraft:sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}"); -+ register(1020, "{Name:'minecraft:sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}"); -+ register(1021, "{Name:'minecraft:sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}"); -+ register(1022, "{Name:'minecraft:sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}"); -+ register(1023, "{Name:'minecraft:sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}"); -+ register(1024, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1025, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1026, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1027, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1028, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1029, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1030, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1031, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1032, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1033, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(1034, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(1035, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:wooden_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(1036, "{Name:'minecraft:oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1037, "{Name:'minecraft:oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1038, "{Name:'minecraft:oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1039, "{Name:'minecraft:oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1042, "{Name:'minecraft:ladder',Properties:{facing:'north'}}", "{Name:'minecraft:ladder',Properties:{facing:'north'}}"); -+ register(1043, "{Name:'minecraft:ladder',Properties:{facing:'south'}}", "{Name:'minecraft:ladder',Properties:{facing:'south'}}"); -+ register(1044, "{Name:'minecraft:ladder',Properties:{facing:'west'}}", "{Name:'minecraft:ladder',Properties:{facing:'west'}}"); -+ register(1045, "{Name:'minecraft:ladder',Properties:{facing:'east'}}", "{Name:'minecraft:ladder',Properties:{facing:'east'}}"); -+ register(1056, "{Name:'minecraft:rail',Properties:{shape:'north_south'}}", "{Name:'minecraft:rail',Properties:{shape:'north_south'}}"); -+ register(1057, "{Name:'minecraft:rail',Properties:{shape:'east_west'}}", "{Name:'minecraft:rail',Properties:{shape:'east_west'}}"); -+ register(1058, "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_east'}}"); -+ register(1059, "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_west'}}"); -+ register(1060, "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_north'}}"); -+ register(1061, "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}", "{Name:'minecraft:rail',Properties:{shape:'ascending_south'}}"); -+ register(1062, "{Name:'minecraft:rail',Properties:{shape:'south_east'}}", "{Name:'minecraft:rail',Properties:{shape:'south_east'}}"); -+ register(1063, "{Name:'minecraft:rail',Properties:{shape:'south_west'}}", "{Name:'minecraft:rail',Properties:{shape:'south_west'}}"); -+ register(1064, "{Name:'minecraft:rail',Properties:{shape:'north_west'}}", "{Name:'minecraft:rail',Properties:{shape:'north_west'}}"); -+ register(1065, "{Name:'minecraft:rail',Properties:{shape:'north_east'}}", "{Name:'minecraft:rail',Properties:{shape:'north_east'}}"); -+ register(1072, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1073, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1074, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1075, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1076, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1077, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1078, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1079, "{Name:'minecraft:cobblestone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1090, "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'north'}}"); -+ register(1091, "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'south'}}"); -+ register(1092, "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'west'}}"); -+ register(1093, "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}", "{Name:'minecraft:wall_sign',Properties:{facing:'east'}}"); -+ register(1104, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'false'}}"); -+ register(1105, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'false'}}"); -+ register(1106, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'false'}}"); -+ register(1107, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'false'}}"); -+ register(1108, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'false'}}"); -+ register(1109, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'false'}}"); -+ register(1110, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'false'}}"); -+ register(1111, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'false'}}"); -+ register(1112, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_x',powered:'true'}}"); -+ register(1113, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'east',powered:'true'}}"); -+ register(1114, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'west',powered:'true'}}"); -+ register(1115, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'south',powered:'true'}}"); -+ register(1116, "{Name:'minecraft:lever',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'north',powered:'true'}}"); -+ register(1117, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_z',powered:'true'}}"); -+ register(1118, "{Name:'minecraft:lever',Properties:{face:'floor',facing:'west',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'up_x',powered:'true'}}"); -+ register(1119, "{Name:'minecraft:lever',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:lever',Properties:{facing:'down_z',powered:'true'}}"); -+ register(1120, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'false'}}"); -+ register(1121, "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:stone_pressure_plate',Properties:{powered:'true'}}"); -+ register(1136, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1137, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1138, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1139, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(1140, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1141, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1142, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1143, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(1144, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1145, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(1146, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(1147, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(1148, "{Name:'minecraft:iron_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1149, "{Name:'minecraft:iron_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1150, "{Name:'minecraft:iron_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1151, "{Name:'minecraft:iron_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(1152, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'false'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'false'}}"); -+ register(1153, "{Name:'minecraft:oak_pressure_plate',Properties:{powered:'true'}}", "{Name:'minecraft:wooden_pressure_plate',Properties:{powered:'true'}}"); -+ register(1168, "{Name:'minecraft:redstone_ore',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_ore'}"); -+ register(1184, "{Name:'minecraft:redstone_ore',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_ore'}"); -+ register(1201, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'east'}}"); -+ register(1202, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'west'}}"); -+ register(1203, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'south'}}"); -+ register(1204, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'north'}}"); -+ register(1205, "{Name:'minecraft:redstone_torch',Properties:{lit:'false'}}", "{Name:'minecraft:unlit_redstone_torch',Properties:{facing:'up'}}"); -+ register(1217, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'east',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'east'}}"); -+ register(1218, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'west',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'west'}}"); -+ register(1219, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'south',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'south'}}"); -+ register(1220, "{Name:'minecraft:redstone_wall_torch',Properties:{facing:'north',lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'north'}}"); -+ register(1221, "{Name:'minecraft:redstone_torch',Properties:{lit:'true'}}", "{Name:'minecraft:redstone_torch',Properties:{facing:'up'}}"); -+ register(1232, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'false'}}"); -+ register(1233, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'false'}}"); -+ register(1234, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'false'}}"); -+ register(1235, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'false'}}"); -+ register(1236, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'false'}}"); -+ register(1237, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'false'}}"); -+ register(1240, "{Name:'minecraft:stone_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'down',powered:'true'}}"); -+ register(1241, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'east',powered:'true'}}"); -+ register(1242, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'west',powered:'true'}}"); -+ register(1243, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'south',powered:'true'}}"); -+ register(1244, "{Name:'minecraft:stone_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'north',powered:'true'}}"); -+ register(1245, "{Name:'minecraft:stone_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:stone_button',Properties:{facing:'up',powered:'true'}}"); -+ register(1248, "{Name:'minecraft:snow',Properties:{layers:'1'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'1'}}"); -+ register(1249, "{Name:'minecraft:snow',Properties:{layers:'2'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'2'}}"); -+ register(1250, "{Name:'minecraft:snow',Properties:{layers:'3'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'3'}}"); -+ register(1251, "{Name:'minecraft:snow',Properties:{layers:'4'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'4'}}"); -+ register(1252, "{Name:'minecraft:snow',Properties:{layers:'5'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'5'}}"); -+ register(1253, "{Name:'minecraft:snow',Properties:{layers:'6'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'6'}}"); -+ register(1254, "{Name:'minecraft:snow',Properties:{layers:'7'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'7'}}"); -+ register(1255, "{Name:'minecraft:snow',Properties:{layers:'8'}}", "{Name:'minecraft:snow_layer',Properties:{layers:'8'}}"); -+ register(1264, "{Name:'minecraft:ice'}", "{Name:'minecraft:ice'}"); -+ register(1280, "{Name:'minecraft:snow_block'}", "{Name:'minecraft:snow'}"); -+ register(1296, "{Name:'minecraft:cactus',Properties:{age:'0'}}", "{Name:'minecraft:cactus',Properties:{age:'0'}}"); -+ register(1297, "{Name:'minecraft:cactus',Properties:{age:'1'}}", "{Name:'minecraft:cactus',Properties:{age:'1'}}"); -+ register(1298, "{Name:'minecraft:cactus',Properties:{age:'2'}}", "{Name:'minecraft:cactus',Properties:{age:'2'}}"); -+ register(1299, "{Name:'minecraft:cactus',Properties:{age:'3'}}", "{Name:'minecraft:cactus',Properties:{age:'3'}}"); -+ register(1300, "{Name:'minecraft:cactus',Properties:{age:'4'}}", "{Name:'minecraft:cactus',Properties:{age:'4'}}"); -+ register(1301, "{Name:'minecraft:cactus',Properties:{age:'5'}}", "{Name:'minecraft:cactus',Properties:{age:'5'}}"); -+ register(1302, "{Name:'minecraft:cactus',Properties:{age:'6'}}", "{Name:'minecraft:cactus',Properties:{age:'6'}}"); -+ register(1303, "{Name:'minecraft:cactus',Properties:{age:'7'}}", "{Name:'minecraft:cactus',Properties:{age:'7'}}"); -+ register(1304, "{Name:'minecraft:cactus',Properties:{age:'8'}}", "{Name:'minecraft:cactus',Properties:{age:'8'}}"); -+ register(1305, "{Name:'minecraft:cactus',Properties:{age:'9'}}", "{Name:'minecraft:cactus',Properties:{age:'9'}}"); -+ register(1306, "{Name:'minecraft:cactus',Properties:{age:'10'}}", "{Name:'minecraft:cactus',Properties:{age:'10'}}"); -+ register(1307, "{Name:'minecraft:cactus',Properties:{age:'11'}}", "{Name:'minecraft:cactus',Properties:{age:'11'}}"); -+ register(1308, "{Name:'minecraft:cactus',Properties:{age:'12'}}", "{Name:'minecraft:cactus',Properties:{age:'12'}}"); -+ register(1309, "{Name:'minecraft:cactus',Properties:{age:'13'}}", "{Name:'minecraft:cactus',Properties:{age:'13'}}"); -+ register(1310, "{Name:'minecraft:cactus',Properties:{age:'14'}}", "{Name:'minecraft:cactus',Properties:{age:'14'}}"); -+ register(1311, "{Name:'minecraft:cactus',Properties:{age:'15'}}", "{Name:'minecraft:cactus',Properties:{age:'15'}}"); -+ register(1312, "{Name:'minecraft:clay'}", "{Name:'minecraft:clay'}"); -+ register(1328, "{Name:'minecraft:sugar_cane',Properties:{age:'0'}}", "{Name:'minecraft:reeds',Properties:{age:'0'}}"); -+ register(1329, "{Name:'minecraft:sugar_cane',Properties:{age:'1'}}", "{Name:'minecraft:reeds',Properties:{age:'1'}}"); -+ register(1330, "{Name:'minecraft:sugar_cane',Properties:{age:'2'}}", "{Name:'minecraft:reeds',Properties:{age:'2'}}"); -+ register(1331, "{Name:'minecraft:sugar_cane',Properties:{age:'3'}}", "{Name:'minecraft:reeds',Properties:{age:'3'}}"); -+ register(1332, "{Name:'minecraft:sugar_cane',Properties:{age:'4'}}", "{Name:'minecraft:reeds',Properties:{age:'4'}}"); -+ register(1333, "{Name:'minecraft:sugar_cane',Properties:{age:'5'}}", "{Name:'minecraft:reeds',Properties:{age:'5'}}"); -+ register(1334, "{Name:'minecraft:sugar_cane',Properties:{age:'6'}}", "{Name:'minecraft:reeds',Properties:{age:'6'}}"); -+ register(1335, "{Name:'minecraft:sugar_cane',Properties:{age:'7'}}", "{Name:'minecraft:reeds',Properties:{age:'7'}}"); -+ register(1336, "{Name:'minecraft:sugar_cane',Properties:{age:'8'}}", "{Name:'minecraft:reeds',Properties:{age:'8'}}"); -+ register(1337, "{Name:'minecraft:sugar_cane',Properties:{age:'9'}}", "{Name:'minecraft:reeds',Properties:{age:'9'}}"); -+ register(1338, "{Name:'minecraft:sugar_cane',Properties:{age:'10'}}", "{Name:'minecraft:reeds',Properties:{age:'10'}}"); -+ register(1339, "{Name:'minecraft:sugar_cane',Properties:{age:'11'}}", "{Name:'minecraft:reeds',Properties:{age:'11'}}"); -+ register(1340, "{Name:'minecraft:sugar_cane',Properties:{age:'12'}}", "{Name:'minecraft:reeds',Properties:{age:'12'}}"); -+ register(1341, "{Name:'minecraft:sugar_cane',Properties:{age:'13'}}", "{Name:'minecraft:reeds',Properties:{age:'13'}}"); -+ register(1342, "{Name:'minecraft:sugar_cane',Properties:{age:'14'}}", "{Name:'minecraft:reeds',Properties:{age:'14'}}"); -+ register(1343, "{Name:'minecraft:sugar_cane',Properties:{age:'15'}}", "{Name:'minecraft:reeds',Properties:{age:'15'}}"); -+ register(1344, "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'false'}}"); -+ register(1345, "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}", "{Name:'minecraft:jukebox',Properties:{has_record:'true'}}"); -+ register(1360, "{Name:'minecraft:oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1376, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'south'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'south'}}"); -+ register(1377, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'west'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'west'}}"); -+ register(1378, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'north'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'north'}}"); -+ register(1379, "{Name:'minecraft:carved_pumpkin',Properties:{facing:'east'}}", "{Name:'minecraft:pumpkin',Properties:{facing:'east'}}"); -+ register(1392, "{Name:'minecraft:netherrack'}", "{Name:'minecraft:netherrack'}"); -+ register(1408, "{Name:'minecraft:soul_sand'}", "{Name:'minecraft:soul_sand'}"); -+ register(1424, "{Name:'minecraft:glowstone'}", "{Name:'minecraft:glowstone'}"); -+ register(1441, "{Name:'minecraft:portal',Properties:{axis:'x'}}", "{Name:'minecraft:portal',Properties:{axis:'x'}}"); -+ register(1442, "{Name:'minecraft:portal',Properties:{axis:'z'}}", "{Name:'minecraft:portal',Properties:{axis:'z'}}"); -+ register(1456, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'south'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'south'}}"); -+ register(1457, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'west'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'west'}}"); -+ register(1458, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'north'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'north'}}"); -+ register(1459, "{Name:'minecraft:jack_o_lantern',Properties:{facing:'east'}}", "{Name:'minecraft:lit_pumpkin',Properties:{facing:'east'}}"); -+ register(1472, "{Name:'minecraft:cake',Properties:{bites:'0'}}", "{Name:'minecraft:cake',Properties:{bites:'0'}}"); -+ register(1473, "{Name:'minecraft:cake',Properties:{bites:'1'}}", "{Name:'minecraft:cake',Properties:{bites:'1'}}"); -+ register(1474, "{Name:'minecraft:cake',Properties:{bites:'2'}}", "{Name:'minecraft:cake',Properties:{bites:'2'}}"); -+ register(1475, "{Name:'minecraft:cake',Properties:{bites:'3'}}", "{Name:'minecraft:cake',Properties:{bites:'3'}}"); -+ register(1476, "{Name:'minecraft:cake',Properties:{bites:'4'}}", "{Name:'minecraft:cake',Properties:{bites:'4'}}"); -+ register(1477, "{Name:'minecraft:cake',Properties:{bites:'5'}}", "{Name:'minecraft:cake',Properties:{bites:'5'}}"); -+ register(1478, "{Name:'minecraft:cake',Properties:{bites:'6'}}", "{Name:'minecraft:cake',Properties:{bites:'6'}}"); -+ register(1488, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); -+ register(1489, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); -+ register(1490, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); -+ register(1491, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); -+ register(1492, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); -+ register(1493, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); -+ register(1494, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); -+ register(1495, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); -+ register(1496, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); -+ register(1497, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); -+ register(1498, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); -+ register(1499, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); -+ register(1500, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); -+ register(1501, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); -+ register(1502, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); -+ register(1503, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:unpowered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); -+ register(1504, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'south',locked:'true'}}"); -+ register(1505, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'west',locked:'true'}}"); -+ register(1506, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'north',locked:'true'}}"); -+ register(1507, "{Name:'minecraft:repeater',Properties:{delay:'1',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'1',facing:'east',locked:'true'}}"); -+ register(1508, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'south',locked:'true'}}"); -+ register(1509, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'west',locked:'true'}}"); -+ register(1510, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'north',locked:'true'}}"); -+ register(1511, "{Name:'minecraft:repeater',Properties:{delay:'2',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'2',facing:'east',locked:'true'}}"); -+ register(1512, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'south',locked:'true'}}"); -+ register(1513, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'west',locked:'true'}}"); -+ register(1514, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'north',locked:'true'}}"); -+ register(1515, "{Name:'minecraft:repeater',Properties:{delay:'3',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'3',facing:'east',locked:'true'}}"); -+ register(1516, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'south',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'south',locked:'true'}}"); -+ register(1517, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'west',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'west',locked:'true'}}"); -+ register(1518, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'north',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'north',locked:'true'}}"); -+ register(1519, "{Name:'minecraft:repeater',Properties:{delay:'4',facing:'east',locked:'false',powered:'true'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'false'}}", "{Name:'minecraft:powered_repeater',Properties:{delay:'4',facing:'east',locked:'true'}}"); -+ register(1520, "{Name:'minecraft:white_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'white'}}"); -+ register(1521, "{Name:'minecraft:orange_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'orange'}}"); -+ register(1522, "{Name:'minecraft:magenta_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'magenta'}}"); -+ register(1523, "{Name:'minecraft:light_blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'light_blue'}}"); -+ register(1524, "{Name:'minecraft:yellow_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'yellow'}}"); -+ register(1525, "{Name:'minecraft:lime_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'lime'}}"); -+ register(1526, "{Name:'minecraft:pink_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'pink'}}"); -+ register(1527, "{Name:'minecraft:gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'gray'}}"); -+ register(1528, "{Name:'minecraft:light_gray_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'silver'}}"); -+ register(1529, "{Name:'minecraft:cyan_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'cyan'}}"); -+ register(1530, "{Name:'minecraft:purple_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'purple'}}"); -+ register(1531, "{Name:'minecraft:blue_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'blue'}}"); -+ register(1532, "{Name:'minecraft:brown_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'brown'}}"); -+ register(1533, "{Name:'minecraft:green_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'green'}}"); -+ register(1534, "{Name:'minecraft:red_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'red'}}"); -+ register(1535, "{Name:'minecraft:black_stained_glass'}", "{Name:'minecraft:stained_glass',Properties:{color:'black'}}"); -+ register(1536, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); -+ register(1537, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); -+ register(1538, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); -+ register(1539, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); -+ register(1540, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); -+ register(1541, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); -+ register(1542, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); -+ register(1543, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); -+ register(1544, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); -+ register(1545, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); -+ register(1546, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); -+ register(1547, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); -+ register(1548, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); -+ register(1549, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); -+ register(1550, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); -+ register(1551, "{Name:'minecraft:oak_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); -+ register(1552, "{Name:'minecraft:infested_stone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone'}}"); -+ register(1553, "{Name:'minecraft:infested_cobblestone'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cobblestone'}}"); -+ register(1554, "{Name:'minecraft:infested_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'stone_brick'}}"); -+ register(1555, "{Name:'minecraft:infested_mossy_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'mossy_brick'}}"); -+ register(1556, "{Name:'minecraft:infested_cracked_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'cracked_brick'}}"); -+ register(1557, "{Name:'minecraft:infested_chiseled_stone_bricks'}", "{Name:'minecraft:monster_egg',Properties:{variant:'chiseled_brick'}}"); -+ register(1568, "{Name:'minecraft:stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'stonebrick'}}"); -+ register(1569, "{Name:'minecraft:mossy_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'mossy_stonebrick'}}"); -+ register(1570, "{Name:'minecraft:cracked_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'cracked_stonebrick'}}"); -+ register(1571, "{Name:'minecraft:chiseled_stone_bricks'}", "{Name:'minecraft:stonebrick',Properties:{variant:'chiseled_stonebrick'}}"); -+ register(1584, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_inside'}}"); -+ register(1585, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_west'}}"); -+ register(1586, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north'}}"); -+ register(1587, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'north_east'}}"); -+ register(1588, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'west'}}"); -+ register(1589, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'center'}}"); -+ register(1590, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'east'}}"); -+ register(1591, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_west'}}"); -+ register(1592, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south'}}"); -+ register(1593, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'south_east'}}"); -+ register(1594, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'stem'}}"); -+ register(1595, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1596, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1597, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1598, "{Name:'minecraft:brown_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_outside'}}"); -+ register(1599, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:brown_mushroom_block',Properties:{variant:'all_stem'}}"); -+ register(1600, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_inside'}}"); -+ register(1601, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_west'}}"); -+ register(1602, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north'}}"); -+ register(1603, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'north_east'}}"); -+ register(1604, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'west'}}"); -+ register(1605, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'center'}}"); -+ register(1606, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'false',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'east'}}"); -+ register(1607, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'true',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_west'}}"); -+ register(1608, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south'}}"); -+ register(1609, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'true',south:'true',west:'false',up:'true',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'south_east'}}"); -+ register(1610, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'false',down:'false'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'stem'}}"); -+ register(1611, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1612, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1613, "{Name:'minecraft:red_mushroom_block',Properties:{north:'false',east:'false',south:'false',west:'false',up:'false',down:'false'}}"); -+ register(1614, "{Name:'minecraft:red_mushroom_block',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_outside'}}"); -+ register(1615, "{Name:'minecraft:mushroom_stem',Properties:{north:'true',east:'true',south:'true',west:'true',up:'true',down:'true'}}", "{Name:'minecraft:red_mushroom_block',Properties:{variant:'all_stem'}}"); -+ register(1616, "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:iron_bars',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1632, "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:glass_pane',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1648, "{Name:'minecraft:melon_block'}", "{Name:'minecraft:melon_block'}"); -+ register(1664, "{Name:'minecraft:pumpkin_stem',Properties:{age:'0'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'0',facing:'west'}}"); -+ register(1665, "{Name:'minecraft:pumpkin_stem',Properties:{age:'1'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'1',facing:'west'}}"); -+ register(1666, "{Name:'minecraft:pumpkin_stem',Properties:{age:'2'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'2',facing:'west'}}"); -+ register(1667, "{Name:'minecraft:pumpkin_stem',Properties:{age:'3'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'3',facing:'west'}}"); -+ register(1668, "{Name:'minecraft:pumpkin_stem',Properties:{age:'4'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'4',facing:'west'}}"); -+ register(1669, "{Name:'minecraft:pumpkin_stem',Properties:{age:'5'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'5',facing:'west'}}"); -+ register(1670, "{Name:'minecraft:pumpkin_stem',Properties:{age:'6'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'6',facing:'west'}}"); -+ register(1671, "{Name:'minecraft:pumpkin_stem',Properties:{age:'7'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:pumpkin_stem',Properties:{age:'7',facing:'west'}}"); -+ register(1680, "{Name:'minecraft:melon_stem',Properties:{age:'0'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'0',facing:'west'}}"); -+ register(1681, "{Name:'minecraft:melon_stem',Properties:{age:'1'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'1',facing:'west'}}"); -+ register(1682, "{Name:'minecraft:melon_stem',Properties:{age:'2'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'2',facing:'west'}}"); -+ register(1683, "{Name:'minecraft:melon_stem',Properties:{age:'3'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'3',facing:'west'}}"); -+ register(1684, "{Name:'minecraft:melon_stem',Properties:{age:'4'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'4',facing:'west'}}"); -+ register(1685, "{Name:'minecraft:melon_stem',Properties:{age:'5'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'5',facing:'west'}}"); -+ register(1686, "{Name:'minecraft:melon_stem',Properties:{age:'6'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'6',facing:'west'}}"); -+ register(1687, "{Name:'minecraft:melon_stem',Properties:{age:'7'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'east'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'north'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'south'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'up'}}", "{Name:'minecraft:melon_stem',Properties:{age:'7',facing:'west'}}"); -+ register(1696, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'false'}}"); -+ register(1697, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'false'}}"); -+ register(1698, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'false',up:'true',west:'true'}}"); -+ register(1699, "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'false',south:'true',up:'true',west:'true'}}"); -+ register(1700, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'false'}}"); -+ register(1701, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'false'}}"); -+ register(1702, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'false',up:'true',west:'true'}}"); -+ register(1703, "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'false',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(1704, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'false'}}"); -+ register(1705, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'false'}}"); -+ register(1706, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'false',up:'true',west:'true'}}"); -+ register(1707, "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'false',south:'true',up:'true',west:'true'}}"); -+ register(1708, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'false'}}"); -+ register(1709, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'false'}}"); -+ register(1710, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'false',up:'true',west:'true'}}"); -+ register(1711, "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:vine',Properties:{east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(1712, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1713, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1714, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1715, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(1716, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1717, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1718, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1719, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(1720, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1721, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1722, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1723, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(1724, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1725, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1726, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1727, "{Name:'minecraft:oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(1728, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1729, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1730, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1731, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1732, "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1733, "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1734, "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1735, "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1744, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1745, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1746, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1747, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1748, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1749, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1750, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1751, "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:stone_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1760, "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'false'}}", "{Name:'minecraft:mycelium',Properties:{snowy:'true'}}"); -+ register(1776, "{Name:'minecraft:lily_pad'}", "{Name:'minecraft:waterlily'}"); -+ register(1792, "{Name:'minecraft:nether_bricks'}", "{Name:'minecraft:nether_brick'}"); -+ register(1808, "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:nether_brick_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(1824, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(1825, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(1826, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(1827, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(1828, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(1829, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(1830, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(1831, "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:nether_brick_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(1840, "{Name:'minecraft:nether_wart',Properties:{age:'0'}}", "{Name:'minecraft:nether_wart',Properties:{age:'0'}}"); -+ register(1841, "{Name:'minecraft:nether_wart',Properties:{age:'1'}}", "{Name:'minecraft:nether_wart',Properties:{age:'1'}}"); -+ register(1842, "{Name:'minecraft:nether_wart',Properties:{age:'2'}}", "{Name:'minecraft:nether_wart',Properties:{age:'2'}}"); -+ register(1843, "{Name:'minecraft:nether_wart',Properties:{age:'3'}}", "{Name:'minecraft:nether_wart',Properties:{age:'3'}}"); -+ register(1856, "{Name:'minecraft:enchanting_table'}", "{Name:'minecraft:enchanting_table'}"); -+ register(1872, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'false'}}"); -+ register(1873, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'false'}}"); -+ register(1874, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'false'}}"); -+ register(1875, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'false'}}"); -+ register(1876, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'false',has_bottle_2:'true'}}"); -+ register(1877, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'false',has_bottle_2:'true'}}"); -+ register(1878, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'false',has_bottle_1:'true',has_bottle_2:'true'}}"); -+ register(1879, "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}", "{Name:'minecraft:brewing_stand',Properties:{has_bottle_0:'true',has_bottle_1:'true',has_bottle_2:'true'}}"); -+ register(1888, "{Name:'minecraft:cauldron',Properties:{level:'0'}}", "{Name:'minecraft:cauldron',Properties:{level:'0'}}"); -+ register(1889, "{Name:'minecraft:cauldron',Properties:{level:'1'}}", "{Name:'minecraft:cauldron',Properties:{level:'1'}}"); -+ register(1890, "{Name:'minecraft:cauldron',Properties:{level:'2'}}", "{Name:'minecraft:cauldron',Properties:{level:'2'}}"); -+ register(1891, "{Name:'minecraft:cauldron',Properties:{level:'3'}}", "{Name:'minecraft:cauldron',Properties:{level:'3'}}"); -+ register(1904, "{Name:'minecraft:end_portal'}", "{Name:'minecraft:end_portal'}"); -+ register(1920, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'south'}}"); -+ register(1921, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'west'}}"); -+ register(1922, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'north'}}"); -+ register(1923, "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'false',facing:'east'}}"); -+ register(1924, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'south'}}"); -+ register(1925, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'west'}}"); -+ register(1926, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'north'}}"); -+ register(1927, "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}", "{Name:'minecraft:end_portal_frame',Properties:{eye:'true',facing:'east'}}"); -+ register(1936, "{Name:'minecraft:end_stone'}", "{Name:'minecraft:end_stone'}"); -+ register(1952, "{Name:'minecraft:dragon_egg'}", "{Name:'minecraft:dragon_egg'}"); -+ register(1968, "{Name:'minecraft:redstone_lamp',Properties:{lit:'false'}}", "{Name:'minecraft:redstone_lamp'}"); -+ register(1984, "{Name:'minecraft:redstone_lamp',Properties:{lit:'true'}}", "{Name:'minecraft:lit_redstone_lamp'}"); -+ register(2000, "{Name:'minecraft:oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'oak'}}"); -+ register(2001, "{Name:'minecraft:spruce_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'spruce'}}"); -+ register(2002, "{Name:'minecraft:birch_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'birch'}}"); -+ register(2003, "{Name:'minecraft:jungle_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'jungle'}}"); -+ register(2004, "{Name:'minecraft:acacia_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'acacia'}}"); -+ register(2005, "{Name:'minecraft:dark_oak_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_wooden_slab',Properties:{variant:'dark_oak'}}"); -+ register(2016, "{Name:'minecraft:oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'oak'}}"); -+ register(2017, "{Name:'minecraft:spruce_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'spruce'}}"); -+ register(2018, "{Name:'minecraft:birch_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'birch'}}"); -+ register(2019, "{Name:'minecraft:jungle_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'jungle'}}"); -+ register(2020, "{Name:'minecraft:acacia_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'acacia'}}"); -+ register(2021, "{Name:'minecraft:dark_oak_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'bottom',variant:'dark_oak'}}"); -+ register(2024, "{Name:'minecraft:oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'oak'}}"); -+ register(2025, "{Name:'minecraft:spruce_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'spruce'}}"); -+ register(2026, "{Name:'minecraft:birch_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'birch'}}"); -+ register(2027, "{Name:'minecraft:jungle_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'jungle'}}"); -+ register(2028, "{Name:'minecraft:acacia_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'acacia'}}"); -+ register(2029, "{Name:'minecraft:dark_oak_slab',Properties:{type:'top'}}", "{Name:'minecraft:wooden_slab',Properties:{half:'top',variant:'dark_oak'}}"); -+ register(2032, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'south'}}"); -+ register(2033, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'west'}}"); -+ register(2034, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'north'}}"); -+ register(2035, "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'0',facing:'east'}}"); -+ register(2036, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'south'}}"); -+ register(2037, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'west'}}"); -+ register(2038, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'north'}}"); -+ register(2039, "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'1',facing:'east'}}"); -+ register(2040, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'south'}}"); -+ register(2041, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'west'}}"); -+ register(2042, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'north'}}"); -+ register(2043, "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}", "{Name:'minecraft:cocoa',Properties:{age:'2',facing:'east'}}"); -+ register(2048, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2049, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2050, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2051, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2052, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2053, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2054, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2055, "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2064, "{Name:'minecraft:emerald_ore'}", "{Name:'minecraft:emerald_ore'}"); -+ register(2082, "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'north'}}"); -+ register(2083, "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'south'}}"); -+ register(2084, "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'west'}}"); -+ register(2085, "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}", "{Name:'minecraft:ender_chest',Properties:{facing:'east'}}"); -+ register(2096, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'false'}}"); -+ register(2097, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'false'}}"); -+ register(2098, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'false'}}"); -+ register(2099, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'false'}}"); -+ register(2100, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'false'}}"); -+ register(2101, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'false'}}"); -+ register(2102, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'false'}}"); -+ register(2103, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'false'}}"); -+ register(2104, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'south',powered:'true'}}"); -+ register(2105, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'west',powered:'true'}}"); -+ register(2106, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'north',powered:'true'}}"); -+ register(2107, "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'false',facing:'east',powered:'true'}}"); -+ register(2108, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'south',powered:'true'}}"); -+ register(2109, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'west',powered:'true'}}"); -+ register(2110, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'north',powered:'true'}}"); -+ register(2111, "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}", "{Name:'minecraft:tripwire_hook',Properties:{attached:'true',facing:'east',powered:'true'}}"); -+ register(2112, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2113, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2114, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2115, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2116, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2117, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2118, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2119, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'false',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2120, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2121, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2122, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2123, "{Name:'minecraft:tripwire',Properties:{attached:'false',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}"); -+ register(2124, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'false',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'false',south:'true',west:'true'}}"); -+ register(2125, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'true',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'false',powered:'true',south:'true',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'false',west:'true'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'false'}}", "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'true',north:'true',powered:'true',south:'true',west:'true'}}"); -+ register(2126, "{Name:'minecraft:tripwire',Properties:{attached:'true',disarmed:'true',east:'false',north:'false',powered:'false',south:'false',west:'false'}}"); -+ register(2128, "{Name:'minecraft:emerald_block'}", "{Name:'minecraft:emerald_block'}"); -+ register(2144, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2145, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2146, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2147, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2148, "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2149, "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2150, "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2151, "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:spruce_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2160, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2161, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2162, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2163, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2164, "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2165, "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2166, "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2167, "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:birch_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2176, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2177, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2178, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2179, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2180, "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2181, "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2182, "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2183, "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:jungle_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2192, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(2193, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(2194, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(2195, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(2196, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(2197, "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(2200, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(2201, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(2202, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(2203, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(2204, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(2205, "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(2208, "{Name:'minecraft:beacon'}", "{Name:'minecraft:beacon'}"); -+ register(2224, "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'cobblestone',west:'true'}}"); -+ register(2225, "{Name:'minecraft:mossy_cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'false',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'false',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'false',up:'true',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'false',variant:'mossy_cobblestone',west:'true'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'false'}}", "{Name:'minecraft:cobblestone_wall',Properties:{east:'true',north:'true',south:'true',up:'true',variant:'mossy_cobblestone',west:'true'}}"); -+ // There are a few changes made to flower pot here, notably handling how legacy data is handled. -+ // The TE itself should contain the target item and from there the proper state can be determined. However, there are -+ // blocks that do not contain a TE. So we need to make sure there is a default to fall on. -+ // I simply followed the legacy handling from BlockFlowerPot from 1.8.8 to find what legacy data mapped to what. -+ // It's better than defaulting everything to a cactus. -+ register(2240, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'0'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'0'}}"); -+ register(2241, "{Name:'minecraft:potted_poppy'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'1'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'1'}}"); -+ register(2242, "{Name:'minecraft:potted_dandelion'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'2'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'2'}}"); -+ register(2243, "{Name:'minecraft:potted_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'3'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'3'}}"); -+ register(2244, "{Name:'minecraft:potted_spruce_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'4'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'4'}}"); -+ register(2245, "{Name:'minecraft:potted_birch_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'5'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'5'}}"); -+ register(2246, "{Name:'minecraft:potted_jungle_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'6'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'6'}}"); -+ register(2247, "{Name:'minecraft:potted_red_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'7'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'7'}}"); -+ register(2248, "{Name:'minecraft:potted_brown_mushroom'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'8'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'8'}}"); -+ register(2249, "{Name:'minecraft:potted_cactus'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'9'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'9'}}"); -+ register(2250, "{Name:'minecraft:potted_dead_bush'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'10'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'10'}}"); -+ register(2251, "{Name:'minecraft:potted_fern'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'11'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'11'}}"); -+ register(2252, "{Name:'minecraft:potted_acacia_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'12'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'12'}}"); -+ register(2253, "{Name:'minecraft:potted_dark_oak_sapling'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'13'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'13'}}"); -+ register(2254, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'14'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'14'}}"); -+ register(2255, "{Name:'minecraft:flower_pot'}", "{Name:'minecraft:flower_pot',Properties:{contents:'acacia_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'allium',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'birch_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'blue_orchid',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'cactus',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dandelion',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dark_oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'dead_bush',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'empty',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'fern',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'houstonia',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'jungle_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_brown',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'mushroom_red',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oak_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'orange_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'oxeye_daisy',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'pink_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'red_tulip',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'rose',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'spruce_sapling',legacy_data:'15'}}", "{Name:'minecraft:flower_pot',Properties:{contents:'white_tulip',legacy_data:'15'}}"); -+ register(2256, "{Name:'minecraft:carrots',Properties:{age:'0'}}", "{Name:'minecraft:carrots',Properties:{age:'0'}}"); -+ register(2257, "{Name:'minecraft:carrots',Properties:{age:'1'}}", "{Name:'minecraft:carrots',Properties:{age:'1'}}"); -+ register(2258, "{Name:'minecraft:carrots',Properties:{age:'2'}}", "{Name:'minecraft:carrots',Properties:{age:'2'}}"); -+ register(2259, "{Name:'minecraft:carrots',Properties:{age:'3'}}", "{Name:'minecraft:carrots',Properties:{age:'3'}}"); -+ register(2260, "{Name:'minecraft:carrots',Properties:{age:'4'}}", "{Name:'minecraft:carrots',Properties:{age:'4'}}"); -+ register(2261, "{Name:'minecraft:carrots',Properties:{age:'5'}}", "{Name:'minecraft:carrots',Properties:{age:'5'}}"); -+ register(2262, "{Name:'minecraft:carrots',Properties:{age:'6'}}", "{Name:'minecraft:carrots',Properties:{age:'6'}}"); -+ register(2263, "{Name:'minecraft:carrots',Properties:{age:'7'}}", "{Name:'minecraft:carrots',Properties:{age:'7'}}"); -+ register(2272, "{Name:'minecraft:potatoes',Properties:{age:'0'}}", "{Name:'minecraft:potatoes',Properties:{age:'0'}}"); -+ register(2273, "{Name:'minecraft:potatoes',Properties:{age:'1'}}", "{Name:'minecraft:potatoes',Properties:{age:'1'}}"); -+ register(2274, "{Name:'minecraft:potatoes',Properties:{age:'2'}}", "{Name:'minecraft:potatoes',Properties:{age:'2'}}"); -+ register(2275, "{Name:'minecraft:potatoes',Properties:{age:'3'}}", "{Name:'minecraft:potatoes',Properties:{age:'3'}}"); -+ register(2276, "{Name:'minecraft:potatoes',Properties:{age:'4'}}", "{Name:'minecraft:potatoes',Properties:{age:'4'}}"); -+ register(2277, "{Name:'minecraft:potatoes',Properties:{age:'5'}}", "{Name:'minecraft:potatoes',Properties:{age:'5'}}"); -+ register(2278, "{Name:'minecraft:potatoes',Properties:{age:'6'}}", "{Name:'minecraft:potatoes',Properties:{age:'6'}}"); -+ register(2279, "{Name:'minecraft:potatoes',Properties:{age:'7'}}", "{Name:'minecraft:potatoes',Properties:{age:'7'}}"); -+ register(2288, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'false'}}"); -+ register(2289, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'false'}}"); -+ register(2290, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'false'}}"); -+ register(2291, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'false'}}"); -+ register(2292, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'false'}}"); -+ register(2293, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'false'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'false'}}"); -+ register(2296, "{Name:'minecraft:oak_button',Properties:{face:'ceiling',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'down',powered:'true'}}"); -+ register(2297, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'east',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'east',powered:'true'}}"); -+ register(2298, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'west',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'west',powered:'true'}}"); -+ register(2299, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'south',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'south',powered:'true'}}"); -+ register(2300, "{Name:'minecraft:oak_button',Properties:{face:'wall',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'north',powered:'true'}}"); -+ register(2301, "{Name:'minecraft:oak_button',Properties:{face:'floor',facing:'north',powered:'true'}}", "{Name:'minecraft:wooden_button',Properties:{facing:'up',powered:'true'}}"); -+ register(2304, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'false'}}"); -+ register(2305, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'false'}}"); -+ register(2306, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'false'}}"); -+ register(2307, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'false'}}"); -+ register(2308, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'false'}}"); -+ register(2309, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'false'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'false'}}"); -+ register(2312, "{Name:'%%FILTER_ME%%',Properties:{facing:'down',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'down',nodrop:'true'}}"); -+ register(2313, "{Name:'%%FILTER_ME%%',Properties:{facing:'up',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'up',nodrop:'true'}}"); -+ register(2314, "{Name:'%%FILTER_ME%%',Properties:{facing:'north',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'north',nodrop:'true'}}"); -+ register(2315, "{Name:'%%FILTER_ME%%',Properties:{facing:'south',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'south',nodrop:'true'}}"); -+ register(2316, "{Name:'%%FILTER_ME%%',Properties:{facing:'west',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'west',nodrop:'true'}}"); -+ register(2317, "{Name:'%%FILTER_ME%%',Properties:{facing:'east',nodrop:'true'}}", "{Name:'minecraft:skull',Properties:{facing:'east',nodrop:'true'}}"); -+ register(2320, "{Name:'minecraft:anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'south'}}"); -+ register(2321, "{Name:'minecraft:anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'west'}}"); -+ register(2322, "{Name:'minecraft:anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'north'}}"); -+ register(2323, "{Name:'minecraft:anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'0',facing:'east'}}"); -+ register(2324, "{Name:'minecraft:chipped_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'south'}}"); -+ register(2325, "{Name:'minecraft:chipped_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'west'}}"); -+ register(2326, "{Name:'minecraft:chipped_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'north'}}"); -+ register(2327, "{Name:'minecraft:chipped_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'1',facing:'east'}}"); -+ register(2328, "{Name:'minecraft:damaged_anvil',Properties:{facing:'south'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'south'}}"); -+ register(2329, "{Name:'minecraft:damaged_anvil',Properties:{facing:'west'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'west'}}"); -+ register(2330, "{Name:'minecraft:damaged_anvil',Properties:{facing:'north'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'north'}}"); -+ register(2331, "{Name:'minecraft:damaged_anvil',Properties:{facing:'east'}}", "{Name:'minecraft:anvil',Properties:{damage:'2',facing:'east'}}"); -+ register(2338, "{Name:'minecraft:trapped_chest',Properties:{facing:'north',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'north'}}"); -+ register(2339, "{Name:'minecraft:trapped_chest',Properties:{facing:'south',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'south'}}"); -+ register(2340, "{Name:'minecraft:trapped_chest',Properties:{facing:'west',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'west'}}"); -+ register(2341, "{Name:'minecraft:trapped_chest',Properties:{facing:'east',type:'single'}}", "{Name:'minecraft:trapped_chest',Properties:{facing:'east'}}"); -+ register(2352, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'0'}}"); -+ register(2353, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'1'}}"); -+ register(2354, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'2'}}"); -+ register(2355, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'3'}}"); -+ register(2356, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'4'}}"); -+ register(2357, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'5'}}"); -+ register(2358, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'6'}}"); -+ register(2359, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'7'}}"); -+ register(2360, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'8'}}"); -+ register(2361, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'9'}}"); -+ register(2362, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'10'}}"); -+ register(2363, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'11'}}"); -+ register(2364, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'12'}}"); -+ register(2365, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'13'}}"); -+ register(2366, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'14'}}"); -+ register(2367, "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:light_weighted_pressure_plate',Properties:{power:'15'}}"); -+ register(2368, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'0'}}"); -+ register(2369, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'1'}}"); -+ register(2370, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'2'}}"); -+ register(2371, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'3'}}"); -+ register(2372, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'4'}}"); -+ register(2373, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'5'}}"); -+ register(2374, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'6'}}"); -+ register(2375, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'7'}}"); -+ register(2376, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'8'}}"); -+ register(2377, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'9'}}"); -+ register(2378, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'10'}}"); -+ register(2379, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'11'}}"); -+ register(2380, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'12'}}"); -+ register(2381, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'13'}}"); -+ register(2382, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'14'}}"); -+ register(2383, "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}", "{Name:'minecraft:heavy_weighted_pressure_plate',Properties:{power:'15'}}"); -+ register(2384, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); -+ register(2385, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); -+ register(2386, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); -+ register(2387, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); -+ register(2388, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); -+ register(2389, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); -+ register(2390, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); -+ register(2391, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); -+ register(2392, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); -+ register(2393, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); -+ register(2394, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); -+ register(2395, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); -+ register(2396, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); -+ register(2397, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); -+ register(2398, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); -+ register(2399, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:unpowered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); -+ register(2400, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'false'}}"); -+ register(2401, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'false'}}"); -+ register(2402, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'false'}}"); -+ register(2403, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'false'}}"); -+ register(2404, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'false'}}"); -+ register(2405, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'false'}}"); -+ register(2406, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'false'}}"); -+ register(2407, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'false'}}"); -+ register(2408, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'compare',powered:'true'}}"); -+ register(2409, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'compare',powered:'true'}}"); -+ register(2410, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'compare',powered:'true'}}"); -+ register(2411, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'compare',powered:'true'}}"); -+ register(2412, "{Name:'minecraft:comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'south',mode:'subtract',powered:'true'}}"); -+ register(2413, "{Name:'minecraft:comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'west',mode:'subtract',powered:'true'}}"); -+ register(2414, "{Name:'minecraft:comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'north',mode:'subtract',powered:'true'}}"); -+ register(2415, "{Name:'minecraft:comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}", "{Name:'minecraft:powered_comparator',Properties:{facing:'east',mode:'subtract',powered:'true'}}"); -+ register(2416, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'0'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'0'}}"); -+ register(2417, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'1'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'1'}}"); -+ register(2418, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'2'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'2'}}"); -+ register(2419, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'3'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'3'}}"); -+ register(2420, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'4'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'4'}}"); -+ register(2421, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'5'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'5'}}"); -+ register(2422, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'6'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'6'}}"); -+ register(2423, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'7'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'7'}}"); -+ register(2424, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'8'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'8'}}"); -+ register(2425, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'9'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'9'}}"); -+ register(2426, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'10'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'10'}}"); -+ register(2427, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'11'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'11'}}"); -+ register(2428, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'12'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'12'}}"); -+ register(2429, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'13'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'13'}}"); -+ register(2430, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'14'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'14'}}"); -+ register(2431, "{Name:'minecraft:daylight_detector',Properties:{inverted:'false',power:'15'}}", "{Name:'minecraft:daylight_detector',Properties:{power:'15'}}"); -+ register(2432, "{Name:'minecraft:redstone_block'}", "{Name:'minecraft:redstone_block'}"); -+ register(2448, "{Name:'minecraft:nether_quartz_ore'}", "{Name:'minecraft:quartz_ore'}"); -+ register(2464, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'down'}}"); -+ register(2466, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'north'}}"); -+ register(2467, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'south'}}"); -+ register(2468, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'west'}}"); -+ register(2469, "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'true',facing:'east'}}"); -+ register(2472, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'down'}}"); -+ register(2474, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'north'}}"); -+ register(2475, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'south'}}"); -+ register(2476, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'west'}}"); -+ register(2477, "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}", "{Name:'minecraft:hopper',Properties:{enabled:'false',facing:'east'}}"); -+ register(2480, "{Name:'minecraft:quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'default'}}"); -+ register(2481, "{Name:'minecraft:chiseled_quartz_block'}", "{Name:'minecraft:quartz_block',Properties:{variant:'chiseled'}}"); -+ register(2482, "{Name:'minecraft:quartz_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_y'}}"); -+ register(2483, "{Name:'minecraft:quartz_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_x'}}"); -+ register(2484, "{Name:'minecraft:quartz_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:quartz_block',Properties:{variant:'lines_z'}}"); -+ register(2496, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2497, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2498, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2499, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2500, "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2501, "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2502, "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2503, "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:quartz_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2512, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'north_south'}}"); -+ register(2513, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'east_west'}}"); -+ register(2514, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_east'}}"); -+ register(2515, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_west'}}"); -+ register(2516, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_north'}}"); -+ register(2517, "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'false',shape:'ascending_south'}}"); -+ register(2520, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'north_south'}}"); -+ register(2521, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'east_west'}}"); -+ register(2522, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_east'}}"); -+ register(2523, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_west'}}"); -+ register(2524, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_north'}}"); -+ register(2525, "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}", "{Name:'minecraft:activator_rail',Properties:{powered:'true',shape:'ascending_south'}}"); -+ register(2528, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'false'}}"); -+ register(2529, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'false'}}"); -+ register(2530, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'false'}}"); -+ register(2531, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'false'}}"); -+ register(2532, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'false'}}"); -+ register(2533, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'false'}}"); -+ register(2536, "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'down',triggered:'true'}}"); -+ register(2537, "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'up',triggered:'true'}}"); -+ register(2538, "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'north',triggered:'true'}}"); -+ register(2539, "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'south',triggered:'true'}}"); -+ register(2540, "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'west',triggered:'true'}}"); -+ register(2541, "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}", "{Name:'minecraft:dropper',Properties:{facing:'east',triggered:'true'}}"); -+ register(2544, "{Name:'minecraft:white_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'white'}}"); -+ register(2545, "{Name:'minecraft:orange_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'orange'}}"); -+ register(2546, "{Name:'minecraft:magenta_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'magenta'}}"); -+ register(2547, "{Name:'minecraft:light_blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'light_blue'}}"); -+ register(2548, "{Name:'minecraft:yellow_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'yellow'}}"); -+ register(2549, "{Name:'minecraft:lime_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'lime'}}"); -+ register(2550, "{Name:'minecraft:pink_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'pink'}}"); -+ register(2551, "{Name:'minecraft:gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'gray'}}"); -+ register(2552, "{Name:'minecraft:light_gray_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'silver'}}"); -+ register(2553, "{Name:'minecraft:cyan_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'cyan'}}"); -+ register(2554, "{Name:'minecraft:purple_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'purple'}}"); -+ register(2555, "{Name:'minecraft:blue_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'blue'}}"); -+ register(2556, "{Name:'minecraft:brown_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'brown'}}"); -+ register(2557, "{Name:'minecraft:green_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'green'}}"); -+ register(2558, "{Name:'minecraft:red_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'red'}}"); -+ register(2559, "{Name:'minecraft:black_terracotta'}", "{Name:'minecraft:stained_hardened_clay',Properties:{color:'black'}}"); -+ register(2560, "{Name:'minecraft:white_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'white',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2561, "{Name:'minecraft:orange_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'orange',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2562, "{Name:'minecraft:magenta_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'magenta',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2563, "{Name:'minecraft:light_blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'light_blue',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2564, "{Name:'minecraft:yellow_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'yellow',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2565, "{Name:'minecraft:lime_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'lime',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2566, "{Name:'minecraft:pink_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'pink',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2567, "{Name:'minecraft:gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'gray',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2568, "{Name:'minecraft:light_gray_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'silver',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2569, "{Name:'minecraft:cyan_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'cyan',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2570, "{Name:'minecraft:purple_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'purple',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2571, "{Name:'minecraft:blue_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'blue',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2572, "{Name:'minecraft:brown_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'brown',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2573, "{Name:'minecraft:green_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'green',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2574, "{Name:'minecraft:red_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'red',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2575, "{Name:'minecraft:black_stained_glass_pane',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:stained_glass_pane',Properties:{color:'black',east:'true',north:'true',south:'true',west:'true'}}"); -+ register(2576, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'acacia'}}"); -+ register(2577, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'true',variant:'dark_oak'}}"); -+ register(2580, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'acacia'}}"); -+ register(2581, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'false',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'false',decayable:'false',variant:'dark_oak'}}"); -+ register(2584, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'acacia'}}"); -+ register(2585, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'true'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'true',variant:'dark_oak'}}"); -+ register(2588, "{Name:'minecraft:acacia_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'acacia'}}"); -+ register(2589, "{Name:'minecraft:dark_oak_leaves',Properties:{check_decay:'true',decayable:'false'}}", "{Name:'minecraft:leaves2',Properties:{check_decay:'true',decayable:'false',variant:'dark_oak'}}"); -+ register(2592, "{Name:'minecraft:acacia_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'acacia'}}"); -+ register(2593, "{Name:'minecraft:dark_oak_log',Properties:{axis:'y'}}", "{Name:'minecraft:log2',Properties:{axis:'y',variant:'dark_oak'}}"); -+ register(2596, "{Name:'minecraft:acacia_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'acacia'}}"); -+ register(2597, "{Name:'minecraft:dark_oak_log',Properties:{axis:'x'}}", "{Name:'minecraft:log2',Properties:{axis:'x',variant:'dark_oak'}}"); -+ register(2600, "{Name:'minecraft:acacia_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'acacia'}}"); -+ register(2601, "{Name:'minecraft:dark_oak_log',Properties:{axis:'z'}}", "{Name:'minecraft:log2',Properties:{axis:'z',variant:'dark_oak'}}"); -+ register(2604, "{Name:'minecraft:acacia_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'acacia'}}"); -+ register(2605, "{Name:'minecraft:dark_oak_bark'}", "{Name:'minecraft:log2',Properties:{axis:'none',variant:'dark_oak'}}"); -+ register(2608, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2609, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2610, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2611, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2612, "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2613, "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2614, "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2615, "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:acacia_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2624, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2625, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2626, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2627, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2628, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2629, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2630, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2631, "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:dark_oak_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2640, "{Name:'minecraft:slime_block'}", "{Name:'minecraft:slime'}"); -+ register(2656, "{Name:'minecraft:barrier'}", "{Name:'minecraft:barrier'}"); -+ register(2672, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'false'}}"); -+ register(2673, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'false'}}"); -+ register(2674, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'false'}}"); -+ register(2675, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'false'}}"); -+ register(2676, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'bottom',open:'true'}}"); -+ register(2677, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'bottom',open:'true'}}"); -+ register(2678, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'bottom',open:'true'}}"); -+ register(2679, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'bottom',open:'true'}}"); -+ register(2680, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'false'}}"); -+ register(2681, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'false'}}"); -+ register(2682, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'false'}}"); -+ register(2683, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'false'}}"); -+ register(2684, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'north',half:'top',open:'true'}}"); -+ register(2685, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'south',half:'top',open:'true'}}"); -+ register(2686, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'west',half:'top',open:'true'}}"); -+ register(2687, "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}", "{Name:'minecraft:iron_trapdoor',Properties:{facing:'east',half:'top',open:'true'}}"); -+ register(2688, "{Name:'minecraft:prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine'}}"); -+ register(2689, "{Name:'minecraft:prismarine_bricks'}", "{Name:'minecraft:prismarine',Properties:{variant:'prismarine_bricks'}}"); -+ register(2690, "{Name:'minecraft:dark_prismarine'}", "{Name:'minecraft:prismarine',Properties:{variant:'dark_prismarine'}}"); -+ register(2704, "{Name:'minecraft:sea_lantern'}", "{Name:'minecraft:sea_lantern'}"); -+ register(2720, "{Name:'minecraft:hay_block',Properties:{axis:'y'}}", "{Name:'minecraft:hay_block',Properties:{axis:'y'}}"); -+ register(2724, "{Name:'minecraft:hay_block',Properties:{axis:'x'}}", "{Name:'minecraft:hay_block',Properties:{axis:'x'}}"); -+ register(2728, "{Name:'minecraft:hay_block',Properties:{axis:'z'}}", "{Name:'minecraft:hay_block',Properties:{axis:'z'}}"); -+ register(2736, "{Name:'minecraft:white_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'white'}}"); -+ register(2737, "{Name:'minecraft:orange_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'orange'}}"); -+ register(2738, "{Name:'minecraft:magenta_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'magenta'}}"); -+ register(2739, "{Name:'minecraft:light_blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'light_blue'}}"); -+ register(2740, "{Name:'minecraft:yellow_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'yellow'}}"); -+ register(2741, "{Name:'minecraft:lime_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'lime'}}"); -+ register(2742, "{Name:'minecraft:pink_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'pink'}}"); -+ register(2743, "{Name:'minecraft:gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'gray'}}"); -+ register(2744, "{Name:'minecraft:light_gray_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'silver'}}"); -+ register(2745, "{Name:'minecraft:cyan_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'cyan'}}"); -+ register(2746, "{Name:'minecraft:purple_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'purple'}}"); -+ register(2747, "{Name:'minecraft:blue_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'blue'}}"); -+ register(2748, "{Name:'minecraft:brown_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'brown'}}"); -+ register(2749, "{Name:'minecraft:green_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'green'}}"); -+ register(2750, "{Name:'minecraft:red_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'red'}}"); -+ register(2751, "{Name:'minecraft:black_carpet'}", "{Name:'minecraft:carpet',Properties:{color:'black'}}"); -+ register(2752, "{Name:'minecraft:terracotta'}", "{Name:'minecraft:hardened_clay'}"); -+ register(2768, "{Name:'minecraft:coal_block'}", "{Name:'minecraft:coal_block'}"); -+ register(2784, "{Name:'minecraft:packed_ice'}", "{Name:'minecraft:packed_ice'}"); -+ register(2800, "{Name:'minecraft:sunflower',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'sunflower'}}"); -+ register(2801, "{Name:'minecraft:lilac',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'syringa'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'syringa'}}"); -+ register(2802, "{Name:'minecraft:tall_grass',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_grass'}}"); -+ register(2803, "{Name:'minecraft:large_fern',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_fern'}}"); -+ register(2804, "{Name:'minecraft:rose_bush',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'double_rose'}}"); -+ register(2805, "{Name:'minecraft:peony',Properties:{half:'lower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'lower',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'lower',variant:'paeonia'}}"); -+ register(2808, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'south',half:'upper',variant:'syringa'}}"); -+ register(2809, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'west',half:'upper',variant:'syringa'}}"); -+ register(2810, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'north',half:'upper',variant:'syringa'}}"); -+ register(2811, "{Name:'minecraft:peony',Properties:{half:'upper'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_fern'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_grass'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'double_rose'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'paeonia'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'sunflower'}}", "{Name:'minecraft:double_plant',Properties:{facing:'east',half:'upper',variant:'syringa'}}"); -+ register(2816, "{Name:'minecraft:white_banner',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'0'}}"); -+ register(2817, "{Name:'minecraft:white_banner',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'1'}}"); -+ register(2818, "{Name:'minecraft:white_banner',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'2'}}"); -+ register(2819, "{Name:'minecraft:white_banner',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'3'}}"); -+ register(2820, "{Name:'minecraft:white_banner',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'4'}}"); -+ register(2821, "{Name:'minecraft:white_banner',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'5'}}"); -+ register(2822, "{Name:'minecraft:white_banner',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'6'}}"); -+ register(2823, "{Name:'minecraft:white_banner',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'7'}}"); -+ register(2824, "{Name:'minecraft:white_banner',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'8'}}"); -+ register(2825, "{Name:'minecraft:white_banner',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'9'}}"); -+ register(2826, "{Name:'minecraft:white_banner',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'10'}}"); -+ register(2827, "{Name:'minecraft:white_banner',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'11'}}"); -+ register(2828, "{Name:'minecraft:white_banner',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'12'}}"); -+ register(2829, "{Name:'minecraft:white_banner',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'13'}}"); -+ register(2830, "{Name:'minecraft:white_banner',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'14'}}"); -+ register(2831, "{Name:'minecraft:white_banner',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_banner',Properties:{rotation:'15'}}"); -+ register(2834, "{Name:'minecraft:white_wall_banner',Properties:{facing:'north'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'north'}}"); -+ register(2835, "{Name:'minecraft:white_wall_banner',Properties:{facing:'south'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'south'}}"); -+ register(2836, "{Name:'minecraft:white_wall_banner',Properties:{facing:'west'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'west'}}"); -+ register(2837, "{Name:'minecraft:white_wall_banner',Properties:{facing:'east'}}", "{Name:'minecraft:wall_banner',Properties:{facing:'east'}}"); -+ register(2848, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'0'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'0'}}"); -+ register(2849, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'1'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'1'}}"); -+ register(2850, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'2'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'2'}}"); -+ register(2851, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'3'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'3'}}"); -+ register(2852, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'4'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'4'}}"); -+ register(2853, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'5'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'5'}}"); -+ register(2854, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'6'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'6'}}"); -+ register(2855, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'7'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'7'}}"); -+ register(2856, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'8'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'8'}}"); -+ register(2857, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'9'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'9'}}"); -+ register(2858, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'10'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'10'}}"); -+ register(2859, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'11'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'11'}}"); -+ register(2860, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'12'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'12'}}"); -+ register(2861, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'13'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'13'}}"); -+ register(2862, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'14'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'14'}}"); -+ register(2863, "{Name:'minecraft:daylight_detector',Properties:{inverted:'true',power:'15'}}", "{Name:'minecraft:daylight_detector_inverted',Properties:{power:'15'}}"); -+ register(2864, "{Name:'minecraft:red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'red_sandstone'}}"); -+ register(2865, "{Name:'minecraft:chiseled_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'chiseled_red_sandstone'}}"); -+ register(2866, "{Name:'minecraft:cut_red_sandstone'}", "{Name:'minecraft:red_sandstone',Properties:{type:'smooth_red_sandstone'}}"); -+ register(2880, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(2881, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(2882, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(2883, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(2884, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(2885, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(2886, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(2887, "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:red_sandstone_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(2896, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'double'}}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'false',variant:'red_sandstone'}}"); -+ register(2904, "{Name:'minecraft:smooth_red_sandstone'}", "{Name:'minecraft:double_stone_slab2',Properties:{seamless:'true',variant:'red_sandstone'}}"); -+ register(2912, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'bottom',variant:'red_sandstone'}}"); -+ register(2920, "{Name:'minecraft:red_sandstone_slab',Properties:{type:'top'}}", "{Name:'minecraft:stone_slab2',Properties:{half:'top',variant:'red_sandstone'}}"); -+ register(2928, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2929, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2930, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2931, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2932, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2933, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2934, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2935, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2936, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2937, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2938, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2939, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2940, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2941, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2942, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2943, "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2944, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2945, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2946, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2947, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2948, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2949, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2950, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2951, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2952, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2953, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2954, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2955, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2956, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2957, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2958, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2959, "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:birch_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2960, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2961, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2962, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2963, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2964, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2965, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2966, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2967, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2968, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2969, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2970, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2971, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2972, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2973, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2974, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2975, "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2976, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2977, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2978, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2979, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2980, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2981, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2982, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2983, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2984, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2985, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2986, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2987, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(2988, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2989, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2990, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2991, "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(2992, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2993, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2994, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2995, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'false'}}"); -+ register(2996, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2997, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2998, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'false'}}"); -+ register(2999, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'false'}}"); -+ register(3000, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3001, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3002, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3003, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'false',powered:'true'}}"); -+ register(3004, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'south',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3005, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'west',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3006, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'north',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3007, "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'false',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_fence_gate',Properties:{facing:'east',in_wall:'true',open:'true',powered:'true'}}"); -+ register(3008, "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:spruce_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3024, "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:birch_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3040, "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:jungle_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3056, "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:dark_oak_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3072, "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'false',north:'true',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'false',south:'true',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'false',west:'true'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'false'}}", "{Name:'minecraft:acacia_fence',Properties:{east:'true',north:'true',south:'true',west:'true'}}"); -+ register(3088, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3089, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3090, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3091, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3092, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3093, "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3094, "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3095, "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3096, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3097, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3098, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3099, "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:spruce_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3104, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3105, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3106, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3107, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3108, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3109, "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3110, "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3111, "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3112, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3113, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3114, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3115, "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:birch_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3120, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3121, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3122, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3123, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3124, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3125, "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3126, "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3127, "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3128, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3129, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3130, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3131, "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:jungle_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3136, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3137, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3138, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3139, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3140, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3141, "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3142, "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3143, "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3144, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3145, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3146, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3147, "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:acacia_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3152, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3153, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3154, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3155, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'false',powered:'true'}}"); -+ register(3156, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3157, "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3158, "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3159, "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'lower',hinge:'right',open:'true',powered:'true'}}"); -+ register(3160, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'false'}}"); -+ register(3161, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'false'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'false'}}"); -+ register(3162, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'left',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'left',open:'true',powered:'true'}}"); -+ register(3163, "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'east',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'north',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'south',half:'upper',hinge:'right',open:'true',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'false',powered:'true'}}", "{Name:'minecraft:dark_oak_door',Properties:{facing:'west',half:'upper',hinge:'right',open:'true',powered:'true'}}"); -+ register(3168, "{Name:'minecraft:end_rod',Properties:{facing:'down'}}", "{Name:'minecraft:end_rod',Properties:{facing:'down'}}"); -+ register(3169, "{Name:'minecraft:end_rod',Properties:{facing:'up'}}", "{Name:'minecraft:end_rod',Properties:{facing:'up'}}"); -+ register(3170, "{Name:'minecraft:end_rod',Properties:{facing:'north'}}", "{Name:'minecraft:end_rod',Properties:{facing:'north'}}"); -+ register(3171, "{Name:'minecraft:end_rod',Properties:{facing:'south'}}", "{Name:'minecraft:end_rod',Properties:{facing:'south'}}"); -+ register(3172, "{Name:'minecraft:end_rod',Properties:{facing:'west'}}", "{Name:'minecraft:end_rod',Properties:{facing:'west'}}"); -+ register(3173, "{Name:'minecraft:end_rod',Properties:{facing:'east'}}", "{Name:'minecraft:end_rod',Properties:{facing:'east'}}"); -+ register(3184, "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'false',east:'true',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'false',north:'true',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'false',south:'true',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'false',up:'true',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'false',west:'true'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'false'}}", "{Name:'minecraft:chorus_plant',Properties:{down:'true',east:'true',north:'true',south:'true',up:'true',west:'true'}}"); -+ register(3200, "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'0'}}"); -+ register(3201, "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'1'}}"); -+ register(3202, "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'2'}}"); -+ register(3203, "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'3'}}"); -+ register(3204, "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'4'}}"); -+ register(3205, "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}", "{Name:'minecraft:chorus_flower',Properties:{age:'5'}}"); -+ register(3216, "{Name:'minecraft:purpur_block'}", "{Name:'minecraft:purpur_block'}"); -+ register(3232, "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'y'}}"); -+ register(3236, "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'x'}}"); -+ register(3240, "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}", "{Name:'minecraft:purpur_pillar',Properties:{axis:'z'}}"); -+ register(3248, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'bottom',shape:'straight'}}"); -+ register(3249, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'bottom',shape:'straight'}}"); -+ register(3250, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'bottom',shape:'straight'}}"); -+ register(3251, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'bottom',shape:'straight'}}"); -+ register(3252, "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'east',half:'top',shape:'straight'}}"); -+ register(3253, "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'west',half:'top',shape:'straight'}}"); -+ register(3254, "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'south',half:'top',shape:'straight'}}"); -+ register(3255, "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'inner_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_left'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'outer_right'}}", "{Name:'minecraft:purpur_stairs',Properties:{facing:'north',half:'top',shape:'straight'}}"); -+ register(3264, "{Name:'minecraft:purpur_slab',Properties:{type:'double'}}", "{Name:'minecraft:purpur_double_slab',Properties:{variant:'default'}}"); -+ register(3280, "{Name:'minecraft:purpur_slab',Properties:{type:'bottom'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'bottom',variant:'default'}}"); -+ register(3288, "{Name:'minecraft:purpur_slab',Properties:{type:'top'}}", "{Name:'minecraft:purpur_slab',Properties:{half:'top',variant:'default'}}"); -+ register(3296, "{Name:'minecraft:end_stone_bricks'}", "{Name:'minecraft:end_bricks'}"); -+ register(3312, "{Name:'minecraft:beetroots',Properties:{age:'0'}}", "{Name:'minecraft:beetroots',Properties:{age:'0'}}"); -+ register(3313, "{Name:'minecraft:beetroots',Properties:{age:'1'}}", "{Name:'minecraft:beetroots',Properties:{age:'1'}}"); -+ register(3314, "{Name:'minecraft:beetroots',Properties:{age:'2'}}", "{Name:'minecraft:beetroots',Properties:{age:'2'}}"); -+ register(3315, "{Name:'minecraft:beetroots',Properties:{age:'3'}}", "{Name:'minecraft:beetroots',Properties:{age:'3'}}"); -+ register(3328, "{Name:'minecraft:grass_path'}", "{Name:'minecraft:grass_path'}"); -+ register(3344, "{Name:'minecraft:end_gateway'}", "{Name:'minecraft:end_gateway'}"); -+ register(3360, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(3361, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(3362, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(3363, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(3364, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(3365, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(3368, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(3369, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(3370, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(3371, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(3372, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(3373, "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:repeating_command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(3376, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'down'}}"); -+ register(3377, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'up'}}"); -+ register(3378, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'north'}}"); -+ register(3379, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'south'}}"); -+ register(3380, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'west'}}"); -+ register(3381, "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'false',facing:'east'}}"); -+ register(3384, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'down'}}"); -+ register(3385, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'up'}}"); -+ register(3386, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'north'}}"); -+ register(3387, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'south'}}"); -+ register(3388, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'west'}}"); -+ register(3389, "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}", "{Name:'minecraft:chain_command_block',Properties:{conditional:'true',facing:'east'}}"); -+ register(3392, "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'0'}}"); -+ register(3393, "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'1'}}"); -+ register(3394, "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'2'}}"); -+ register(3395, "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}", "{Name:'minecraft:frosted_ice',Properties:{age:'3'}}"); -+ register(3408, "{Name:'minecraft:magma_block'}", "{Name:'minecraft:magma'}"); -+ register(3424, "{Name:'minecraft:nether_wart_block'}", "{Name:'minecraft:nether_wart_block'}"); -+ register(3440, "{Name:'minecraft:red_nether_bricks'}", "{Name:'minecraft:red_nether_brick'}"); -+ register(3456, "{Name:'minecraft:bone_block',Properties:{axis:'y'}}", "{Name:'minecraft:bone_block',Properties:{axis:'y'}}"); -+ register(3460, "{Name:'minecraft:bone_block',Properties:{axis:'x'}}", "{Name:'minecraft:bone_block',Properties:{axis:'x'}}"); -+ register(3464, "{Name:'minecraft:bone_block',Properties:{axis:'z'}}", "{Name:'minecraft:bone_block',Properties:{axis:'z'}}"); -+ register(3472, "{Name:'minecraft:structure_void'}", "{Name:'minecraft:structure_void'}"); -+ register(3488, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'false'}}"); -+ register(3489, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'false'}}"); -+ register(3490, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'false'}}"); -+ register(3491, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'false'}}"); -+ register(3492, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'false'}}"); -+ register(3493, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'false'}}"); -+ register(3496, "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'down',powered:'true'}}"); -+ register(3497, "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'up',powered:'true'}}"); -+ register(3498, "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'north',powered:'true'}}"); -+ register(3499, "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'south',powered:'true'}}"); -+ register(3500, "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'west',powered:'true'}}"); -+ register(3501, "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}", "{Name:'minecraft:observer',Properties:{facing:'east',powered:'true'}}"); -+ register(3504, "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'down'}}"); -+ register(3505, "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'up'}}"); -+ register(3506, "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'north'}}"); -+ register(3507, "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'south'}}"); -+ register(3508, "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'west'}}"); -+ register(3509, "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:white_shulker_box',Properties:{facing:'east'}}"); -+ register(3520, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'down'}}"); -+ register(3521, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'up'}}"); -+ register(3522, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'north'}}"); -+ register(3523, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'south'}}"); -+ register(3524, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'west'}}"); -+ register(3525, "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:orange_shulker_box',Properties:{facing:'east'}}"); -+ register(3536, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'down'}}"); -+ register(3537, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'up'}}"); -+ register(3538, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'north'}}"); -+ register(3539, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'south'}}"); -+ register(3540, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'west'}}"); -+ register(3541, "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_shulker_box',Properties:{facing:'east'}}"); -+ register(3552, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'down'}}"); -+ register(3553, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'up'}}"); -+ register(3554, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'north'}}"); -+ register(3555, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'south'}}"); -+ register(3556, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'west'}}"); -+ register(3557, "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_shulker_box',Properties:{facing:'east'}}"); -+ register(3568, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'down'}}"); -+ register(3569, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'up'}}"); -+ register(3570, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'north'}}"); -+ register(3571, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'south'}}"); -+ register(3572, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'west'}}"); -+ register(3573, "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_shulker_box',Properties:{facing:'east'}}"); -+ register(3584, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'down'}}"); -+ register(3585, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'up'}}"); -+ register(3586, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'north'}}"); -+ register(3587, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'south'}}"); -+ register(3588, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'west'}}"); -+ register(3589, "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:lime_shulker_box',Properties:{facing:'east'}}"); -+ register(3600, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'down'}}"); -+ register(3601, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'up'}}"); -+ register(3602, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'north'}}"); -+ register(3603, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'south'}}"); -+ register(3604, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'west'}}"); -+ register(3605, "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:pink_shulker_box',Properties:{facing:'east'}}"); -+ register(3616, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'down'}}"); -+ register(3617, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'up'}}"); -+ register(3618, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'north'}}"); -+ register(3619, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'south'}}"); -+ register(3620, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'west'}}"); -+ register(3621, "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:gray_shulker_box',Properties:{facing:'east'}}"); -+ register(3632, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'down'}}"); -+ register(3633, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'up'}}"); -+ register(3634, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'north'}}"); -+ register(3635, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'south'}}"); -+ register(3636, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'west'}}"); -+ register(3637, "{Name:'minecraft:light_gray_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:silver_shulker_box',Properties:{facing:'east'}}"); -+ register(3648, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'down'}}"); -+ register(3649, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'up'}}"); -+ register(3650, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'north'}}"); -+ register(3651, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'south'}}"); -+ register(3652, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'west'}}"); -+ register(3653, "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_shulker_box',Properties:{facing:'east'}}"); -+ register(3664, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'down'}}"); -+ register(3665, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'up'}}"); -+ register(3666, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'north'}}"); -+ register(3667, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'south'}}"); -+ register(3668, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'west'}}"); -+ register(3669, "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:purple_shulker_box',Properties:{facing:'east'}}"); -+ register(3680, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'down'}}"); -+ register(3681, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'up'}}"); -+ register(3682, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'north'}}"); -+ register(3683, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'south'}}"); -+ register(3684, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'west'}}"); -+ register(3685, "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:blue_shulker_box',Properties:{facing:'east'}}"); -+ register(3696, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'down'}}"); -+ register(3697, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'up'}}"); -+ register(3698, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'north'}}"); -+ register(3699, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'south'}}"); -+ register(3700, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'west'}}"); -+ register(3701, "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:brown_shulker_box',Properties:{facing:'east'}}"); -+ register(3712, "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'down'}}"); -+ register(3713, "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'up'}}"); -+ register(3714, "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'north'}}"); -+ register(3715, "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'south'}}"); -+ register(3716, "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'west'}}"); -+ register(3717, "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:green_shulker_box',Properties:{facing:'east'}}"); -+ register(3728, "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'down'}}"); -+ register(3729, "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'up'}}"); -+ register(3730, "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'north'}}"); -+ register(3731, "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'south'}}"); -+ register(3732, "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'west'}}"); -+ register(3733, "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:red_shulker_box',Properties:{facing:'east'}}"); -+ register(3744, "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'down'}}"); -+ register(3745, "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'up'}}"); -+ register(3746, "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'north'}}"); -+ register(3747, "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'south'}}"); -+ register(3748, "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'west'}}"); -+ register(3749, "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}", "{Name:'minecraft:black_shulker_box',Properties:{facing:'east'}}"); -+ register(3760, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3761, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3762, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3763, "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:white_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3776, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3777, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3778, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3779, "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:orange_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3792, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3793, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3794, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3795, "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:magenta_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3808, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3809, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3810, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3811, "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:light_blue_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3824, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3825, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3826, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3827, "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:yellow_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3840, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3841, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3842, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3843, "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:lime_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3856, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3857, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3858, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3859, "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:pink_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3872, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3873, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3874, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3875, "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:gray_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3888, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3889, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3890, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3891, "{Name:'minecraft:light_gray_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:silver_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3904, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3905, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3906, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3907, "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:cyan_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3920, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3921, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3922, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3923, "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:purple_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3936, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3937, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3938, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3939, "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:blue_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3952, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3953, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3954, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3955, "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:brown_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3968, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3969, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3970, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3971, "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:green_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(3984, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(3985, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(3986, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(3987, "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:red_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(4000, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'south'}}"); -+ register(4001, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'west'}}"); -+ register(4002, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'north'}}"); -+ register(4003, "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}", "{Name:'minecraft:black_glazed_terracotta',Properties:{facing:'east'}}"); -+ register(4016, "{Name:'minecraft:white_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'white'}}"); -+ register(4017, "{Name:'minecraft:orange_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'orange'}}"); -+ register(4018, "{Name:'minecraft:magenta_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'magenta'}}"); -+ register(4019, "{Name:'minecraft:light_blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'light_blue'}}"); -+ register(4020, "{Name:'minecraft:yellow_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'yellow'}}"); -+ register(4021, "{Name:'minecraft:lime_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'lime'}}"); -+ register(4022, "{Name:'minecraft:pink_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'pink'}}"); -+ register(4023, "{Name:'minecraft:gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'gray'}}"); -+ register(4024, "{Name:'minecraft:light_gray_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'silver'}}"); -+ register(4025, "{Name:'minecraft:cyan_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'cyan'}}"); -+ register(4026, "{Name:'minecraft:purple_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'purple'}}"); -+ register(4027, "{Name:'minecraft:blue_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'blue'}}"); -+ register(4028, "{Name:'minecraft:brown_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'brown'}}"); -+ register(4029, "{Name:'minecraft:green_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'green'}}"); -+ register(4030, "{Name:'minecraft:red_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'red'}}"); -+ register(4031, "{Name:'minecraft:black_concrete'}", "{Name:'minecraft:concrete',Properties:{color:'black'}}"); -+ register(4032, "{Name:'minecraft:white_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'white'}}"); -+ register(4033, "{Name:'minecraft:orange_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'orange'}}"); -+ register(4034, "{Name:'minecraft:magenta_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'magenta'}}"); -+ register(4035, "{Name:'minecraft:light_blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'light_blue'}}"); -+ register(4036, "{Name:'minecraft:yellow_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'yellow'}}"); -+ register(4037, "{Name:'minecraft:lime_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'lime'}}"); -+ register(4038, "{Name:'minecraft:pink_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'pink'}}"); -+ register(4039, "{Name:'minecraft:gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'gray'}}"); -+ register(4040, "{Name:'minecraft:light_gray_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'silver'}}"); -+ register(4041, "{Name:'minecraft:cyan_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'cyan'}}"); -+ register(4042, "{Name:'minecraft:purple_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'purple'}}"); -+ register(4043, "{Name:'minecraft:blue_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'blue'}}"); -+ register(4044, "{Name:'minecraft:brown_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'brown'}}"); -+ register(4045, "{Name:'minecraft:green_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'green'}}"); -+ register(4046, "{Name:'minecraft:red_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'red'}}"); -+ register(4047, "{Name:'minecraft:black_concrete_powder'}", "{Name:'minecraft:concrete_powder',Properties:{color:'black'}}"); -+ register(4080, "{Name:'minecraft:structure_block',Properties:{mode:'save'}}", "{Name:'minecraft:structure_block',Properties:{mode:'save'}}"); -+ register(4081, "{Name:'minecraft:structure_block',Properties:{mode:'load'}}", "{Name:'minecraft:structure_block',Properties:{mode:'load'}}"); -+ register(4082, "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}", "{Name:'minecraft:structure_block',Properties:{mode:'corner'}}"); -+ register(4083, "{Name:'minecraft:structure_block',Properties:{mode:'data'}}", "{Name:'minecraft:structure_block',Properties:{mode:'data'}}"); -+ finalizeMaps(); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7ce9cf645ccb2dc796b87858915dba1c3efc3d5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperItemNameV102.java -@@ -0,0 +1,566 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class HelperItemNameV102 { -+ -+ // This class is responsible for mapping the id -> string update in itemstacks and potions -+ -+ private static final Int2ObjectOpenHashMap ITEM_NAMES = new Int2ObjectOpenHashMap() { -+ @Override -+ public String put(final int k, final String o) { -+ final String ret = super.put(k, o); -+ -+ if (ret != null) { -+ throw new IllegalStateException("Mapping already exists for " + k + ": prev: " + ret + ", new: " + o); -+ } -+ -+ return ret; -+ } -+ }; -+ -+ static { -+ ITEM_NAMES.put(0, "minecraft:air"); -+ ITEM_NAMES.put(1, "minecraft:stone"); -+ ITEM_NAMES.put(2, "minecraft:grass"); -+ ITEM_NAMES.put(3, "minecraft:dirt"); -+ ITEM_NAMES.put(4, "minecraft:cobblestone"); -+ ITEM_NAMES.put(5, "minecraft:planks"); -+ ITEM_NAMES.put(6, "minecraft:sapling"); -+ ITEM_NAMES.put(7, "minecraft:bedrock"); -+ ITEM_NAMES.put(8, "minecraft:flowing_water"); -+ ITEM_NAMES.put(9, "minecraft:water"); -+ ITEM_NAMES.put(10, "minecraft:flowing_lava"); -+ ITEM_NAMES.put(11, "minecraft:lava"); -+ ITEM_NAMES.put(12, "minecraft:sand"); -+ ITEM_NAMES.put(13, "minecraft:gravel"); -+ ITEM_NAMES.put(14, "minecraft:gold_ore"); -+ ITEM_NAMES.put(15, "minecraft:iron_ore"); -+ ITEM_NAMES.put(16, "minecraft:coal_ore"); -+ ITEM_NAMES.put(17, "minecraft:log"); -+ ITEM_NAMES.put(18, "minecraft:leaves"); -+ ITEM_NAMES.put(19, "minecraft:sponge"); -+ ITEM_NAMES.put(20, "minecraft:glass"); -+ ITEM_NAMES.put(21, "minecraft:lapis_ore"); -+ ITEM_NAMES.put(22, "minecraft:lapis_block"); -+ ITEM_NAMES.put(23, "minecraft:dispenser"); -+ ITEM_NAMES.put(24, "minecraft:sandstone"); -+ ITEM_NAMES.put(25, "minecraft:noteblock"); -+ ITEM_NAMES.put(27, "minecraft:golden_rail"); -+ ITEM_NAMES.put(28, "minecraft:detector_rail"); -+ ITEM_NAMES.put(29, "minecraft:sticky_piston"); -+ ITEM_NAMES.put(30, "minecraft:web"); -+ ITEM_NAMES.put(31, "minecraft:tallgrass"); -+ ITEM_NAMES.put(32, "minecraft:deadbush"); -+ ITEM_NAMES.put(33, "minecraft:piston"); -+ ITEM_NAMES.put(35, "minecraft:wool"); -+ ITEM_NAMES.put(37, "minecraft:yellow_flower"); -+ ITEM_NAMES.put(38, "minecraft:red_flower"); -+ ITEM_NAMES.put(39, "minecraft:brown_mushroom"); -+ ITEM_NAMES.put(40, "minecraft:red_mushroom"); -+ ITEM_NAMES.put(41, "minecraft:gold_block"); -+ ITEM_NAMES.put(42, "minecraft:iron_block"); -+ ITEM_NAMES.put(43, "minecraft:double_stone_slab"); -+ ITEM_NAMES.put(44, "minecraft:stone_slab"); -+ ITEM_NAMES.put(45, "minecraft:brick_block"); -+ ITEM_NAMES.put(46, "minecraft:tnt"); -+ ITEM_NAMES.put(47, "minecraft:bookshelf"); -+ ITEM_NAMES.put(48, "minecraft:mossy_cobblestone"); -+ ITEM_NAMES.put(49, "minecraft:obsidian"); -+ ITEM_NAMES.put(50, "minecraft:torch"); -+ ITEM_NAMES.put(51, "minecraft:fire"); -+ ITEM_NAMES.put(52, "minecraft:mob_spawner"); -+ ITEM_NAMES.put(53, "minecraft:oak_stairs"); -+ ITEM_NAMES.put(54, "minecraft:chest"); -+ ITEM_NAMES.put(56, "minecraft:diamond_ore"); -+ ITEM_NAMES.put(57, "minecraft:diamond_block"); -+ ITEM_NAMES.put(58, "minecraft:crafting_table"); -+ ITEM_NAMES.put(60, "minecraft:farmland"); -+ ITEM_NAMES.put(61, "minecraft:furnace"); -+ ITEM_NAMES.put(62, "minecraft:lit_furnace"); -+ ITEM_NAMES.put(65, "minecraft:ladder"); -+ ITEM_NAMES.put(66, "minecraft:rail"); -+ ITEM_NAMES.put(67, "minecraft:stone_stairs"); -+ ITEM_NAMES.put(69, "minecraft:lever"); -+ ITEM_NAMES.put(70, "minecraft:stone_pressure_plate"); -+ ITEM_NAMES.put(72, "minecraft:wooden_pressure_plate"); -+ ITEM_NAMES.put(73, "minecraft:redstone_ore"); -+ ITEM_NAMES.put(76, "minecraft:redstone_torch"); -+ ITEM_NAMES.put(77, "minecraft:stone_button"); -+ ITEM_NAMES.put(78, "minecraft:snow_layer"); -+ ITEM_NAMES.put(79, "minecraft:ice"); -+ ITEM_NAMES.put(80, "minecraft:snow"); -+ ITEM_NAMES.put(81, "minecraft:cactus"); -+ ITEM_NAMES.put(82, "minecraft:clay"); -+ ITEM_NAMES.put(84, "minecraft:jukebox"); -+ ITEM_NAMES.put(85, "minecraft:fence"); -+ ITEM_NAMES.put(86, "minecraft:pumpkin"); -+ ITEM_NAMES.put(87, "minecraft:netherrack"); -+ ITEM_NAMES.put(88, "minecraft:soul_sand"); -+ ITEM_NAMES.put(89, "minecraft:glowstone"); -+ ITEM_NAMES.put(90, "minecraft:portal"); -+ ITEM_NAMES.put(91, "minecraft:lit_pumpkin"); -+ ITEM_NAMES.put(95, "minecraft:stained_glass"); -+ ITEM_NAMES.put(96, "minecraft:trapdoor"); -+ ITEM_NAMES.put(97, "minecraft:monster_egg"); -+ ITEM_NAMES.put(98, "minecraft:stonebrick"); -+ ITEM_NAMES.put(99, "minecraft:brown_mushroom_block"); -+ ITEM_NAMES.put(100, "minecraft:red_mushroom_block"); -+ ITEM_NAMES.put(101, "minecraft:iron_bars"); -+ ITEM_NAMES.put(102, "minecraft:glass_pane"); -+ ITEM_NAMES.put(103, "minecraft:melon_block"); -+ ITEM_NAMES.put(106, "minecraft:vine"); -+ ITEM_NAMES.put(107, "minecraft:fence_gate"); -+ ITEM_NAMES.put(108, "minecraft:brick_stairs"); -+ ITEM_NAMES.put(109, "minecraft:stone_brick_stairs"); -+ ITEM_NAMES.put(110, "minecraft:mycelium"); -+ ITEM_NAMES.put(111, "minecraft:waterlily"); -+ ITEM_NAMES.put(112, "minecraft:nether_brick"); -+ ITEM_NAMES.put(113, "minecraft:nether_brick_fence"); -+ ITEM_NAMES.put(114, "minecraft:nether_brick_stairs"); -+ ITEM_NAMES.put(116, "minecraft:enchanting_table"); -+ ITEM_NAMES.put(119, "minecraft:end_portal"); -+ ITEM_NAMES.put(120, "minecraft:end_portal_frame"); -+ ITEM_NAMES.put(121, "minecraft:end_stone"); -+ ITEM_NAMES.put(122, "minecraft:dragon_egg"); -+ ITEM_NAMES.put(123, "minecraft:redstone_lamp"); -+ ITEM_NAMES.put(125, "minecraft:double_wooden_slab"); -+ ITEM_NAMES.put(126, "minecraft:wooden_slab"); -+ ITEM_NAMES.put(127, "minecraft:cocoa"); -+ ITEM_NAMES.put(128, "minecraft:sandstone_stairs"); -+ ITEM_NAMES.put(129, "minecraft:emerald_ore"); -+ ITEM_NAMES.put(130, "minecraft:ender_chest"); -+ ITEM_NAMES.put(131, "minecraft:tripwire_hook"); -+ ITEM_NAMES.put(133, "minecraft:emerald_block"); -+ ITEM_NAMES.put(134, "minecraft:spruce_stairs"); -+ ITEM_NAMES.put(135, "minecraft:birch_stairs"); -+ ITEM_NAMES.put(136, "minecraft:jungle_stairs"); -+ ITEM_NAMES.put(137, "minecraft:command_block"); -+ ITEM_NAMES.put(138, "minecraft:beacon"); -+ ITEM_NAMES.put(139, "minecraft:cobblestone_wall"); -+ ITEM_NAMES.put(141, "minecraft:carrots"); -+ ITEM_NAMES.put(142, "minecraft:potatoes"); -+ ITEM_NAMES.put(143, "minecraft:wooden_button"); -+ ITEM_NAMES.put(145, "minecraft:anvil"); -+ ITEM_NAMES.put(146, "minecraft:trapped_chest"); -+ ITEM_NAMES.put(147, "minecraft:light_weighted_pressure_plate"); -+ ITEM_NAMES.put(148, "minecraft:heavy_weighted_pressure_plate"); -+ ITEM_NAMES.put(151, "minecraft:daylight_detector"); -+ ITEM_NAMES.put(152, "minecraft:redstone_block"); -+ ITEM_NAMES.put(153, "minecraft:quartz_ore"); -+ ITEM_NAMES.put(154, "minecraft:hopper"); -+ ITEM_NAMES.put(155, "minecraft:quartz_block"); -+ ITEM_NAMES.put(156, "minecraft:quartz_stairs"); -+ ITEM_NAMES.put(157, "minecraft:activator_rail"); -+ ITEM_NAMES.put(158, "minecraft:dropper"); -+ ITEM_NAMES.put(159, "minecraft:stained_hardened_clay"); -+ ITEM_NAMES.put(160, "minecraft:stained_glass_pane"); -+ ITEM_NAMES.put(161, "minecraft:leaves2"); -+ ITEM_NAMES.put(162, "minecraft:log2"); -+ ITEM_NAMES.put(163, "minecraft:acacia_stairs"); -+ ITEM_NAMES.put(164, "minecraft:dark_oak_stairs"); -+ ITEM_NAMES.put(170, "minecraft:hay_block"); -+ ITEM_NAMES.put(171, "minecraft:carpet"); -+ ITEM_NAMES.put(172, "minecraft:hardened_clay"); -+ ITEM_NAMES.put(173, "minecraft:coal_block"); -+ ITEM_NAMES.put(174, "minecraft:packed_ice"); -+ ITEM_NAMES.put(175, "minecraft:double_plant"); -+ ITEM_NAMES.put(256, "minecraft:iron_shovel"); -+ ITEM_NAMES.put(257, "minecraft:iron_pickaxe"); -+ ITEM_NAMES.put(258, "minecraft:iron_axe"); -+ ITEM_NAMES.put(259, "minecraft:flint_and_steel"); -+ ITEM_NAMES.put(260, "minecraft:apple"); -+ ITEM_NAMES.put(261, "minecraft:bow"); -+ ITEM_NAMES.put(262, "minecraft:arrow"); -+ ITEM_NAMES.put(263, "minecraft:coal"); -+ ITEM_NAMES.put(264, "minecraft:diamond"); -+ ITEM_NAMES.put(265, "minecraft:iron_ingot"); -+ ITEM_NAMES.put(266, "minecraft:gold_ingot"); -+ ITEM_NAMES.put(267, "minecraft:iron_sword"); -+ ITEM_NAMES.put(268, "minecraft:wooden_sword"); -+ ITEM_NAMES.put(269, "minecraft:wooden_shovel"); -+ ITEM_NAMES.put(270, "minecraft:wooden_pickaxe"); -+ ITEM_NAMES.put(271, "minecraft:wooden_axe"); -+ ITEM_NAMES.put(272, "minecraft:stone_sword"); -+ ITEM_NAMES.put(273, "minecraft:stone_shovel"); -+ ITEM_NAMES.put(274, "minecraft:stone_pickaxe"); -+ ITEM_NAMES.put(275, "minecraft:stone_axe"); -+ ITEM_NAMES.put(276, "minecraft:diamond_sword"); -+ ITEM_NAMES.put(277, "minecraft:diamond_shovel"); -+ ITEM_NAMES.put(278, "minecraft:diamond_pickaxe"); -+ ITEM_NAMES.put(279, "minecraft:diamond_axe"); -+ ITEM_NAMES.put(280, "minecraft:stick"); -+ ITEM_NAMES.put(281, "minecraft:bowl"); -+ ITEM_NAMES.put(282, "minecraft:mushroom_stew"); -+ ITEM_NAMES.put(283, "minecraft:golden_sword"); -+ ITEM_NAMES.put(284, "minecraft:golden_shovel"); -+ ITEM_NAMES.put(285, "minecraft:golden_pickaxe"); -+ ITEM_NAMES.put(286, "minecraft:golden_axe"); -+ ITEM_NAMES.put(287, "minecraft:string"); -+ ITEM_NAMES.put(288, "minecraft:feather"); -+ ITEM_NAMES.put(289, "minecraft:gunpowder"); -+ ITEM_NAMES.put(290, "minecraft:wooden_hoe"); -+ ITEM_NAMES.put(291, "minecraft:stone_hoe"); -+ ITEM_NAMES.put(292, "minecraft:iron_hoe"); -+ ITEM_NAMES.put(293, "minecraft:diamond_hoe"); -+ ITEM_NAMES.put(294, "minecraft:golden_hoe"); -+ ITEM_NAMES.put(295, "minecraft:wheat_seeds"); -+ ITEM_NAMES.put(296, "minecraft:wheat"); -+ ITEM_NAMES.put(297, "minecraft:bread"); -+ ITEM_NAMES.put(298, "minecraft:leather_helmet"); -+ ITEM_NAMES.put(299, "minecraft:leather_chestplate"); -+ ITEM_NAMES.put(300, "minecraft:leather_leggings"); -+ ITEM_NAMES.put(301, "minecraft:leather_boots"); -+ ITEM_NAMES.put(302, "minecraft:chainmail_helmet"); -+ ITEM_NAMES.put(303, "minecraft:chainmail_chestplate"); -+ ITEM_NAMES.put(304, "minecraft:chainmail_leggings"); -+ ITEM_NAMES.put(305, "minecraft:chainmail_boots"); -+ ITEM_NAMES.put(306, "minecraft:iron_helmet"); -+ ITEM_NAMES.put(307, "minecraft:iron_chestplate"); -+ ITEM_NAMES.put(308, "minecraft:iron_leggings"); -+ ITEM_NAMES.put(309, "minecraft:iron_boots"); -+ ITEM_NAMES.put(310, "minecraft:diamond_helmet"); -+ ITEM_NAMES.put(311, "minecraft:diamond_chestplate"); -+ ITEM_NAMES.put(312, "minecraft:diamond_leggings"); -+ ITEM_NAMES.put(313, "minecraft:diamond_boots"); -+ ITEM_NAMES.put(314, "minecraft:golden_helmet"); -+ ITEM_NAMES.put(315, "minecraft:golden_chestplate"); -+ ITEM_NAMES.put(316, "minecraft:golden_leggings"); -+ ITEM_NAMES.put(317, "minecraft:golden_boots"); -+ ITEM_NAMES.put(318, "minecraft:flint"); -+ ITEM_NAMES.put(319, "minecraft:porkchop"); -+ ITEM_NAMES.put(320, "minecraft:cooked_porkchop"); -+ ITEM_NAMES.put(321, "minecraft:painting"); -+ ITEM_NAMES.put(322, "minecraft:golden_apple"); -+ ITEM_NAMES.put(323, "minecraft:sign"); -+ ITEM_NAMES.put(324, "minecraft:wooden_door"); -+ ITEM_NAMES.put(325, "minecraft:bucket"); -+ ITEM_NAMES.put(326, "minecraft:water_bucket"); -+ ITEM_NAMES.put(327, "minecraft:lava_bucket"); -+ ITEM_NAMES.put(328, "minecraft:minecart"); -+ ITEM_NAMES.put(329, "minecraft:saddle"); -+ ITEM_NAMES.put(330, "minecraft:iron_door"); -+ ITEM_NAMES.put(331, "minecraft:redstone"); -+ ITEM_NAMES.put(332, "minecraft:snowball"); -+ ITEM_NAMES.put(333, "minecraft:boat"); -+ ITEM_NAMES.put(334, "minecraft:leather"); -+ ITEM_NAMES.put(335, "minecraft:milk_bucket"); -+ ITEM_NAMES.put(336, "minecraft:brick"); -+ ITEM_NAMES.put(337, "minecraft:clay_ball"); -+ ITEM_NAMES.put(338, "minecraft:reeds"); -+ ITEM_NAMES.put(339, "minecraft:paper"); -+ ITEM_NAMES.put(340, "minecraft:book"); -+ ITEM_NAMES.put(341, "minecraft:slime_ball"); -+ ITEM_NAMES.put(342, "minecraft:chest_minecart"); -+ ITEM_NAMES.put(343, "minecraft:furnace_minecart"); -+ ITEM_NAMES.put(344, "minecraft:egg"); -+ ITEM_NAMES.put(345, "minecraft:compass"); -+ ITEM_NAMES.put(346, "minecraft:fishing_rod"); -+ ITEM_NAMES.put(347, "minecraft:clock"); -+ ITEM_NAMES.put(348, "minecraft:glowstone_dust"); -+ ITEM_NAMES.put(349, "minecraft:fish"); -+ ITEM_NAMES.put(350, "minecraft:cooked_fish"); // Fix typo, the game never recognized cooked_fished -+ ITEM_NAMES.put(351, "minecraft:dye"); -+ ITEM_NAMES.put(352, "minecraft:bone"); -+ ITEM_NAMES.put(353, "minecraft:sugar"); -+ ITEM_NAMES.put(354, "minecraft:cake"); -+ ITEM_NAMES.put(355, "minecraft:bed"); -+ ITEM_NAMES.put(356, "minecraft:repeater"); -+ ITEM_NAMES.put(357, "minecraft:cookie"); -+ ITEM_NAMES.put(358, "minecraft:filled_map"); -+ ITEM_NAMES.put(359, "minecraft:shears"); -+ ITEM_NAMES.put(360, "minecraft:melon"); -+ ITEM_NAMES.put(361, "minecraft:pumpkin_seeds"); -+ ITEM_NAMES.put(362, "minecraft:melon_seeds"); -+ ITEM_NAMES.put(363, "minecraft:beef"); -+ ITEM_NAMES.put(364, "minecraft:cooked_beef"); -+ ITEM_NAMES.put(365, "minecraft:chicken"); -+ ITEM_NAMES.put(366, "minecraft:cooked_chicken"); -+ ITEM_NAMES.put(367, "minecraft:rotten_flesh"); -+ ITEM_NAMES.put(368, "minecraft:ender_pearl"); -+ ITEM_NAMES.put(369, "minecraft:blaze_rod"); -+ ITEM_NAMES.put(370, "minecraft:ghast_tear"); -+ ITEM_NAMES.put(371, "minecraft:gold_nugget"); -+ ITEM_NAMES.put(372, "minecraft:nether_wart"); -+ ITEM_NAMES.put(373, "minecraft:potion"); -+ ITEM_NAMES.put(374, "minecraft:glass_bottle"); -+ ITEM_NAMES.put(375, "minecraft:spider_eye"); -+ ITEM_NAMES.put(376, "minecraft:fermented_spider_eye"); -+ ITEM_NAMES.put(377, "minecraft:blaze_powder"); -+ ITEM_NAMES.put(378, "minecraft:magma_cream"); -+ ITEM_NAMES.put(379, "minecraft:brewing_stand"); -+ ITEM_NAMES.put(380, "minecraft:cauldron"); -+ ITEM_NAMES.put(381, "minecraft:ender_eye"); -+ ITEM_NAMES.put(382, "minecraft:speckled_melon"); -+ ITEM_NAMES.put(383, "minecraft:spawn_egg"); -+ ITEM_NAMES.put(384, "minecraft:experience_bottle"); -+ ITEM_NAMES.put(385, "minecraft:fire_charge"); -+ ITEM_NAMES.put(386, "minecraft:writable_book"); -+ ITEM_NAMES.put(387, "minecraft:written_book"); -+ ITEM_NAMES.put(388, "minecraft:emerald"); -+ ITEM_NAMES.put(389, "minecraft:item_frame"); -+ ITEM_NAMES.put(390, "minecraft:flower_pot"); -+ ITEM_NAMES.put(391, "minecraft:carrot"); -+ ITEM_NAMES.put(392, "minecraft:potato"); -+ ITEM_NAMES.put(393, "minecraft:baked_potato"); -+ ITEM_NAMES.put(394, "minecraft:poisonous_potato"); -+ ITEM_NAMES.put(395, "minecraft:map"); -+ ITEM_NAMES.put(396, "minecraft:golden_carrot"); -+ ITEM_NAMES.put(397, "minecraft:skull"); -+ ITEM_NAMES.put(398, "minecraft:carrot_on_a_stick"); -+ ITEM_NAMES.put(399, "minecraft:nether_star"); -+ ITEM_NAMES.put(400, "minecraft:pumpkin_pie"); -+ ITEM_NAMES.put(401, "minecraft:fireworks"); -+ ITEM_NAMES.put(402, "minecraft:firework_charge"); -+ ITEM_NAMES.put(403, "minecraft:enchanted_book"); -+ ITEM_NAMES.put(404, "minecraft:comparator"); -+ ITEM_NAMES.put(405, "minecraft:netherbrick"); -+ ITEM_NAMES.put(406, "minecraft:quartz"); -+ ITEM_NAMES.put(407, "minecraft:tnt_minecart"); -+ ITEM_NAMES.put(408, "minecraft:hopper_minecart"); -+ ITEM_NAMES.put(417, "minecraft:iron_horse_armor"); -+ ITEM_NAMES.put(418, "minecraft:golden_horse_armor"); -+ ITEM_NAMES.put(419, "minecraft:diamond_horse_armor"); -+ ITEM_NAMES.put(420, "minecraft:lead"); -+ ITEM_NAMES.put(421, "minecraft:name_tag"); -+ ITEM_NAMES.put(422, "minecraft:command_block_minecart"); -+ ITEM_NAMES.put(2256, "minecraft:record_13"); -+ ITEM_NAMES.put(2257, "minecraft:record_cat"); -+ ITEM_NAMES.put(2258, "minecraft:record_blocks"); -+ ITEM_NAMES.put(2259, "minecraft:record_chirp"); -+ ITEM_NAMES.put(2260, "minecraft:record_far"); -+ ITEM_NAMES.put(2261, "minecraft:record_mall"); -+ ITEM_NAMES.put(2262, "minecraft:record_mellohi"); -+ ITEM_NAMES.put(2263, "minecraft:record_stal"); -+ ITEM_NAMES.put(2264, "minecraft:record_strad"); -+ ITEM_NAMES.put(2265, "minecraft:record_ward"); -+ ITEM_NAMES.put(2266, "minecraft:record_11"); -+ ITEM_NAMES.put(2267, "minecraft:record_wait"); -+ // https://github.com/starlis/empirecraft/commit/2da59d1901407fc0c135ef0458c0fe9b016570b3 -+ // It's likely that this is a result of old CB/Spigot behavior still writing ids into items as ints. -+ // These ids do not appear to be used by regular MC anyways, so I do not see the harm of porting it here. -+ // Extras can be added if needed -+ String[] extra = new String[4_000]; -+ // EMC start -+ extra[409] = "minecraft:prismarine_shard"; -+ extra[410] = "minecraft:prismarine_crystals"; -+ extra[411] = "minecraft:rabbit"; -+ extra[412] = "minecraft:cooked_rabbit"; -+ extra[413] = "minecraft:rabbit_stew"; -+ extra[414] = "minecraft:rabbit_foot"; -+ extra[415] = "minecraft:rabbit_hide"; -+ extra[416] = "minecraft:armor_stand"; -+ extra[423] = "minecraft:mutton"; -+ extra[424] = "minecraft:cooked_mutton"; -+ extra[425] = "minecraft:banner"; -+ extra[426] = "minecraft:end_crystal"; -+ extra[427] = "minecraft:spruce_door"; -+ extra[428] = "minecraft:birch_door"; -+ extra[429] = "minecraft:jungle_door"; -+ extra[430] = "minecraft:acacia_door"; -+ extra[431] = "minecraft:dark_oak_door"; -+ extra[432] = "minecraft:chorus_fruit"; -+ extra[433] = "minecraft:chorus_fruit_popped"; -+ extra[434] = "minecraft:beetroot"; -+ extra[435] = "minecraft:beetroot_seeds"; -+ extra[436] = "minecraft:beetroot_soup"; -+ extra[437] = "minecraft:dragon_breath"; -+ extra[438] = "minecraft:splash_potion"; -+ extra[439] = "minecraft:spectral_arrow"; -+ extra[440] = "minecraft:tipped_arrow"; -+ extra[441] = "minecraft:lingering_potion"; -+ extra[442] = "minecraft:shield"; -+ extra[443] = "minecraft:elytra"; -+ extra[444] = "minecraft:spruce_boat"; -+ extra[445] = "minecraft:birch_boat"; -+ extra[446] = "minecraft:jungle_boat"; -+ extra[447] = "minecraft:acacia_boat"; -+ extra[448] = "minecraft:dark_oak_boat"; -+ extra[449] = "minecraft:totem_of_undying"; -+ extra[450] = "minecraft:shulker_shell"; -+ extra[452] = "minecraft:iron_nugget"; -+ extra[453] = "minecraft:knowledge_book"; -+ // EMC end -+ -+ // dump extra into map -+ for (int i = 0; i < extra.length; ++i) { -+ if (extra[i] != null) { -+ ITEM_NAMES.put(i, extra[i]); -+ } -+ } -+ -+ // Add block ids into conversion as well -+ // Very old versions of the game handled them, but it seems 1.8.8 did not parse them at all, so no conversion -+ // was written. -+ // block ids are only skipped (set to AIR) if there is no 1-1 replacement item. -+ ITEM_NAMES.put(26, "minecraft:bed"); // bed block -+ ITEM_NAMES.put(34, ITEM_NAMES.get(0)); // skip (piston head block) -+ ITEM_NAMES.put(55, "minecraft:redstone"); // redstone wire block -+ ITEM_NAMES.put(59, ITEM_NAMES.get(0)); // skip (wheat crop block) -+ ITEM_NAMES.put(63, "minecraft:sign"); // standing sign -+ ITEM_NAMES.put(64, "minecraft:wooden_door"); // wooden door block -+ ITEM_NAMES.put(68, "minecraft:sign"); // wall sign -+ ITEM_NAMES.put(71, "minecraft:iron_door"); // iron door block -+ ITEM_NAMES.put(74, "minecraft:redstone_ore"); // lit redstone ore block -+ ITEM_NAMES.put(75, "minecraft:redstone_torch"); // unlit redstone torch -+ ITEM_NAMES.put(83, "minecraft:reeds"); // sugar cane block -+ ITEM_NAMES.put(92, "minecraft:cake"); // cake block -+ ITEM_NAMES.put(93, "minecraft:repeater"); // unpowered repeater block -+ ITEM_NAMES.put(94, "minecraft:repeater"); // powered repeater block -+ ITEM_NAMES.put(104, ITEM_NAMES.get(0)); // skip (pumpkin stem) -+ ITEM_NAMES.put(105, ITEM_NAMES.get(0)); // skip (melon stem) -+ ITEM_NAMES.put(115, "minecraft:nether_wart"); // nether wart block -+ ITEM_NAMES.put(117, "minecraft:brewing_stand"); // brewing stand block -+ ITEM_NAMES.put(118, "minecraft:cauldron"); // cauldron block -+ ITEM_NAMES.put(124, "minecraft:redstone_lamp"); // lit redstone lamp block -+ ITEM_NAMES.put(132, ITEM_NAMES.get(0)); // skip (tripwire wire block) -+ ITEM_NAMES.put(140, "minecraft:flower_pot"); // flower pot block -+ ITEM_NAMES.put(144, "minecraft:skull"); // skull block -+ ITEM_NAMES.put(149, "minecraft:comparator"); // unpowered comparator block -+ ITEM_NAMES.put(150, "minecraft:comparator"); // powered comparator block -+ // there are technically more, but at some point even older versions pre id -> name conversion didn't even load them. -+ // (all I know is 1.7.10 does not load them) -+ // and so given even the vanilla game wouldn't load them, there's no conversion path for them - they were never valid. -+ } -+ -+ private static final String[] POTION_NAMES = new String[128]; -+ static { -+ POTION_NAMES[0] = "minecraft:water"; -+ POTION_NAMES[1] = "minecraft:regeneration"; -+ POTION_NAMES[2] = "minecraft:swiftness"; -+ POTION_NAMES[3] = "minecraft:fire_resistance"; -+ POTION_NAMES[4] = "minecraft:poison"; -+ POTION_NAMES[5] = "minecraft:healing"; -+ POTION_NAMES[6] = "minecraft:night_vision"; -+ POTION_NAMES[7] = null; -+ POTION_NAMES[8] = "minecraft:weakness"; -+ POTION_NAMES[9] = "minecraft:strength"; -+ POTION_NAMES[10] = "minecraft:slowness"; -+ POTION_NAMES[11] = "minecraft:leaping"; -+ POTION_NAMES[12] = "minecraft:harming"; -+ POTION_NAMES[13] = "minecraft:water_breathing"; -+ POTION_NAMES[14] = "minecraft:invisibility"; -+ POTION_NAMES[15] = null; -+ POTION_NAMES[16] = "minecraft:awkward"; -+ POTION_NAMES[17] = "minecraft:regeneration"; -+ POTION_NAMES[18] = "minecraft:swiftness"; -+ POTION_NAMES[19] = "minecraft:fire_resistance"; -+ POTION_NAMES[20] = "minecraft:poison"; -+ POTION_NAMES[21] = "minecraft:healing"; -+ POTION_NAMES[22] = "minecraft:night_vision"; -+ POTION_NAMES[23] = null; -+ POTION_NAMES[24] = "minecraft:weakness"; -+ POTION_NAMES[25] = "minecraft:strength"; -+ POTION_NAMES[26] = "minecraft:slowness"; -+ POTION_NAMES[27] = "minecraft:leaping"; -+ POTION_NAMES[28] = "minecraft:harming"; -+ POTION_NAMES[29] = "minecraft:water_breathing"; -+ POTION_NAMES[30] = "minecraft:invisibility"; -+ POTION_NAMES[31] = null; -+ POTION_NAMES[32] = "minecraft:thick"; -+ POTION_NAMES[33] = "minecraft:strong_regeneration"; -+ POTION_NAMES[34] = "minecraft:strong_swiftness"; -+ POTION_NAMES[35] = "minecraft:fire_resistance"; -+ POTION_NAMES[36] = "minecraft:strong_poison"; -+ POTION_NAMES[37] = "minecraft:strong_healing"; -+ POTION_NAMES[38] = "minecraft:night_vision"; -+ POTION_NAMES[39] = null; -+ POTION_NAMES[40] = "minecraft:weakness"; -+ POTION_NAMES[41] = "minecraft:strong_strength"; -+ POTION_NAMES[42] = "minecraft:slowness"; -+ POTION_NAMES[43] = "minecraft:strong_leaping"; -+ POTION_NAMES[44] = "minecraft:strong_harming"; -+ POTION_NAMES[45] = "minecraft:water_breathing"; -+ POTION_NAMES[46] = "minecraft:invisibility"; -+ POTION_NAMES[47] = null; -+ POTION_NAMES[48] = null; -+ POTION_NAMES[49] = "minecraft:strong_regeneration"; -+ POTION_NAMES[50] = "minecraft:strong_swiftness"; -+ POTION_NAMES[51] = "minecraft:fire_resistance"; -+ POTION_NAMES[52] = "minecraft:strong_poison"; -+ POTION_NAMES[53] = "minecraft:strong_healing"; -+ POTION_NAMES[54] = "minecraft:night_vision"; -+ POTION_NAMES[55] = null; -+ POTION_NAMES[56] = "minecraft:weakness"; -+ POTION_NAMES[57] = "minecraft:strong_strength"; -+ POTION_NAMES[58] = "minecraft:slowness"; -+ POTION_NAMES[59] = "minecraft:strong_leaping"; -+ POTION_NAMES[60] = "minecraft:strong_harming"; -+ POTION_NAMES[61] = "minecraft:water_breathing"; -+ POTION_NAMES[62] = "minecraft:invisibility"; -+ POTION_NAMES[63] = null; -+ POTION_NAMES[64] = "minecraft:mundane"; -+ POTION_NAMES[65] = "minecraft:long_regeneration"; -+ POTION_NAMES[66] = "minecraft:long_swiftness"; -+ POTION_NAMES[67] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[68] = "minecraft:long_poison"; -+ POTION_NAMES[69] = "minecraft:healing"; -+ POTION_NAMES[70] = "minecraft:long_night_vision"; -+ POTION_NAMES[71] = null; -+ POTION_NAMES[72] = "minecraft:long_weakness"; -+ POTION_NAMES[73] = "minecraft:long_strength"; -+ POTION_NAMES[74] = "minecraft:long_slowness"; -+ POTION_NAMES[75] = "minecraft:long_leaping"; -+ POTION_NAMES[76] = "minecraft:harming"; -+ POTION_NAMES[77] = "minecraft:long_water_breathing"; -+ POTION_NAMES[78] = "minecraft:long_invisibility"; -+ POTION_NAMES[79] = null; -+ POTION_NAMES[80] = "minecraft:awkward"; -+ POTION_NAMES[81] = "minecraft:long_regeneration"; -+ POTION_NAMES[82] = "minecraft:long_swiftness"; -+ POTION_NAMES[83] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[84] = "minecraft:long_poison"; -+ POTION_NAMES[85] = "minecraft:healing"; -+ POTION_NAMES[86] = "minecraft:long_night_vision"; -+ POTION_NAMES[87] = null; -+ POTION_NAMES[88] = "minecraft:long_weakness"; -+ POTION_NAMES[89] = "minecraft:long_strength"; -+ POTION_NAMES[90] = "minecraft:long_slowness"; -+ POTION_NAMES[91] = "minecraft:long_leaping"; -+ POTION_NAMES[92] = "minecraft:harming"; -+ POTION_NAMES[93] = "minecraft:long_water_breathing"; -+ POTION_NAMES[94] = "minecraft:long_invisibility"; -+ POTION_NAMES[95] = null; -+ POTION_NAMES[96] = "minecraft:thick"; -+ POTION_NAMES[97] = "minecraft:regeneration"; -+ POTION_NAMES[98] = "minecraft:swiftness"; -+ POTION_NAMES[99] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[100] = "minecraft:poison"; -+ POTION_NAMES[101] = "minecraft:strong_healing"; -+ POTION_NAMES[102] = "minecraft:long_night_vision"; -+ POTION_NAMES[103] = null; -+ POTION_NAMES[104] = "minecraft:long_weakness"; -+ POTION_NAMES[105] = "minecraft:strength"; -+ POTION_NAMES[106] = "minecraft:long_slowness"; -+ POTION_NAMES[107] = "minecraft:leaping"; -+ POTION_NAMES[108] = "minecraft:strong_harming"; -+ POTION_NAMES[109] = "minecraft:long_water_breathing"; -+ POTION_NAMES[110] = "minecraft:long_invisibility"; -+ POTION_NAMES[111] = null; -+ POTION_NAMES[112] = null; -+ POTION_NAMES[113] = "minecraft:regeneration"; -+ POTION_NAMES[114] = "minecraft:swiftness"; -+ POTION_NAMES[115] = "minecraft:long_fire_resistance"; -+ POTION_NAMES[116] = "minecraft:poison"; -+ POTION_NAMES[117] = "minecraft:strong_healing"; -+ POTION_NAMES[118] = "minecraft:long_night_vision"; -+ POTION_NAMES[119] = null; -+ POTION_NAMES[120] = "minecraft:long_weakness"; -+ POTION_NAMES[121] = "minecraft:strength"; -+ POTION_NAMES[122] = "minecraft:long_slowness"; -+ POTION_NAMES[123] = "minecraft:leaping"; -+ POTION_NAMES[124] = "minecraft:strong_harming"; -+ POTION_NAMES[125] = "minecraft:long_water_breathing"; -+ POTION_NAMES[126] = "minecraft:long_invisibility"; -+ POTION_NAMES[127] = null; -+ } -+ -+ // ret is nullable, you are supposed to log when it does not exist, NOT HIDE IT! -+ public static String getNameFromId(final int id) { -+ return ITEM_NAMES.get(id); -+ } -+ -+ public static String getPotionNameFromId(final short id) { -+ return POTION_NAMES[id & 127]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5008c6d28b7f9b730bfaf257a264edcb45c78487 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/HelperSpawnEggNameV105.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+public final class HelperSpawnEggNameV105 { -+ -+ private static final String[] ID_TO_STRING = new String[256]; -+ static { -+ ID_TO_STRING[1] = "Item"; -+ ID_TO_STRING[2] = "XPOrb"; -+ ID_TO_STRING[7] = "ThrownEgg"; -+ ID_TO_STRING[8] = "LeashKnot"; -+ ID_TO_STRING[9] = "Painting"; -+ ID_TO_STRING[10] = "Arrow"; -+ ID_TO_STRING[11] = "Snowball"; -+ ID_TO_STRING[12] = "Fireball"; -+ ID_TO_STRING[13] = "SmallFireball"; -+ ID_TO_STRING[14] = "ThrownEnderpearl"; -+ ID_TO_STRING[15] = "EyeOfEnderSignal"; -+ ID_TO_STRING[16] = "ThrownPotion"; -+ ID_TO_STRING[17] = "ThrownExpBottle"; -+ ID_TO_STRING[18] = "ItemFrame"; -+ ID_TO_STRING[19] = "WitherSkull"; -+ ID_TO_STRING[20] = "PrimedTnt"; -+ ID_TO_STRING[21] = "FallingSand"; -+ ID_TO_STRING[22] = "FireworksRocketEntity"; -+ ID_TO_STRING[23] = "TippedArrow"; -+ ID_TO_STRING[24] = "SpectralArrow"; -+ ID_TO_STRING[25] = "ShulkerBullet"; -+ ID_TO_STRING[26] = "DragonFireball"; -+ ID_TO_STRING[30] = "ArmorStand"; -+ ID_TO_STRING[41] = "Boat"; -+ ID_TO_STRING[42] = "MinecartRideable"; -+ ID_TO_STRING[43] = "MinecartChest"; -+ ID_TO_STRING[44] = "MinecartFurnace"; -+ ID_TO_STRING[45] = "MinecartTNT"; -+ ID_TO_STRING[46] = "MinecartHopper"; -+ ID_TO_STRING[47] = "MinecartSpawner"; -+ ID_TO_STRING[40] = "MinecartCommandBlock"; -+ ID_TO_STRING[48] = "Mob"; -+ ID_TO_STRING[49] = "Monster"; -+ ID_TO_STRING[50] = "Creeper"; -+ ID_TO_STRING[51] = "Skeleton"; -+ ID_TO_STRING[52] = "Spider"; -+ ID_TO_STRING[53] = "Giant"; -+ ID_TO_STRING[54] = "Zombie"; -+ ID_TO_STRING[55] = "Slime"; -+ ID_TO_STRING[56] = "Ghast"; -+ ID_TO_STRING[57] = "PigZombie"; -+ ID_TO_STRING[58] = "Enderman"; -+ ID_TO_STRING[59] = "CaveSpider"; -+ ID_TO_STRING[60] = "Silverfish"; -+ ID_TO_STRING[61] = "Blaze"; -+ ID_TO_STRING[62] = "LavaSlime"; -+ ID_TO_STRING[63] = "EnderDragon"; -+ ID_TO_STRING[64] = "WitherBoss"; -+ ID_TO_STRING[65] = "Bat"; -+ ID_TO_STRING[66] = "Witch"; -+ ID_TO_STRING[67] = "Endermite"; -+ ID_TO_STRING[68] = "Guardian"; -+ ID_TO_STRING[69] = "Shulker"; -+ ID_TO_STRING[90] = "Pig"; -+ ID_TO_STRING[91] = "Sheep"; -+ ID_TO_STRING[92] = "Cow"; -+ ID_TO_STRING[93] = "Chicken"; -+ ID_TO_STRING[94] = "Squid"; -+ ID_TO_STRING[95] = "Wolf"; -+ ID_TO_STRING[96] = "MushroomCow"; -+ ID_TO_STRING[97] = "SnowMan"; -+ ID_TO_STRING[98] = "Ozelot"; -+ ID_TO_STRING[99] = "VillagerGolem"; -+ ID_TO_STRING[100] = "EntityHorse"; -+ ID_TO_STRING[101] = "Rabbit"; -+ ID_TO_STRING[120] = "Villager"; -+ ID_TO_STRING[200] = "EnderCrystal"; -+ } -+ -+ public static String getSpawnNameFromId(final short id) { -+ return ID_TO_STRING[id & 255]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d1d99bec73595d49eadf0fdeb8d3999ced38762a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/helpers/RenameHelper.java -@@ -0,0 +1,63 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.helpers; -+ -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.function.Function; -+ -+public final class RenameHelper { -+ -+ // assumes no two or more entries are renamed to a single value, otherwise result will be only one of them will win -+ // and there is no defined winner in such a case -+ public static void renameKeys(final MapType data, final Function renamer) { -+ List newKeys = null; -+ List newValues = null; -+ boolean needsRename = false; -+ for (final String key : data.keys()) { -+ final String renamed = renamer.apply(key); -+ if (renamed != null) { -+ newKeys = new ArrayList<>(); -+ newValues = new ArrayList<>(); -+ newValues.add(data.getGeneric(key)); -+ newKeys.add(renamed); -+ data.remove(key); -+ needsRename = true; -+ break; -+ } -+ } -+ -+ if (!needsRename) { -+ return; -+ } -+ -+ for (final String key : new ArrayList<>(data.keys())) { -+ final String renamed = renamer.apply(key); -+ -+ if (renamed != null) { -+ newValues.add(data.getGeneric(key)); -+ newKeys.add(renamed); -+ data.remove(key); -+ } -+ } -+ -+ // insert new keys -+ for (int i = 0, len = newKeys.size(); i < len; ++i) { -+ final String key = newKeys.get(i); -+ final Object value = newValues.get(i); -+ -+ data.setGeneric(key, value); -+ } -+ } -+ -+ // Clobbers anything in toKey if fromKey exists -+ public static void renameSingle(final MapType data, final String fromKey, final String toKey) { -+ final Object value = data.getGeneric(fromKey); -+ if (value != null) { -+ data.remove(fromKey); -+ data.setGeneric(toKey, value); -+ } -+ } -+ -+ private RenameHelper() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94569f0ccff0d3a09eafd4ba73572d9db0a0ac5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemname/ConverterAbstractItemRename.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemname; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import java.util.function.Function; -+ -+public final class ConverterAbstractItemRename { -+ -+ private ConverterAbstractItemRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.ITEM_NAME, renamer); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java -new file mode 100644 -index 0000000000000000000000000000000000000000..21176b8b96be6cb93d3dc1a74ae9f53f1ad4740c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenItemStack.java -@@ -0,0 +1,460 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ConverterFlattenItemStack extends DataConverter, MapType> { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ // Map of "id.damage" -> "flattened id" -+ private static final Map FLATTEN_MAP = new HashMap<>(); -+ static { -+ FLATTEN_MAP.put("minecraft:stone.0", "minecraft:stone"); -+ FLATTEN_MAP.put("minecraft:stone.1", "minecraft:granite"); -+ FLATTEN_MAP.put("minecraft:stone.2", "minecraft:polished_granite"); -+ FLATTEN_MAP.put("minecraft:stone.3", "minecraft:diorite"); -+ FLATTEN_MAP.put("minecraft:stone.4", "minecraft:polished_diorite"); -+ FLATTEN_MAP.put("minecraft:stone.5", "minecraft:andesite"); -+ FLATTEN_MAP.put("minecraft:stone.6", "minecraft:polished_andesite"); -+ FLATTEN_MAP.put("minecraft:dirt.0", "minecraft:dirt"); -+ FLATTEN_MAP.put("minecraft:dirt.1", "minecraft:coarse_dirt"); -+ FLATTEN_MAP.put("minecraft:dirt.2", "minecraft:podzol"); -+ FLATTEN_MAP.put("minecraft:leaves.0", "minecraft:oak_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.1", "minecraft:spruce_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.2", "minecraft:birch_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves.3", "minecraft:jungle_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves2.0", "minecraft:acacia_leaves"); -+ FLATTEN_MAP.put("minecraft:leaves2.1", "minecraft:dark_oak_leaves"); -+ FLATTEN_MAP.put("minecraft:log.0", "minecraft:oak_log"); -+ FLATTEN_MAP.put("minecraft:log.1", "minecraft:spruce_log"); -+ FLATTEN_MAP.put("minecraft:log.2", "minecraft:birch_log"); -+ FLATTEN_MAP.put("minecraft:log.3", "minecraft:jungle_log"); -+ FLATTEN_MAP.put("minecraft:log2.0", "minecraft:acacia_log"); -+ FLATTEN_MAP.put("minecraft:log2.1", "minecraft:dark_oak_log"); -+ FLATTEN_MAP.put("minecraft:sapling.0", "minecraft:oak_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.1", "minecraft:spruce_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.2", "minecraft:birch_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.3", "minecraft:jungle_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.4", "minecraft:acacia_sapling"); -+ FLATTEN_MAP.put("minecraft:sapling.5", "minecraft:dark_oak_sapling"); -+ FLATTEN_MAP.put("minecraft:planks.0", "minecraft:oak_planks"); -+ FLATTEN_MAP.put("minecraft:planks.1", "minecraft:spruce_planks"); -+ FLATTEN_MAP.put("minecraft:planks.2", "minecraft:birch_planks"); -+ FLATTEN_MAP.put("minecraft:planks.3", "minecraft:jungle_planks"); -+ FLATTEN_MAP.put("minecraft:planks.4", "minecraft:acacia_planks"); -+ FLATTEN_MAP.put("minecraft:planks.5", "minecraft:dark_oak_planks"); -+ FLATTEN_MAP.put("minecraft:sand.0", "minecraft:sand"); -+ FLATTEN_MAP.put("minecraft:sand.1", "minecraft:red_sand"); -+ FLATTEN_MAP.put("minecraft:quartz_block.0", "minecraft:quartz_block"); -+ FLATTEN_MAP.put("minecraft:quartz_block.1", "minecraft:chiseled_quartz_block"); -+ FLATTEN_MAP.put("minecraft:quartz_block.2", "minecraft:quartz_pillar"); -+ FLATTEN_MAP.put("minecraft:anvil.0", "minecraft:anvil"); -+ FLATTEN_MAP.put("minecraft:anvil.1", "minecraft:chipped_anvil"); -+ FLATTEN_MAP.put("minecraft:anvil.2", "minecraft:damaged_anvil"); -+ FLATTEN_MAP.put("minecraft:wool.0", "minecraft:white_wool"); -+ FLATTEN_MAP.put("minecraft:wool.1", "minecraft:orange_wool"); -+ FLATTEN_MAP.put("minecraft:wool.2", "minecraft:magenta_wool"); -+ FLATTEN_MAP.put("minecraft:wool.3", "minecraft:light_blue_wool"); -+ FLATTEN_MAP.put("minecraft:wool.4", "minecraft:yellow_wool"); -+ FLATTEN_MAP.put("minecraft:wool.5", "minecraft:lime_wool"); -+ FLATTEN_MAP.put("minecraft:wool.6", "minecraft:pink_wool"); -+ FLATTEN_MAP.put("minecraft:wool.7", "minecraft:gray_wool"); -+ FLATTEN_MAP.put("minecraft:wool.8", "minecraft:light_gray_wool"); -+ FLATTEN_MAP.put("minecraft:wool.9", "minecraft:cyan_wool"); -+ FLATTEN_MAP.put("minecraft:wool.10", "minecraft:purple_wool"); -+ FLATTEN_MAP.put("minecraft:wool.11", "minecraft:blue_wool"); -+ FLATTEN_MAP.put("minecraft:wool.12", "minecraft:brown_wool"); -+ FLATTEN_MAP.put("minecraft:wool.13", "minecraft:green_wool"); -+ FLATTEN_MAP.put("minecraft:wool.14", "minecraft:red_wool"); -+ FLATTEN_MAP.put("minecraft:wool.15", "minecraft:black_wool"); -+ FLATTEN_MAP.put("minecraft:carpet.0", "minecraft:white_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.1", "minecraft:orange_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.2", "minecraft:magenta_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.3", "minecraft:light_blue_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.4", "minecraft:yellow_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.5", "minecraft:lime_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.6", "minecraft:pink_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.7", "minecraft:gray_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.8", "minecraft:light_gray_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.9", "minecraft:cyan_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.10", "minecraft:purple_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.11", "minecraft:blue_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.12", "minecraft:brown_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.13", "minecraft:green_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.14", "minecraft:red_carpet"); -+ FLATTEN_MAP.put("minecraft:carpet.15", "minecraft:black_carpet"); -+ FLATTEN_MAP.put("minecraft:hardened_clay.0", "minecraft:terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.0", "minecraft:white_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.1", "minecraft:orange_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.2", "minecraft:magenta_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.3", "minecraft:light_blue_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.4", "minecraft:yellow_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.5", "minecraft:lime_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.6", "minecraft:pink_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.7", "minecraft:gray_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.8", "minecraft:light_gray_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.9", "minecraft:cyan_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.10", "minecraft:purple_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.11", "minecraft:blue_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.12", "minecraft:brown_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.13", "minecraft:green_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.14", "minecraft:red_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_hardened_clay.15", "minecraft:black_terracotta"); -+ FLATTEN_MAP.put("minecraft:silver_glazed_terracotta.0", "minecraft:light_gray_glazed_terracotta"); -+ FLATTEN_MAP.put("minecraft:stained_glass.0", "minecraft:white_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.1", "minecraft:orange_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.2", "minecraft:magenta_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.3", "minecraft:light_blue_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.4", "minecraft:yellow_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.5", "minecraft:lime_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.6", "minecraft:pink_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.7", "minecraft:gray_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.8", "minecraft:light_gray_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.9", "minecraft:cyan_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.10", "minecraft:purple_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.11", "minecraft:blue_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.12", "minecraft:brown_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.13", "minecraft:green_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.14", "minecraft:red_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass.15", "minecraft:black_stained_glass"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.0", "minecraft:white_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.1", "minecraft:orange_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.2", "minecraft:magenta_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.3", "minecraft:light_blue_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.4", "minecraft:yellow_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.5", "minecraft:lime_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.6", "minecraft:pink_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.7", "minecraft:gray_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.8", "minecraft:light_gray_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.9", "minecraft:cyan_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.10", "minecraft:purple_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.11", "minecraft:blue_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.12", "minecraft:brown_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.13", "minecraft:green_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.14", "minecraft:red_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:stained_glass_pane.15", "minecraft:black_stained_glass_pane"); -+ FLATTEN_MAP.put("minecraft:prismarine.0", "minecraft:prismarine"); -+ FLATTEN_MAP.put("minecraft:prismarine.1", "minecraft:prismarine_bricks"); -+ FLATTEN_MAP.put("minecraft:prismarine.2", "minecraft:dark_prismarine"); -+ FLATTEN_MAP.put("minecraft:concrete.0", "minecraft:white_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.1", "minecraft:orange_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.2", "minecraft:magenta_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.3", "minecraft:light_blue_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.4", "minecraft:yellow_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.5", "minecraft:lime_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.6", "minecraft:pink_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.7", "minecraft:gray_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.8", "minecraft:light_gray_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.9", "minecraft:cyan_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.10", "minecraft:purple_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.11", "minecraft:blue_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.12", "minecraft:brown_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.13", "minecraft:green_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.14", "minecraft:red_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete.15", "minecraft:black_concrete"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.0", "minecraft:white_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.1", "minecraft:orange_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.2", "minecraft:magenta_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.3", "minecraft:light_blue_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.4", "minecraft:yellow_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.5", "minecraft:lime_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.6", "minecraft:pink_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.7", "minecraft:gray_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.8", "minecraft:light_gray_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.9", "minecraft:cyan_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.10", "minecraft:purple_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.11", "minecraft:blue_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.12", "minecraft:brown_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.13", "minecraft:green_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.14", "minecraft:red_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:concrete_powder.15", "minecraft:black_concrete_powder"); -+ FLATTEN_MAP.put("minecraft:cobblestone_wall.0", "minecraft:cobblestone_wall"); -+ FLATTEN_MAP.put("minecraft:cobblestone_wall.1", "minecraft:mossy_cobblestone_wall"); -+ FLATTEN_MAP.put("minecraft:sandstone.0", "minecraft:sandstone"); -+ FLATTEN_MAP.put("minecraft:sandstone.1", "minecraft:chiseled_sandstone"); -+ FLATTEN_MAP.put("minecraft:sandstone.2", "minecraft:cut_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.0", "minecraft:red_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.1", "minecraft:chiseled_red_sandstone"); -+ FLATTEN_MAP.put("minecraft:red_sandstone.2", "minecraft:cut_red_sandstone"); -+ FLATTEN_MAP.put("minecraft:stonebrick.0", "minecraft:stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.1", "minecraft:mossy_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.2", "minecraft:cracked_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:stonebrick.3", "minecraft:chiseled_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.0", "minecraft:infested_stone"); -+ FLATTEN_MAP.put("minecraft:monster_egg.1", "minecraft:infested_cobblestone"); -+ FLATTEN_MAP.put("minecraft:monster_egg.2", "minecraft:infested_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.3", "minecraft:infested_mossy_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.4", "minecraft:infested_cracked_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:monster_egg.5", "minecraft:infested_chiseled_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:yellow_flower.0", "minecraft:dandelion"); -+ FLATTEN_MAP.put("minecraft:red_flower.0", "minecraft:poppy"); -+ FLATTEN_MAP.put("minecraft:red_flower.1", "minecraft:blue_orchid"); -+ FLATTEN_MAP.put("minecraft:red_flower.2", "minecraft:allium"); -+ FLATTEN_MAP.put("minecraft:red_flower.3", "minecraft:azure_bluet"); -+ FLATTEN_MAP.put("minecraft:red_flower.4", "minecraft:red_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.5", "minecraft:orange_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.6", "minecraft:white_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.7", "minecraft:pink_tulip"); -+ FLATTEN_MAP.put("minecraft:red_flower.8", "minecraft:oxeye_daisy"); -+ FLATTEN_MAP.put("minecraft:double_plant.0", "minecraft:sunflower"); -+ FLATTEN_MAP.put("minecraft:double_plant.1", "minecraft:lilac"); -+ FLATTEN_MAP.put("minecraft:double_plant.2", "minecraft:tall_grass"); -+ FLATTEN_MAP.put("minecraft:double_plant.3", "minecraft:large_fern"); -+ FLATTEN_MAP.put("minecraft:double_plant.4", "minecraft:rose_bush"); -+ FLATTEN_MAP.put("minecraft:double_plant.5", "minecraft:peony"); -+ FLATTEN_MAP.put("minecraft:deadbush.0", "minecraft:dead_bush"); -+ FLATTEN_MAP.put("minecraft:tallgrass.0", "minecraft:dead_bush"); -+ FLATTEN_MAP.put("minecraft:tallgrass.1", "minecraft:grass"); -+ FLATTEN_MAP.put("minecraft:tallgrass.2", "minecraft:fern"); -+ FLATTEN_MAP.put("minecraft:sponge.0", "minecraft:sponge"); -+ FLATTEN_MAP.put("minecraft:sponge.1", "minecraft:wet_sponge"); -+ FLATTEN_MAP.put("minecraft:purpur_slab.0", "minecraft:purpur_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.0", "minecraft:stone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.1", "minecraft:sandstone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.2", "minecraft:petrified_oak_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.3", "minecraft:cobblestone_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.4", "minecraft:brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.5", "minecraft:stone_brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.6", "minecraft:nether_brick_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab.7", "minecraft:quartz_slab"); -+ FLATTEN_MAP.put("minecraft:stone_slab2.0", "minecraft:red_sandstone_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.0", "minecraft:oak_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.1", "minecraft:spruce_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.2", "minecraft:birch_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.3", "minecraft:jungle_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.4", "minecraft:acacia_slab"); -+ FLATTEN_MAP.put("minecraft:wooden_slab.5", "minecraft:dark_oak_slab"); -+ FLATTEN_MAP.put("minecraft:coal.0", "minecraft:coal"); -+ FLATTEN_MAP.put("minecraft:coal.1", "minecraft:charcoal"); -+ FLATTEN_MAP.put("minecraft:fish.0", "minecraft:cod"); -+ FLATTEN_MAP.put("minecraft:fish.1", "minecraft:salmon"); -+ FLATTEN_MAP.put("minecraft:fish.2", "minecraft:clownfish"); -+ FLATTEN_MAP.put("minecraft:fish.3", "minecraft:pufferfish"); -+ FLATTEN_MAP.put("minecraft:cooked_fish.0", "minecraft:cooked_cod"); -+ FLATTEN_MAP.put("minecraft:cooked_fish.1", "minecraft:cooked_salmon"); -+ FLATTEN_MAP.put("minecraft:skull.0", "minecraft:skeleton_skull"); -+ FLATTEN_MAP.put("minecraft:skull.1", "minecraft:wither_skeleton_skull"); -+ FLATTEN_MAP.put("minecraft:skull.2", "minecraft:zombie_head"); -+ FLATTEN_MAP.put("minecraft:skull.3", "minecraft:player_head"); -+ FLATTEN_MAP.put("minecraft:skull.4", "minecraft:creeper_head"); -+ FLATTEN_MAP.put("minecraft:skull.5", "minecraft:dragon_head"); -+ FLATTEN_MAP.put("minecraft:golden_apple.0", "minecraft:golden_apple"); -+ FLATTEN_MAP.put("minecraft:golden_apple.1", "minecraft:enchanted_golden_apple"); -+ FLATTEN_MAP.put("minecraft:fireworks.0", "minecraft:firework_rocket"); -+ FLATTEN_MAP.put("minecraft:firework_charge.0", "minecraft:firework_star"); -+ FLATTEN_MAP.put("minecraft:dye.0", "minecraft:ink_sac"); -+ FLATTEN_MAP.put("minecraft:dye.1", "minecraft:rose_red"); -+ FLATTEN_MAP.put("minecraft:dye.2", "minecraft:cactus_green"); -+ FLATTEN_MAP.put("minecraft:dye.3", "minecraft:cocoa_beans"); -+ FLATTEN_MAP.put("minecraft:dye.4", "minecraft:lapis_lazuli"); -+ FLATTEN_MAP.put("minecraft:dye.5", "minecraft:purple_dye"); -+ FLATTEN_MAP.put("minecraft:dye.6", "minecraft:cyan_dye"); -+ FLATTEN_MAP.put("minecraft:dye.7", "minecraft:light_gray_dye"); -+ FLATTEN_MAP.put("minecraft:dye.8", "minecraft:gray_dye"); -+ FLATTEN_MAP.put("minecraft:dye.9", "minecraft:pink_dye"); -+ FLATTEN_MAP.put("minecraft:dye.10", "minecraft:lime_dye"); -+ FLATTEN_MAP.put("minecraft:dye.11", "minecraft:dandelion_yellow"); -+ FLATTEN_MAP.put("minecraft:dye.12", "minecraft:light_blue_dye"); -+ FLATTEN_MAP.put("minecraft:dye.13", "minecraft:magenta_dye"); -+ FLATTEN_MAP.put("minecraft:dye.14", "minecraft:orange_dye"); -+ FLATTEN_MAP.put("minecraft:dye.15", "minecraft:bone_meal"); -+ FLATTEN_MAP.put("minecraft:silver_shulker_box.0", "minecraft:light_gray_shulker_box"); -+ FLATTEN_MAP.put("minecraft:fence.0", "minecraft:oak_fence"); -+ FLATTEN_MAP.put("minecraft:fence_gate.0", "minecraft:oak_fence_gate"); -+ FLATTEN_MAP.put("minecraft:wooden_door.0", "minecraft:oak_door"); -+ FLATTEN_MAP.put("minecraft:boat.0", "minecraft:oak_boat"); -+ FLATTEN_MAP.put("minecraft:lit_pumpkin.0", "minecraft:jack_o_lantern"); -+ FLATTEN_MAP.put("minecraft:pumpkin.0", "minecraft:carved_pumpkin"); -+ FLATTEN_MAP.put("minecraft:trapdoor.0", "minecraft:oak_trapdoor"); -+ FLATTEN_MAP.put("minecraft:nether_brick.0", "minecraft:nether_bricks"); -+ FLATTEN_MAP.put("minecraft:red_nether_brick.0", "minecraft:red_nether_bricks"); -+ FLATTEN_MAP.put("minecraft:netherbrick.0", "minecraft:nether_brick"); -+ FLATTEN_MAP.put("minecraft:wooden_button.0", "minecraft:oak_button"); -+ FLATTEN_MAP.put("minecraft:wooden_pressure_plate.0", "minecraft:oak_pressure_plate"); -+ FLATTEN_MAP.put("minecraft:noteblock.0", "minecraft:note_block"); -+ FLATTEN_MAP.put("minecraft:bed.0", "minecraft:white_bed"); -+ FLATTEN_MAP.put("minecraft:bed.1", "minecraft:orange_bed"); -+ FLATTEN_MAP.put("minecraft:bed.2", "minecraft:magenta_bed"); -+ FLATTEN_MAP.put("minecraft:bed.3", "minecraft:light_blue_bed"); -+ FLATTEN_MAP.put("minecraft:bed.4", "minecraft:yellow_bed"); -+ FLATTEN_MAP.put("minecraft:bed.5", "minecraft:lime_bed"); -+ FLATTEN_MAP.put("minecraft:bed.6", "minecraft:pink_bed"); -+ FLATTEN_MAP.put("minecraft:bed.7", "minecraft:gray_bed"); -+ FLATTEN_MAP.put("minecraft:bed.8", "minecraft:light_gray_bed"); -+ FLATTEN_MAP.put("minecraft:bed.9", "minecraft:cyan_bed"); -+ FLATTEN_MAP.put("minecraft:bed.10", "minecraft:purple_bed"); -+ FLATTEN_MAP.put("minecraft:bed.11", "minecraft:blue_bed"); -+ FLATTEN_MAP.put("minecraft:bed.12", "minecraft:brown_bed"); -+ FLATTEN_MAP.put("minecraft:bed.13", "minecraft:green_bed"); -+ FLATTEN_MAP.put("minecraft:bed.14", "minecraft:red_bed"); -+ FLATTEN_MAP.put("minecraft:bed.15", "minecraft:black_bed"); -+ FLATTEN_MAP.put("minecraft:banner.15", "minecraft:white_banner"); -+ FLATTEN_MAP.put("minecraft:banner.14", "minecraft:orange_banner"); -+ FLATTEN_MAP.put("minecraft:banner.13", "minecraft:magenta_banner"); -+ FLATTEN_MAP.put("minecraft:banner.12", "minecraft:light_blue_banner"); -+ FLATTEN_MAP.put("minecraft:banner.11", "minecraft:yellow_banner"); -+ FLATTEN_MAP.put("minecraft:banner.10", "minecraft:lime_banner"); -+ FLATTEN_MAP.put("minecraft:banner.9", "minecraft:pink_banner"); -+ FLATTEN_MAP.put("minecraft:banner.8", "minecraft:gray_banner"); -+ FLATTEN_MAP.put("minecraft:banner.7", "minecraft:light_gray_banner"); -+ FLATTEN_MAP.put("minecraft:banner.6", "minecraft:cyan_banner"); -+ FLATTEN_MAP.put("minecraft:banner.5", "minecraft:purple_banner"); -+ FLATTEN_MAP.put("minecraft:banner.4", "minecraft:blue_banner"); -+ FLATTEN_MAP.put("minecraft:banner.3", "minecraft:brown_banner"); -+ FLATTEN_MAP.put("minecraft:banner.2", "minecraft:green_banner"); -+ FLATTEN_MAP.put("minecraft:banner.1", "minecraft:red_banner"); -+ FLATTEN_MAP.put("minecraft:banner.0", "minecraft:black_banner"); -+ FLATTEN_MAP.put("minecraft:grass.0", "minecraft:grass_block"); -+ FLATTEN_MAP.put("minecraft:brick_block.0", "minecraft:bricks"); -+ FLATTEN_MAP.put("minecraft:end_bricks.0", "minecraft:end_stone_bricks"); -+ FLATTEN_MAP.put("minecraft:golden_rail.0", "minecraft:powered_rail"); -+ FLATTEN_MAP.put("minecraft:magma.0", "minecraft:magma_block"); -+ FLATTEN_MAP.put("minecraft:quartz_ore.0", "minecraft:nether_quartz_ore"); -+ FLATTEN_MAP.put("minecraft:reeds.0", "minecraft:sugar_cane"); -+ FLATTEN_MAP.put("minecraft:slime.0", "minecraft:slime_block"); -+ FLATTEN_MAP.put("minecraft:stone_stairs.0", "minecraft:cobblestone_stairs"); -+ FLATTEN_MAP.put("minecraft:waterlily.0", "minecraft:lily_pad"); -+ FLATTEN_MAP.put("minecraft:web.0", "minecraft:cobweb"); -+ FLATTEN_MAP.put("minecraft:snow.0", "minecraft:snow_block"); -+ FLATTEN_MAP.put("minecraft:snow_layer.0", "minecraft:snow"); -+ FLATTEN_MAP.put("minecraft:record_11.0", "minecraft:music_disc_11"); -+ FLATTEN_MAP.put("minecraft:record_13.0", "minecraft:music_disc_13"); -+ FLATTEN_MAP.put("minecraft:record_blocks.0", "minecraft:music_disc_blocks"); -+ FLATTEN_MAP.put("minecraft:record_cat.0", "minecraft:music_disc_cat"); -+ FLATTEN_MAP.put("minecraft:record_chirp.0", "minecraft:music_disc_chirp"); -+ FLATTEN_MAP.put("minecraft:record_far.0", "minecraft:music_disc_far"); -+ FLATTEN_MAP.put("minecraft:record_mall.0", "minecraft:music_disc_mall"); -+ FLATTEN_MAP.put("minecraft:record_mellohi.0", "minecraft:music_disc_mellohi"); -+ FLATTEN_MAP.put("minecraft:record_stal.0", "minecraft:music_disc_stal"); -+ FLATTEN_MAP.put("minecraft:record_strad.0", "minecraft:music_disc_strad"); -+ FLATTEN_MAP.put("minecraft:record_wait.0", "minecraft:music_disc_wait"); -+ FLATTEN_MAP.put("minecraft:record_ward.0", "minecraft:music_disc_ward"); -+ } -+ -+ // maps out ids requiring flattening -+ private static final Set IDS_REQUIRING_FLATTENING = new HashSet<>(); -+ static { -+ for (final String key : FLATTEN_MAP.keySet()) { -+ IDS_REQUIRING_FLATTENING.add(key.substring(0, key.indexOf('.'))); -+ } -+ } -+ -+ // Damage tag is moved from the ItemStack base tag to the ItemStack tag, and we only want to migrate that -+ // for items that actually require it for damage purposes (Remember, old damage was used to differentiate item types) -+ // It should be noted that this ID set should not be included in the flattening map, because damage for these items -+ // is actual damage and not a subtype specifier -+ private static final Set ITEMS_WITH_DAMAGE = new HashSet<>(Arrays.asList( -+ "minecraft:bow", -+ "minecraft:carrot_on_a_stick", -+ "minecraft:chainmail_boots", -+ "minecraft:chainmail_chestplate", -+ "minecraft:chainmail_helmet", -+ "minecraft:chainmail_leggings", -+ "minecraft:diamond_axe", -+ "minecraft:diamond_boots", -+ "minecraft:diamond_chestplate", -+ "minecraft:diamond_helmet", -+ "minecraft:diamond_hoe", -+ "minecraft:diamond_leggings", -+ "minecraft:diamond_pickaxe", -+ "minecraft:diamond_shovel", -+ "minecraft:diamond_sword", -+ "minecraft:elytra", -+ "minecraft:fishing_rod", -+ "minecraft:flint_and_steel", -+ "minecraft:golden_axe", -+ "minecraft:golden_boots", -+ "minecraft:golden_chestplate", -+ "minecraft:golden_helmet", -+ "minecraft:golden_hoe", -+ "minecraft:golden_leggings", -+ "minecraft:golden_pickaxe", -+ "minecraft:golden_shovel", -+ "minecraft:golden_sword", -+ "minecraft:iron_axe", -+ "minecraft:iron_boots", -+ "minecraft:iron_chestplate", -+ "minecraft:iron_helmet", -+ "minecraft:iron_hoe", -+ "minecraft:iron_leggings", -+ "minecraft:iron_pickaxe", -+ "minecraft:iron_shovel", -+ "minecraft:iron_sword", -+ "minecraft:leather_boots", -+ "minecraft:leather_chestplate", -+ "minecraft:leather_helmet", -+ "minecraft:leather_leggings", -+ "minecraft:shears", -+ "minecraft:shield", -+ "minecraft:stone_axe", -+ "minecraft:stone_hoe", -+ "minecraft:stone_pickaxe", -+ "minecraft:stone_shovel", -+ "minecraft:stone_sword", -+ "minecraft:wooden_axe", -+ "minecraft:wooden_hoe", -+ "minecraft:wooden_pickaxe", -+ "minecraft:wooden_shovel", -+ "minecraft:wooden_sword" -+ )); -+ -+ public ConverterFlattenItemStack() { -+ super(MCVersions.V17W47A, 4); -+ } -+ -+ public static String flattenItem(final String oldName, final int data) { -+ if (IDS_REQUIRING_FLATTENING.contains(oldName)) { -+ final String flattened = FLATTEN_MAP.get(oldName + '.' + data); -+ return flattened == null ? FLATTEN_MAP.get(oldName.concat(".0")) : flattened; -+ } else { -+ return null; -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ -+ if (id == null) { -+ return null; -+ } -+ -+ final int damage = data.getInt("Damage"); -+ data.remove("Damage"); -+ -+ if (IDS_REQUIRING_FLATTENING.contains(id)) { -+ String remap = FLATTEN_MAP.get(id + '.' + damage); -+ if (remap == null) { -+ remap = FLATTEN_MAP.get(id.concat(".0")); -+ // this shouldn't be null -+ } -+ if (remap != null) { -+ data.setString("id", remap); -+ } else { -+ LOGGER.warn("Item '" + id + "' requires flattening but found no mapping for it! (ConverterFlattenItemStack)"); -+ } -+ } -+ -+ if (damage != 0 && ITEMS_WITH_DAMAGE.contains(id)) { -+ // migrate damage -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ tag.setInt("Damage", damage); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d88b12e6b9e381ba614dc04599a44e472a37ca03 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/itemstack/ConverterFlattenSpawnEgg.java -@@ -0,0 +1,83 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.itemstack; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class ConverterFlattenSpawnEgg extends DataConverter, MapType> { -+ -+ private static final Map ENTITY_ID_TO_NEW_EGG_ID = new HashMap<>(); -+ static { -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:bat", "minecraft:bat_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:blaze", "minecraft:blaze_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cave_spider", "minecraft:cave_spider_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:chicken", "minecraft:chicken_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:cow", "minecraft:cow_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:creeper", "minecraft:creeper_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:donkey", "minecraft:donkey_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:elder_guardian", "minecraft:elder_guardian_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:enderman", "minecraft:enderman_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:endermite", "minecraft:endermite_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:evocation_illager", "minecraft:evocation_illager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ghast", "minecraft:ghast_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:guardian", "minecraft:guardian_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:horse", "minecraft:horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:husk", "minecraft:husk_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:llama", "minecraft:llama_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:magma_cube", "minecraft:magma_cube_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mooshroom", "minecraft:mooshroom_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:mule", "minecraft:mule_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:ocelot", "minecraft:ocelot_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pufferfish", "minecraft:pufferfish_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:parrot", "minecraft:parrot_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:pig", "minecraft:pig_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:polar_bear", "minecraft:polar_bear_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:rabbit", "minecraft:rabbit_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:sheep", "minecraft:sheep_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:shulker", "minecraft:shulker_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:silverfish", "minecraft:silverfish_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton", "minecraft:skeleton_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:skeleton_horse", "minecraft:skeleton_horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:slime", "minecraft:slime_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:spider", "minecraft:spider_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:squid", "minecraft:squid_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:stray", "minecraft:stray_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:turtle", "minecraft:turtle_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vex", "minecraft:vex_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:villager", "minecraft:villager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:vindication_illager", "minecraft:vindication_illager_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:witch", "minecraft:witch_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wither_skeleton", "minecraft:wither_skeleton_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:wolf", "minecraft:wolf_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie", "minecraft:zombie_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_horse", "minecraft:zombie_horse_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_pigman", "minecraft:zombie_pigman_spawn_egg"); -+ ENTITY_ID_TO_NEW_EGG_ID.put("minecraft:zombie_villager", "minecraft:zombie_villager_spawn_egg"); -+ } -+ -+ public ConverterFlattenSpawnEgg() { -+ super(MCVersions.V17W47A,5); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag == null) { -+ return null; -+ } -+ -+ final String id = entityTag.getString("id"); -+ if (id != null) { -+ data.setString("id", ENTITY_ID_TO_NEW_EGG_ID.getOrDefault(id, "minecraft:pig_spawn_egg")); -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..769dd8447976b66dcfc36283ede4ae16f1e4206d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/options/ConverterAbstractOptionsRename.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.options; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractOptionsRename { -+ -+ private ConverterAbstractOptionsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameKeys(data, renamer); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..57e210bf2bb189b15a32899011c4800b19668a5e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterAbstractPOIRename.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.poi; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.function.Function; -+ -+public final class ConverterAbstractPOIRename { -+ -+ private ConverterAbstractPOIRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ final ListType records = section.getList("Records", ObjectType.MAP); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ for (int i = 0, len = records.size(); i < len; ++i) { -+ final MapType record = records.getMap(i); -+ -+ final String type = record.getString("type"); -+ if (type != null) { -+ final String converted = renamer.apply(type); -+ if (converted != null) { -+ record.setString("type", converted); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java -new file mode 100644 -index 0000000000000000000000000000000000000000..36aa9c3eedb3f2e2f577efed3622fed74268bce1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/poi/ConverterPoiDelete.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.poi; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import java.util.function.Predicate; -+ -+public final class ConverterPoiDelete extends DataConverter, MapType> { -+ -+ private final Predicate delete; -+ -+ public ConverterPoiDelete(final int toVersion, final Predicate delete) { -+ super(toVersion); -+ this.delete = delete; -+ } -+ -+ public ConverterPoiDelete(final int toVersion, final int versionStep, final Predicate delete) { -+ super(toVersion, versionStep); -+ this.delete = delete; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ final ListType records = section.getList("Records", ObjectType.MAP); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ for (int i = 0; i < records.size();) { -+ final MapType record = records.getMap(i); -+ -+ final String type = record.getString("type"); -+ if (type != null && this.delete.test(type)) { -+ records.remove(i); -+ continue; -+ } -+ ++i; -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f35cbbd78a629712f9ae3cd5d180269f015a11d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/recipe/ConverterAbstractRecipeRename.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.recipe; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import java.util.function.Function; -+ -+public final class ConverterAbstractRecipeRename { -+ -+ private ConverterAbstractRecipeRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ ConverterAbstractStringValueTypeRename.register(version, subVersion, MCTypeRegistry.RECIPE, renamer); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a1985c85aa9193699d7d20e6f4f11b6e9744ee70 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterAbstractStatsRename.java -@@ -0,0 +1,66 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.stats; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+import java.util.function.Function; -+ -+public final class ConverterAbstractStatsRename { -+ -+ private ConverterAbstractStatsRename() {} -+ -+ public static void register(final int version, final Function renamer) { -+ register(version, 0, renamer); -+ } -+ -+ public static void register(final int version, final int subVersion, final Function renamer) { -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ if (criteriaType == null) { -+ return null; -+ } -+ -+ final String type = criteriaType.getString("type"); -+ if (!"minecraft:custom".equals(type)) { -+ return null; -+ } -+ -+ final String id = criteriaType.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ final String rename = renamer.apply(id); -+ if (rename != null) { -+ criteriaType.setString("id", rename); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STATS.addStructureConverter(new DataConverter<>(version, subVersion) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType stats = data.getMap("stats"); -+ -+ if (stats == null) { -+ return null; -+ } -+ -+ final MapType custom = stats.getMap("minecraft:custom"); -+ if (custom == null) { -+ return null; -+ } -+ -+ RenameHelper.renameKeys(custom, renamer); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java -new file mode 100644 -index 0000000000000000000000000000000000000000..99d2c2c84820295be1f8bb0b43784e58f51a46dd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/converters/stats/ConverterFlattenStats.java -@@ -0,0 +1,94 @@ -+package ca.spottedleaf.dataconverter.minecraft.converters.stats; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.ImmutableSet; -+import org.apache.commons.lang3.StringUtils; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ConverterFlattenStats extends DataConverter, MapType> { -+ -+ private static final Set SKIP = ImmutableSet.builder().add("stat.craftItem.minecraft.spawn_egg").add("stat.useItem.minecraft.spawn_egg").add("stat.breakItem.minecraft.spawn_egg").add("stat.pickup.minecraft.spawn_egg").add("stat.drop.minecraft.spawn_egg").build(); -+ private static final Map CUSTOM_MAP = ImmutableMap.builder().put("stat.leaveGame", "minecraft:leave_game").put("stat.playOneMinute", "minecraft:play_one_minute").put("stat.timeSinceDeath", "minecraft:time_since_death").put("stat.sneakTime", "minecraft:sneak_time").put("stat.walkOneCm", "minecraft:walk_one_cm").put("stat.crouchOneCm", "minecraft:crouch_one_cm").put("stat.sprintOneCm", "minecraft:sprint_one_cm").put("stat.swimOneCm", "minecraft:swim_one_cm").put("stat.fallOneCm", "minecraft:fall_one_cm").put("stat.climbOneCm", "minecraft:climb_one_cm").put("stat.flyOneCm", "minecraft:fly_one_cm").put("stat.diveOneCm", "minecraft:dive_one_cm").put("stat.minecartOneCm", "minecraft:minecart_one_cm").put("stat.boatOneCm", "minecraft:boat_one_cm").put("stat.pigOneCm", "minecraft:pig_one_cm").put("stat.horseOneCm", "minecraft:horse_one_cm").put("stat.aviateOneCm", "minecraft:aviate_one_cm").put("stat.jump", "minecraft:jump").put("stat.drop", "minecraft:drop").put("stat.damageDealt", "minecraft:damage_dealt").put("stat.damageTaken", "minecraft:damage_taken").put("stat.deaths", "minecraft:deaths").put("stat.mobKills", "minecraft:mob_kills").put("stat.animalsBred", "minecraft:animals_bred").put("stat.playerKills", "minecraft:player_kills").put("stat.fishCaught", "minecraft:fish_caught").put("stat.talkedToVillager", "minecraft:talked_to_villager").put("stat.tradedWithVillager", "minecraft:traded_with_villager").put("stat.cakeSlicesEaten", "minecraft:eat_cake_slice").put("stat.cauldronFilled", "minecraft:fill_cauldron").put("stat.cauldronUsed", "minecraft:use_cauldron").put("stat.armorCleaned", "minecraft:clean_armor").put("stat.bannerCleaned", "minecraft:clean_banner").put("stat.brewingstandInteraction", "minecraft:interact_with_brewingstand").put("stat.beaconInteraction", "minecraft:interact_with_beacon").put("stat.dropperInspected", "minecraft:inspect_dropper").put("stat.hopperInspected", "minecraft:inspect_hopper").put("stat.dispenserInspected", "minecraft:inspect_dispenser").put("stat.noteblockPlayed", "minecraft:play_noteblock").put("stat.noteblockTuned", "minecraft:tune_noteblock").put("stat.flowerPotted", "minecraft:pot_flower").put("stat.trappedChestTriggered", "minecraft:trigger_trapped_chest").put("stat.enderchestOpened", "minecraft:open_enderchest").put("stat.itemEnchanted", "minecraft:enchant_item").put("stat.recordPlayed", "minecraft:play_record").put("stat.furnaceInteraction", "minecraft:interact_with_furnace").put("stat.craftingTableInteraction", "minecraft:interact_with_crafting_table").put("stat.chestOpened", "minecraft:open_chest").put("stat.sleepInBed", "minecraft:sleep_in_bed").put("stat.shulkerBoxOpened", "minecraft:open_shulker_box").build(); -+ private static final Map ITEM_KEYS = ImmutableMap.builder().put("stat.craftItem", "minecraft:crafted").put("stat.useItem", "minecraft:used").put("stat.breakItem", "minecraft:broken").put("stat.pickup", "minecraft:picked_up").put("stat.drop", "minecraft:dropped").build(); -+ private static final Map ENTITY_KEYS = ImmutableMap.builder().put("stat.entityKilledBy", "minecraft:killed_by").put("stat.killEntity", "minecraft:killed").build(); -+ private static final Map ENTITIES = ImmutableMap.builder().put("Bat", "minecraft:bat").put("Blaze", "minecraft:blaze").put("CaveSpider", "minecraft:cave_spider").put("Chicken", "minecraft:chicken").put("Cow", "minecraft:cow").put("Creeper", "minecraft:creeper").put("Donkey", "minecraft:donkey").put("ElderGuardian", "minecraft:elder_guardian").put("Enderman", "minecraft:enderman").put("Endermite", "minecraft:endermite").put("EvocationIllager", "minecraft:evocation_illager").put("Ghast", "minecraft:ghast").put("Guardian", "minecraft:guardian").put("Horse", "minecraft:horse").put("Husk", "minecraft:husk").put("Llama", "minecraft:llama").put("LavaSlime", "minecraft:magma_cube").put("MushroomCow", "minecraft:mooshroom").put("Mule", "minecraft:mule").put("Ozelot", "minecraft:ocelot").put("Parrot", "minecraft:parrot").put("Pig", "minecraft:pig").put("PolarBear", "minecraft:polar_bear").put("Rabbit", "minecraft:rabbit").put("Sheep", "minecraft:sheep").put("Shulker", "minecraft:shulker").put("Silverfish", "minecraft:silverfish").put("SkeletonHorse", "minecraft:skeleton_horse").put("Skeleton", "minecraft:skeleton").put("Slime", "minecraft:slime").put("Spider", "minecraft:spider").put("Squid", "minecraft:squid").put("Stray", "minecraft:stray").put("Vex", "minecraft:vex").put("Villager", "minecraft:villager").put("VindicationIllager", "minecraft:vindication_illager").put("Witch", "minecraft:witch").put("WitherSkeleton", "minecraft:wither_skeleton").put("Wolf", "minecraft:wolf").put("ZombieHorse", "minecraft:zombie_horse").put("PigZombie", "minecraft:zombie_pigman").put("ZombieVillager", "minecraft:zombie_villager").put("Zombie", "minecraft:zombie").build(); -+ -+ public ConverterFlattenStats() { -+ super(MCVersions.V17W47A, 6); -+ } -+ -+ private static String upgradeItem(final String itemName) { -+ return ConverterFlattenItemStack.flattenItem(itemName, 0); -+ } -+ -+ private static String upgradeBlock(final String block) { -+ return HelperBlockFlatteningV1450.getNewBlockName(block); -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType stats = Types.NBT.createEmptyMap(); -+ -+ for (final String statKey : data.keys()) { -+ final Number value = data.getNumber(statKey); -+ if (value == null) { -+ continue; -+ } -+ -+ if (SKIP.contains(statKey)) { -+ continue; -+ } -+ -+ final String statType; -+ final String newStatKey; -+ -+ if (CUSTOM_MAP.containsKey(statKey)) { -+ statType = "minecraft:custom"; -+ newStatKey = CUSTOM_MAP.get(statKey); -+ } else { -+ final int i = StringUtils.ordinalIndexOf(statKey, ".", 2); -+ if (i < 0) { -+ continue; -+ } -+ -+ final String key = statKey.substring(0, i); -+ -+ if ("stat.mineBlock".equals(key)) { -+ statType = "minecraft:mined"; -+ newStatKey = upgradeBlock(statKey.substring(i + 1).replace('.', ':')); -+ } else if (ITEM_KEYS.containsKey(key)) { -+ statType = ITEM_KEYS.get(key); -+ final String item = statKey.substring(i + 1).replace('.', ':'); -+ final String upgradedItem = upgradeItem(item); -+ newStatKey = upgradedItem == null ? item : upgradedItem; -+ } else if (ENTITY_KEYS.containsKey(key)) { -+ statType = ENTITY_KEYS.get(key); -+ final String entity = statKey.substring(i + 1).replace('.', ':'); -+ newStatKey = ENTITIES.getOrDefault(entity, entity); -+ } else { -+ continue; -+ } -+ } -+ -+ MapType statTypeMap = stats.getMap(statType); -+ if (statTypeMap == null) { -+ stats.setMap(statType, statTypeMap = Types.NBT.createEmptyMap()); -+ } -+ -+ statTypeMap.setGeneric(newStatKey, value); -+ } -+ -+ data.clear(); -+ -+ data.setMap("stats", stats); -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b2c2b4c4ae83f14639fa53e38f2c75ccd284c2d2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/IDDataType.java -@@ -0,0 +1,166 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+ -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+ -+public class IDDataType extends MCDataType { -+ -+ protected final Map>>> walkersById = new HashMap<>(); -+ -+ public IDDataType(final String name) { -+ super(name); -+ } -+ -+ public void addConverterForId(final String id, final DataConverter, MapType> converter) { -+ this.addStructureConverter(new DataConverter<>(converter.getToVersion(), converter.getVersionStep()) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!id.equals(data.getString("id"))) { -+ return null; -+ } -+ return converter.convert(data, sourceVersion, toVersion); -+ } -+ }); -+ } -+ -+ public void addWalker(final int minVersion, final String id, final DataWalker walker) { -+ this.addWalker(minVersion, 0, id, walker); -+ } -+ -+ public void addWalker(final int minVersion, final int versionStep, final String id, final DataWalker walker) { -+ this.walkersById.computeIfAbsent(id, (final String keyInMap) -> { -+ return new Long2ObjectArraySortedMap<>(); -+ }).computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(walker); -+ } -+ -+ public void copyWalkers(final int minVersion, final String fromId, final String toId) { -+ this.copyWalkers(minVersion, 0, fromId, toId); -+ } -+ -+ public void copyWalkers(final int minVersion, final int versionStep, final String fromId, final String toId) { -+ final long version = DataConverter.encodeVersions(minVersion, versionStep); -+ final Long2ObjectArraySortedMap>> walkersForId = this.walkersById.get(fromId); -+ if (walkersForId == null) { -+ return; -+ } -+ -+ final List> nearest = walkersForId.getFloor(version); -+ -+ if (nearest == null) { -+ return; -+ } -+ -+ for (final DataWalker walker : nearest) { -+ this.addWalker(minVersion, versionStep, toId, walker); -+ } -+ } -+ -+ @Override -+ public MapType convert(MapType data, final long fromVersion, final long toVersion) { -+ MapType ret = null; -+ -+ final List, MapType>> converters = this.structureConverters; -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter, MapType> converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final MapType replace = converter.convert(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ } -+ -+ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); -+ -+ // run pre hooks -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ // run all walkers -+ -+ final List> walkers = this.structureWalkers.getFloor(toVersion); -+ if (walkers != null) { -+ for (int i = 0, len = walkers.size(); i < len; ++i) { -+ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final Long2ObjectArraySortedMap>> walkersByVersion = this.walkersById.get(data.getString("id")); -+ if (walkersByVersion != null) { -+ final List> walkersForId = walkersByVersion.getFloor(toVersion); -+ if (walkersForId != null) { -+ for (int i = 0, len = walkersForId.size(); i < len; ++i) { -+ final MapType replace = walkersForId.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ } -+ -+ // run post hooks -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..76a6e3efa5c69150e8f5e0063cb6357bed1bffae ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCDataType.java -@@ -0,0 +1,129 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+import java.util.ArrayList; -+import java.util.List; -+ -+public class MCDataType extends DataType, MapType> { -+ -+ public final String name; -+ -+ protected final ArrayList, MapType>> structureConverters = new ArrayList<>(); -+ protected final Long2ObjectArraySortedMap>> structureWalkers = new Long2ObjectArraySortedMap<>(); -+ protected final Long2ObjectArraySortedMap, MapType>>> structureHooks = new Long2ObjectArraySortedMap<>(); -+ -+ public MCDataType(final String name) { -+ this.name = name; -+ } -+ -+ public void addStructureConverter(final DataConverter, MapType> converter) { -+ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); -+ this.structureConverters.add(converter); -+ this.structureConverters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); -+ } -+ -+ public void addStructureWalker(final int minVersion, final DataWalker walker) { -+ this.addStructureWalker(minVersion, 0, walker); -+ } -+ -+ public void addStructureWalker(final int minVersion, final int versionStep, final DataWalker walker) { -+ this.structureWalkers.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(walker); -+ } -+ -+ public void addStructureHook(final int minVersion, final DataHook, MapType> hook) { -+ this.addStructureHook(minVersion, 0, hook); -+ } -+ -+ public void addStructureHook(final int minVersion, final int versionStep, final DataHook, MapType> hook) { -+ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(hook); -+ } -+ -+ @Override -+ public MapType convert(MapType data, final long fromVersion, final long toVersion) { -+ MapType ret = null; -+ -+ final List, MapType>> converters = this.structureConverters; -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter, MapType> converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List, MapType>> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final MapType replace = converter.convert(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ } -+ -+ final List, MapType>> hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final MapType replace = hooks.get(k).preHook(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ final List> walkers = this.structureWalkers.getFloor(toVersion); -+ if (walkers != null) { -+ for (int i = 0, len = walkers.size(); i < len; ++i) { -+ final MapType replace = walkers.get(i).walk(data, fromVersion, toVersion); -+ if (replace != null) { -+ ret = data = replace; -+ } -+ } -+ } -+ -+ if (hooks != null) { -+ for (int klen = hooks.size(), k = klen - 1; k >= 0; --k) { -+ final MapType postReplace = hooks.get(k).postHook(data, fromVersion, toVersion); -+ if (postReplace != null) { -+ ret = data = postReplace; -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java -new file mode 100644 -index 0000000000000000000000000000000000000000..602c210df505613482bb4b1bbb782c0d8d6a32c8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCTypeRegistry.java -@@ -0,0 +1,236 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.minecraft.versions.*; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+public final class MCTypeRegistry { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ public static final MCDataType LEVEL = new MCDataType("Level"); -+ public static final MCDataType PLAYER = new MCDataType("Player"); -+ public static final MCDataType CHUNK = new MCDataType("Chunk"); -+ public static final MCDataType HOTBAR = new MCDataType("CreativeHotbar"); -+ public static final MCDataType OPTIONS = new MCDataType("Options"); -+ public static final MCDataType STRUCTURE = new MCDataType("Structure"); -+ public static final MCDataType STATS = new MCDataType("Stats"); -+ public static final MCDataType SAVED_DATA = new MCDataType("SavedData"); -+ public static final MCDataType ADVANCEMENTS = new MCDataType("Advancements"); -+ public static final MCDataType POI_CHUNK = new MCDataType("PoiChunk"); -+ public static final MCDataType ENTITY_CHUNK = new MCDataType("EntityChunk"); -+ public static final IDDataType TILE_ENTITY = new IDDataType("TileEntity"); -+ public static final IDDataType ITEM_STACK = new IDDataType("ItemStack"); -+ public static final MCDataType BLOCK_STATE = new MCDataType("BlockState"); -+ public static final MCValueType ENTITY_NAME = new MCValueType("EntityName"); -+ public static final IDDataType ENTITY = new IDDataType("Entity"); -+ public static final MCValueType BLOCK_NAME = new MCValueType("BlockName"); -+ public static final MCValueType ITEM_NAME = new MCValueType("ItemName"); -+ public static final MCDataType UNTAGGED_SPAWNER = new MCDataType("Spawner"); -+ public static final MCDataType STRUCTURE_FEATURE = new MCDataType("StructureFeature"); -+ public static final MCDataType OBJECTIVE = new MCDataType("Objective"); -+ public static final MCDataType TEAM = new MCDataType("Team"); -+ public static final MCValueType RECIPE = new MCValueType("RecipeName"); -+ public static final MCValueType BIOME = new MCValueType("Biome"); -+ public static final MCDataType WORLD_GEN_SETTINGS = new MCDataType("WorldGenSettings"); -+ public static final MCValueType GAME_EVENT_NAME = new MCValueType("GameEventName"); -+ -+ static { -+ try { -+ registerAll(); -+ } catch (final ThreadDeath thr) { -+ throw thr; -+ } catch (final Throwable thr) { -+ LOGGER.error(LogUtils.FATAL_MARKER, "Failed to register data converters", thr); -+ throw new RuntimeException(thr); -+ } -+ } -+ -+ private static void registerAll() { -+ // General notes: -+ // - Structure converters run before everything. -+ // - ID specific converters run after structure converters. -+ // - Structure walkers run after id specific converters. -+ // - ID specific walkers run after structure walkers. -+ -+ V99.register(); // all legacy data before converters existed -+ V100.register(); // first version with version id -+ V101.register(); -+ V102.register(); -+ V105.register(); -+ V106.register(); -+ V107.register(); -+ V108.register(); -+ V109.register(); -+ V110.register(); -+ V111.register(); -+ V113.register(); -+ V135.register(); -+ V143.register(); -+ V147.register(); -+ V165.register(); -+ V501.register(); -+ V502.register(); -+ V505.register(); -+ V700.register(); -+ V701.register(); -+ V702.register(); -+ V703.register(); -+ V704.register(); -+ V705.register(); -+ V804.register(); -+ V806.register(); -+ V808.register(); -+ V813.register(); -+ V816.register(); -+ V820.register(); -+ V1022.register(); -+ V1125.register(); -+ // END OF LEGACY DATA CONVERTERS -+ -+ // V1.13 -+ V1344.register(); -+ V1446.register(); -+ // START THE FLATTENING -+ V1450.register(); -+ V1451.register(); -+ // END THE FLATTENING -+ -+ V1456.register(); -+ V1458.register(); -+ V1460.register(); -+ V1466.register(); -+ V1470.register(); -+ V1474.register(); -+ V1475.register(); -+ V1480.register(); -+ // V1481 is adding simple block entity -+ V1483.register(); -+ V1484.register(); -+ V1486.register(); -+ V1487.register(); -+ V1488.register(); -+ V1490.register(); -+ V1492.register(); -+ V1494.register(); -+ V1496.register(); -+ V1500.register(); -+ V1501.register(); -+ V1502.register(); -+ V1506.register(); -+ V1510.register(); -+ V1514.register(); -+ V1515.register(); -+ V1624.register(); -+ // V1.14 -+ V1800.register(); -+ V1801.register(); -+ V1802.register(); -+ V1803.register(); -+ V1904.register(); -+ V1905.register(); -+ V1906.register(); -+ // V1909 is just adding a simple block entity (jigsaw) -+ V1911.register(); -+ V1914.register(); -+ V1917.register(); -+ V1918.register(); -+ V1920.register(); -+ V1925.register(); -+ V1928.register(); -+ V1929.register(); -+ V1931.register(); -+ V1936.register(); -+ V1946.register(); -+ V1948.register(); -+ V1953.register(); -+ V1955.register(); -+ V1961.register(); -+ V1963.register(); -+ // V1.15 -+ V2100.register(); -+ V2202.register(); -+ V2209.register(); -+ V2211.register(); -+ V2218.register(); -+ // V1.16 -+ V2501.register(); -+ V2502.register(); -+ V2503.register(); -+ V2505.register(); -+ V2508.register(); -+ V2509.register(); -+ V2511.register(); -+ V2514.register(); -+ V2516.register(); -+ V2518.register(); -+ V2519.register(); -+ V2522.register(); -+ V2523.register(); -+ V2527.register(); -+ V2528.register(); -+ V2529.register(); -+ V2531.register(); -+ V2533.register(); -+ V2535.register(); -+ V2550.register(); -+ V2551.register(); -+ V2552.register(); -+ V2553.register(); -+ V2558.register(); -+ V2568.register(); -+ // V1.17 -+ // WARN: Mojang registers V2671 under 2571, but that version predates 1.16.5? So it looks like a typo... -+ // I changed it to 2671, just so that it's after 1.16.5, but even then this looks misplaced... Thankfully this is -+ // the first datafixer, and all it does is add a walker, so I think even if the version here is just wrong it will -+ // work. -+ V2671.register(); -+ V2679.register(); -+ V2680.register(); -+ V2684.register(); -+ V2686.register(); -+ V2688.register(); -+ V2690.register(); -+ V2691.register(); -+ V2693.register(); -+ V2696.register(); -+ V2700.register(); -+ V2701.register(); -+ V2702.register(); -+ // In reference to V2671, why the fuck is goat being registered again? For this obvious reason, V2704 is absent. -+ V2707.register(); -+ V2710.register(); -+ V2717.register(); -+ // V1.18 -+ V2825.register(); -+ V2831.register(); -+ V2832.register(); -+ V2833.register(); -+ V2838.register(); -+ V2841.register(); -+ V2842.register(); -+ V2843.register(); -+ V2846.register(); -+ V2852.register(); -+ V2967.register(); -+ V2970.register(); -+ // V1.19 -+ // V3076 is registering a simple tile entity (sculk_catalyst) -+ V3077.register(); -+ V3078.register(); -+ V3081.register(); -+ V3082.register(); -+ V3083.register(); -+ V3084.register(); -+ V3086.register(); -+ V3087.register(); -+ V3088.register(); -+ V3090.register(); -+ V3093.register(); -+ V3094.register(); -+ V3097.register(); -+ V3108.register(); -+ } -+ -+ private MCTypeRegistry() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..13c1381261909ef672fbeb665907f01f2d5c1ced ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/datatypes/MCValueType.java -@@ -0,0 +1,86 @@ -+package ca.spottedleaf.dataconverter.minecraft.datatypes; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.minecraft.MCVersionRegistry; -+import ca.spottedleaf.dataconverter.util.Long2ObjectArraySortedMap; -+import java.util.ArrayList; -+import java.util.List; -+ -+public class MCValueType extends DataType { -+ -+ public final String name; -+ -+ protected final ArrayList> converters = new ArrayList<>(); -+ protected final Long2ObjectArraySortedMap>> structureHooks = new Long2ObjectArraySortedMap<>(); -+ -+ public MCValueType(final String name) { -+ this.name = name; -+ } -+ -+ public void addStructureHook(final int minVersion, final DataHook hook) { -+ this.addStructureHook(minVersion, 0, hook); -+ } -+ -+ public void addStructureHook(final int minVersion, final int versionStep, final DataHook hook) { -+ this.structureHooks.computeIfAbsent(DataConverter.encodeVersions(minVersion, versionStep), (final long keyInMap) -> { -+ return new ArrayList<>(); -+ }).add(hook); -+ } -+ -+ public void addConverter(final DataConverter converter) { -+ MCVersionRegistry.checkVersion(converter.getEncodedVersion()); -+ this.converters.add(converter); -+ this.converters.sort(DataConverter.LOWEST_VERSION_COMPARATOR); -+ } -+ -+ @Override -+ public Object convert(final Object data, final long fromVersion, final long toVersion) { -+ Object ret = null; -+ final List> converters = this.converters; -+ -+ for (int i = 0, len = converters.size(); i < len; ++i) { -+ final DataConverter converter = converters.get(i); -+ final long converterVersion = converter.getEncodedVersion(); -+ -+ if (converterVersion <= fromVersion) { -+ continue; -+ } -+ -+ if (converterVersion > toVersion) { -+ break; -+ } -+ -+ List> hooks = this.structureHooks.getFloor(converterVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final Object replace = hooks.get(k).preHook(ret == null ? data : ret, fromVersion, toVersion); -+ if (replace != null) { -+ ret = replace; -+ } -+ } -+ } -+ -+ final Object converted = converter.convert(ret == null ? data : ret, fromVersion, toVersion); -+ if (converted != null) { -+ ret = converted; -+ } -+ -+ // possibly new data format, update hooks -+ hooks = this.structureHooks.getFloor(toVersion); -+ -+ if (hooks != null) { -+ for (int k = 0, klen = hooks.size(); k < klen; ++k) { -+ final Object replace = hooks.get(k).postHook(ret == null ? data : ret, fromVersion, toVersion); -+ if (replace != null) { -+ ret = replace; -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java -new file mode 100644 -index 0000000000000000000000000000000000000000..26a03007a4386ff037a1ae50045d0c44dd438235 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookEnforceNamespacedID.java -@@ -0,0 +1,35 @@ -+package ca.spottedleaf.dataconverter.minecraft.hooks; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public class DataHookEnforceNamespacedID implements DataHook, MapType> { -+ -+ private final String path; -+ -+ public DataHookEnforceNamespacedID() { -+ this("id"); -+ } -+ -+ public DataHookEnforceNamespacedID(final String path) { -+ this.path = path; -+ } -+ -+ @Override -+ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { -+ final String id = data.getString(this.path); -+ if (id != null) { -+ final String replace = NamespaceUtil.correctNamespaceOrNull(id); -+ if (replace != null) { -+ data.setString(this.path, replace); -+ } -+ } -+ return null; -+ } -+ -+ @Override -+ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f88487e7db589070512fafef1eb243ae29a379a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/hooks/DataHookValueTypeEnforceNamespaced.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.hooks; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public class DataHookValueTypeEnforceNamespaced implements DataHook { -+ -+ @Override -+ public Object preHook(final Object data, final long fromVersion, final long toVersion) { -+ if (data instanceof String) { -+ return NamespaceUtil.correctNamespaceOrNull((String)data); -+ } -+ return null; -+ } -+ -+ @Override -+ public Object postHook(final Object data, final long fromVersion, final long toVersion) { -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7e8f42eb57c12c885a1c17eafab1c9d9be4d8963 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V100.java -@@ -0,0 +1,159 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V100 { -+ -+ protected static final int VERSION = MCVersions.V15W32A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType equipment = data.getList("Equipment", ObjectType.MAP); -+ data.remove("Equipment"); -+ -+ if (equipment != null) { -+ if (equipment.size() > 0 && data.getListUnchecked("HandItems") == null) { -+ final ListType handItems = Types.NBT.createEmptyList(); -+ data.setList("HandItems", handItems); -+ handItems.addMap(equipment.getMap(0)); -+ handItems.addMap(Types.NBT.createEmptyMap()); -+ } -+ -+ if (equipment.size() > 1 && data.getListUnchecked("ArmorItems") == null) { -+ final ListType armorItems = Types.NBT.createEmptyList(); -+ data.setList("ArmorItems", armorItems); -+ for (int i = 1; i < Math.min(equipment.size(), 5); ++i) { -+ armorItems.addMap(equipment.getMap(i)); -+ } -+ } -+ } -+ -+ final ListType dropChances = data.getList("DropChances", ObjectType.FLOAT); -+ data.remove("DropChances"); -+ -+ if (dropChances != null) { -+ if (data.getListUnchecked("HandDropChances") == null) { -+ final ListType handDropChances = Types.NBT.createEmptyList(); -+ data.setList("HandDropChances", handDropChances); -+ if (0 < dropChances.size()) { -+ handDropChances.addFloat(dropChances.getFloat(0)); -+ } else { -+ handDropChances.addFloat(0.0F); -+ } -+ handDropChances.addFloat(0.0F); -+ } -+ -+ if (data.getListUnchecked("ArmorDropChances") == null) { -+ final ListType armorDropChances = Types.NBT.createEmptyList(); -+ data.setList("ArmorDropChances", armorDropChances); -+ for (int i = 1; i < 5; ++i) { -+ if (i < dropChances.size()) { -+ armorDropChances.addFloat(dropChances.getFloat(i)); -+ } else { -+ armorDropChances.addFloat(0.0F); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("ArmorStand"); -+ registerMob("Creeper"); -+ registerMob("Skeleton"); -+ registerMob("Spider"); -+ registerMob("Giant"); -+ registerMob("Zombie"); -+ registerMob("Slime"); -+ registerMob("Ghast"); -+ registerMob("PigZombie"); -+ registerMob("Enderman"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); -+ registerMob("CaveSpider"); -+ registerMob("Silverfish"); -+ registerMob("Blaze"); -+ registerMob("LavaSlime"); -+ registerMob("EnderDragon"); -+ registerMob("WitherBoss"); -+ registerMob("Bat"); -+ registerMob("Witch"); -+ registerMob("Endermite"); -+ registerMob("Guardian"); -+ registerMob("Pig"); -+ registerMob("Sheep"); -+ registerMob("Cow"); -+ registerMob("Chicken"); -+ registerMob("Squid"); -+ registerMob("Wolf"); -+ registerMob("MushroomCow"); -+ registerMob("SnowMan"); -+ registerMob("Ozelot"); -+ registerMob("VillagerGolem"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ registerMob("Rabbit"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ registerMob("Shulker"); -+ -+ MCTypeRegistry.STRUCTURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType entities = data.getList("entities", ObjectType.MAP); -+ if (entities != null) { -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, entities.getMap(i), "nbt", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType blocks = data.getList("blocks", ObjectType.MAP); -+ if (blocks != null) { -+ for (int i = 0, len = blocks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.TILE_ENTITY, blocks.getMap(i), "nbt", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, data, "palette", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V100() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bcdca74d1ca7265afaa3fc0f6913ddfb07800377 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V101.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.gson.JsonParseException; -+import net.minecraft.network.chat.CommonComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.util.GsonHelper; -+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; -+ -+public final class V101 { -+ -+ protected static final int VERSION = MCVersions.V15W32A + 1; -+ -+ protected static void updateLine(final MapType data, final String path) { -+ final String textString = data.getString(path); -+ if (textString == null || textString.isEmpty() || "null".equals(textString)) { -+ data.setString(path, Component.Serializer.toJson(CommonComponents.EMPTY)); -+ return; -+ } -+ -+ Component component = null; -+ -+ if (textString.charAt(0) == '"' && textString.charAt(textString.length() - 1) == '"' -+ || textString.charAt(0) == '{' && textString.charAt(textString.length() - 1) == '}') { -+ try { -+ component = GsonHelper.fromJson(BlockEntitySignTextStrictJsonFix.GSON, textString, Component.class, true); -+ if (component == null) { -+ component = CommonComponents.EMPTY; -+ } -+ } catch (final JsonParseException ignored) {} -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJson(textString); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJsonLenient(textString); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ component = Component.literal(textString); -+ } -+ } else { -+ component = Component.literal(textString); -+ } -+ -+ data.setString(path, Component.Serializer.toJson(component)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("Sign", new DataConverter<>(VERSION) { -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLine(data, "Text1"); -+ updateLine(data, "Text2"); -+ updateLine(data, "Text3"); -+ updateLine(data, "Text4"); -+ return null; -+ } -+ }); -+ } -+ -+ private V101() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java -new file mode 100644 -index 0000000000000000000000000000000000000000..76374ca99efdf898dee0829fd8eb5fac26dc9a22 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V102.java -@@ -0,0 +1,87 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+public final class V102 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32A + 2; -+ -+ public static void register() { -+ // V102 -> V15W32A + 2 -+ // V102 schema only modifies ITEM_STACK to have only a string ID, but our ITEM_NAME is generic (int or String) so we don't -+ // actually need to update the walker -+ -+ MCTypeRegistry.ITEM_NAME.addConverter(new DataConverter<>(VERSION) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ if (!(data instanceof Number)) { -+ return null; -+ } -+ final int id = ((Number)data).intValue(); -+ final String remap = HelperItemNameV102.getNameFromId(id); -+ if (remap == null) { -+ LOGGER.warn("Unknown legacy integer id (V102) " + id); -+ } -+ return remap == null ? HelperItemNameV102.getNameFromId(0) : remap; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("id", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ final int id = data.getInt("id"); -+ -+ String remap = HelperItemNameV102.getNameFromId(id); -+ if (remap == null) { -+ LOGGER.warn("Unknown legacy integer id (V102) " + id); -+ remap = HelperItemNameV102.getNameFromId(0); -+ } -+ -+ data.setString("id", remap); -+ -+ return null; -+ } -+ }); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final short damage = data.getShort("Damage"); -+ if (damage != 0) { -+ data.setShort("Damage", (short)0); -+ } -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("Potion", ObjectType.STRING)) { -+ final String converted = HelperItemNameV102.getPotionNameFromId(damage); -+ tag.setString("Potion", converted == null ? "minecraft:water" : converted); -+ if ((damage & 16384) == 16384) { -+ data.setString("id", "minecraft:splash_potion"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V102() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e251ead28d7d90937ae5871ffac489c1161e6e87 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1022.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1022 { -+ -+ protected static final int VERSION = MCVersions.V17W06A; -+ -+ public static void register() { -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle != null) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "EnderItems", fromVersion, toVersion); -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityLeft", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "ShoulderEntityRight", fromVersion, toVersion); -+ -+ final MapType recipeBook = data.getMap("recipeBook"); -+ if (recipeBook != null) { -+ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "recipes", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.RECIPE, recipeBook, "toBeDisplayed", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.HOTBAR.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ for (final String key : data.keys()) { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, key, fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ } -+ -+ private V1022() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java -new file mode 100644 -index 0000000000000000000000000000000000000000..544f6a54041147a8c9ee3ff52c31c480a3696924 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V105.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperSpawnEggNameV105; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V105 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ } -+ -+ final short damage = data.getShort("Damage"); -+ if (damage != 0) { -+ data.setShort("Damage", (short)0); -+ } -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag == null) { -+ entityTag = Types.NBT.createEmptyMap(); -+ } -+ -+ if (!entityTag.hasKey("id", ObjectType.STRING)) { -+ final String converted = HelperSpawnEggNameV105.getSpawnNameFromId(damage); -+ if (converted != null) { -+ entityTag.setString("id", converted); -+ tag.setMap("EntityTag", entityTag); -+ data.setMap("tag", tag); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V105() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java -new file mode 100644 -index 0000000000000000000000000000000000000000..951838b0f4f2b4ed82d707706ef15d779f3f41eb ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V106.java -@@ -0,0 +1,84 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V106 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 2; -+ -+ public static void register() { -+ // V106 -> V15W32C + 2 -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // While all converters for spawners check the id for this version, we don't because spawners exist in minecarts. ooops! Loading a chunk -+ // with a minecart spawner from 1.7.10 in 1.16.5 vanilla will fail to convert! Clearly there was a mistake in how they -+ // used and applied spawner converters. In anycase, do not check the id - we are not guaranteed to be a tile -+ // entity. We can be a regular old minecart spawner. And we know we are a spawner because this is only called from data walkers. -+ -+ final String entityId = data.getString("EntityId"); -+ if (entityId != null) { -+ data.remove("EntityId"); -+ MapType spawnData = data.getMap("SpawnData"); -+ if (spawnData == null) { -+ spawnData = Types.NBT.createEmptyMap(); -+ data.setMap("SpawnData", spawnData); -+ } -+ spawnData.setString("id", entityId.isEmpty() ? "Pig" : entityId); -+ } -+ -+ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ // convert to standard entity format (it's not a coincidence a walker for spawners is only added -+ // in this version) -+ final MapType spawn = spawnPotentials.getMap(i); -+ final String spawnType = spawn.getString("Type"); -+ if (spawnType == null) { -+ continue; -+ } -+ spawn.remove("Type"); -+ -+ MapType properties = spawn.getMap("Properties"); -+ if (properties == null) { -+ properties = Types.NBT.createEmptyMap(); -+ } else { -+ spawn.remove("Properties"); -+ } -+ -+ properties.setString("id", spawnType); -+ -+ spawn.setMap("Entity", properties); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType spawnPotentials = data.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential, "Entity", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "SpawnData", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V106() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java -new file mode 100644 -index 0000000000000000000000000000000000000000..aa8c8d22ee2a77604d923b62f5a93ede9b3f333f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V107.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V107 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 3; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Minecart", new DataConverter<>(VERSION) { -+ protected final String[] MINECART_IDS = new String[] { -+ "MinecartRideable", // 0 -+ "MinecartChest", // 1 -+ "MinecartFurnace", // 2 -+ "MinecartTNT", // 3 -+ "MinecartSpawner", // 4 -+ "MinecartHopper", // 5 -+ "MinecartCommandBlock" // 6 -+ }; -+ // Vanilla does not use all of the IDs here. The legacy (pre DFU) code does, so I'm going to use them. -+ // No harm in catching more cases here. -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ String newId = "MinecartRideable"; // dfl -+ final int type = data.getInt("Type"); -+ data.remove("Type"); -+ -+ if (type >= 0 && type < MINECART_IDS.length) { -+ newId = MINECART_IDS[type]; -+ } -+ data.setString("id", newId); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V107() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6bc4e2939bd26538492a7b94b743957d56ddc575 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V108.java -@@ -0,0 +1,48 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+ -+import java.util.UUID; -+ -+public final class V108 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32C + 4; -+ -+ public static void register() { -+ // Convert String UUID into UUIDMost and UUIDLeast -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String uuidString = data.getString("UUID"); -+ -+ if (uuidString == null) { -+ return null; -+ } -+ data.remove("UUID"); -+ -+ final UUID uuid; -+ try { -+ uuid = UUID.fromString(uuidString); -+ } catch (final Exception ex) { -+ LOGGER.warn("Failed to parse UUID for legacy entity (V108): " + uuidString, ex); -+ return null; -+ } -+ -+ data.setLong("UUIDMost", uuid.getMostSignificantBits()); -+ data.setLong("UUIDLeast", uuid.getLeastSignificantBits()); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V108() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e0e52fa2d8988ca973f8a97b2374a8c3d4ef80c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V109.java -@@ -0,0 +1,83 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.Sets; -+import java.util.Set; -+ -+public final class V109 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 5; -+ -+ // DFU declares this exact field but leaves it unused. Not sure why, legacy conversion system checked if the ID matched. -+ // I'm going to leave it here unused as well, just in case it's needed in the future. -+ protected static final Set ENTITIES = Sets.newHashSet( -+ "ArmorStand", -+ "Bat", -+ "Blaze", -+ "CaveSpider", -+ "Chicken", -+ "Cow", -+ "Creeper", -+ "EnderDragon", -+ "Enderman", -+ "Endermite", -+ "EntityHorse", -+ "Ghast", -+ "Giant", -+ "Guardian", -+ "LavaSlime", -+ "MushroomCow", -+ "Ozelot", -+ "Pig", -+ "PigZombie", -+ "Rabbit", -+ "Sheep", -+ "Shulker", -+ "Silverfish", -+ "Skeleton", -+ "Slime", -+ "SnowMan", -+ "Spider", -+ "Squid", -+ "Villager", -+ "VillagerGolem", -+ "Witch", -+ "WitherBoss", -+ "Wolf", -+ "Zombie" -+ ); -+ -+ public static void register() { -+ // Converts health to be in float, and cleans up whatever the hell was going on with HealF and Health... -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number healF = data.getNumber("HealF"); -+ final Number heal = data.getNumber("Health"); -+ -+ final float newHealth; -+ -+ if (healF != null) { -+ data.remove("HealF"); -+ newHealth = healF.floatValue(); -+ } else { -+ if (heal == null) { -+ return null; -+ } -+ -+ newHealth = heal.floatValue(); -+ } -+ -+ data.setFloat("Health", newHealth); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V109() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9771810a1f1cbf760fd9a8a5fd575f6052f40ea9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V110.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V110 { -+ -+ protected static final int VERSION = MCVersions.V15W32C + 6; -+ -+ public static void register() { -+ // Moves the Saddle boolean to be an actual saddle item. Note: The data walker for the SaddleItem exists -+ // in V99, it doesn't need to be added here. -+ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.getBoolean("Saddle") || data.hasKey("SaddleItem", ObjectType.MAP)) { -+ return null; -+ } -+ -+ final MapType saddleItem = Types.NBT.createEmptyMap(); -+ data.remove("Saddle"); -+ data.setMap("SaddleItem", saddleItem); -+ -+ saddleItem.setString("id", "minecraft:saddle"); -+ saddleItem.setByte("Count", (byte)1); -+ saddleItem.setShort("Damage", (short)0); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V110() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5bae7effda7761a3f2a0a2ce550d867cb2c18b99 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V111.java -@@ -0,0 +1,67 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V111 { -+ -+ protected static final int VERSION = MCVersions.V15W33B; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Painting", new EntityRotationFix("Painting")); -+ MCTypeRegistry.ENTITY.addConverterForId("ItemFrame", new EntityRotationFix("ItemFrame")); -+ } -+ -+ private V111() {} -+ -+ protected static final class EntityRotationFix extends DataConverter, MapType> { -+ -+ private static final int[][] DIRECTIONS = new int[][] { -+ {0, 0, 1}, -+ {-1, 0, 0}, -+ {0, 0, -1}, -+ {1, 0, 0} -+ }; -+ -+ protected final String id; -+ -+ public EntityRotationFix(final String id) { -+ super(VERSION); -+ this.id = id; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getNumber("Facing") != null) { -+ return null; -+ } -+ -+ final Number direction = data.getNumber("Direction"); -+ final int facing; -+ if (direction != null) { -+ data.remove("Direction"); -+ facing = direction.intValue() % DIRECTIONS.length; -+ final int[] offsets = DIRECTIONS[facing]; -+ data.setInt("TileX", data.getInt("TileX") + offsets[0]); -+ data.setInt("TileY", data.getInt("TileY") + offsets[1]); -+ data.setInt("TileZ", data.getInt("TileZ") + offsets[2]); -+ if ("ItemFrame".equals(data.getString("id"))) { -+ final Number rotation = data.getNumber("ItemRotation"); -+ if (rotation != null) { -+ data.setByte("ItemRotation", (byte)(rotation.byteValue() * 2)); -+ } -+ } -+ } else { -+ facing = data.getByte("Dir") % DIRECTIONS.length; -+ data.remove("Dir"); -+ } -+ -+ data.setByte("Facing", (byte)facing); -+ -+ return null; -+ } -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a6d3c28e8c97b53f388c03ccad3449390937d3b2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1125.java -@@ -0,0 +1,102 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1125 { -+ -+ protected static final int VERSION = MCVersions.V17W15A; -+ protected static final int BED_BLOCK_ID = 416; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ if (tileEntities == null) { -+ tileEntities = Types.NBT.createEmptyList(); -+ level.setList("TileEntities", tileEntities); -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final byte sectionY = section.getByte("Y"); -+ final byte[] blocks = section.getBytes("Blocks"); -+ -+ if (blocks == null) { -+ continue; -+ } -+ -+ for (int blockIndex = 0; blockIndex < blocks.length; ++blockIndex) { -+ if (BED_BLOCK_ID != ((blocks[blockIndex] & 255) << 4)) { -+ continue; -+ } -+ -+ final int localX = blockIndex & 15; -+ final int localZ = (blockIndex >> 4) & 15; -+ final int localY = (blockIndex >> 8) & 15; -+ -+ final MapType newTile = Types.NBT.createEmptyMap(); -+ newTile.setString("id", "minecraft:bed"); -+ newTile.setInt("x", localX + (chunkX << 4)); -+ newTile.setInt("y", localY + (sectionY << 4)); -+ newTile.setInt("z", localZ + (chunkZ << 4)); -+ newTile.setShort("color", (short)14); // Red -+ -+ tileEntities.addMap(newTile); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:bed", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getShort("Damage") == 0) { -+ data.setShort("Damage", (short)14); // Red -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ MCTypeRegistry.ADVANCEMENTS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertKeys(MCTypeRegistry.BIOME, data.getMap("minecraft:adventure/adventuring_time"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_a_mob"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/kill_all_mobs"), "criteria", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, data.getMap("minecraft:adventure/bred_all_animals"), "criteria", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ // Enforce namespacing for ids -+ MCTypeRegistry.BIOME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ } -+ -+ private V1125() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b93bba2b084e20d346461f53d2f7662c3d6238b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V113.java -@@ -0,0 +1,41 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V113 { -+ -+ protected static final int VERSION = MCVersions.V15W33C + 1; -+ -+ protected static void checkList(final MapType data, final String id, final int requiredLength) { -+ final ListType list = data.getList(id, ObjectType.FLOAT); -+ if (list != null && list.size() == requiredLength) { -+ for (int i = 0; i < requiredLength; ++i) { -+ if (list.getFloat(i) != 0.0F) { -+ return; -+ } -+ } -+ } -+ -+ data.remove(id); -+ } -+ -+ public static void register() { -+ // Removes "HandDropChances" and "ArmorDropChances" if they're empty. -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ checkList(data, "HandDropChances", 2); -+ checkList(data, "ArmorDropChances", 4); -+ return null; -+ } -+ }); -+ } -+ -+ private V113() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ac390a6111ba1a4aae3d5726747f60f4929fa254 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1344.java -@@ -0,0 +1,177 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V1344 { -+ -+ protected static final int VERSION = MCVersions.V1_12_2 + 1; -+ -+ private static final Int2ObjectOpenHashMap BUTTON_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); -+ static { -+ BUTTON_ID_TO_NAME.put(0, "key.unknown"); -+ BUTTON_ID_TO_NAME.put(11, "key.0"); -+ BUTTON_ID_TO_NAME.put(2, "key.1"); -+ BUTTON_ID_TO_NAME.put(3, "key.2"); -+ BUTTON_ID_TO_NAME.put(4, "key.3"); -+ BUTTON_ID_TO_NAME.put(5, "key.4"); -+ BUTTON_ID_TO_NAME.put(6, "key.5"); -+ BUTTON_ID_TO_NAME.put(7, "key.6"); -+ BUTTON_ID_TO_NAME.put(8, "key.7"); -+ BUTTON_ID_TO_NAME.put(9, "key.8"); -+ BUTTON_ID_TO_NAME.put(10, "key.9"); -+ BUTTON_ID_TO_NAME.put(30, "key.a"); -+ BUTTON_ID_TO_NAME.put(40, "key.apostrophe"); -+ BUTTON_ID_TO_NAME.put(48, "key.b"); -+ BUTTON_ID_TO_NAME.put(43, "key.backslash"); -+ BUTTON_ID_TO_NAME.put(14, "key.backspace"); -+ BUTTON_ID_TO_NAME.put(46, "key.c"); -+ BUTTON_ID_TO_NAME.put(58, "key.caps.lock"); -+ BUTTON_ID_TO_NAME.put(51, "key.comma"); -+ BUTTON_ID_TO_NAME.put(32, "key.d"); -+ BUTTON_ID_TO_NAME.put(211, "key.delete"); -+ BUTTON_ID_TO_NAME.put(208, "key.down"); -+ BUTTON_ID_TO_NAME.put(18, "key.e"); -+ BUTTON_ID_TO_NAME.put(207, "key.end"); -+ BUTTON_ID_TO_NAME.put(28, "key.enter"); -+ BUTTON_ID_TO_NAME.put(13, "key.equal"); -+ BUTTON_ID_TO_NAME.put(1, "key.escape"); -+ BUTTON_ID_TO_NAME.put(33, "key.f"); -+ BUTTON_ID_TO_NAME.put(59, "key.f1"); -+ BUTTON_ID_TO_NAME.put(68, "key.f10"); -+ BUTTON_ID_TO_NAME.put(87, "key.f11"); -+ BUTTON_ID_TO_NAME.put(88, "key.f12"); -+ BUTTON_ID_TO_NAME.put(100, "key.f13"); -+ BUTTON_ID_TO_NAME.put(101, "key.f14"); -+ BUTTON_ID_TO_NAME.put(102, "key.f15"); -+ BUTTON_ID_TO_NAME.put(103, "key.f16"); -+ BUTTON_ID_TO_NAME.put(104, "key.f17"); -+ BUTTON_ID_TO_NAME.put(105, "key.f18"); -+ BUTTON_ID_TO_NAME.put(113, "key.f19"); -+ BUTTON_ID_TO_NAME.put(60, "key.f2"); -+ BUTTON_ID_TO_NAME.put(61, "key.f3"); -+ BUTTON_ID_TO_NAME.put(62, "key.f4"); -+ BUTTON_ID_TO_NAME.put(63, "key.f5"); -+ BUTTON_ID_TO_NAME.put(64, "key.f6"); -+ BUTTON_ID_TO_NAME.put(65, "key.f7"); -+ BUTTON_ID_TO_NAME.put(66, "key.f8"); -+ BUTTON_ID_TO_NAME.put(67, "key.f9"); -+ BUTTON_ID_TO_NAME.put(34, "key.g"); -+ BUTTON_ID_TO_NAME.put(41, "key.grave.accent"); -+ BUTTON_ID_TO_NAME.put(35, "key.h"); -+ BUTTON_ID_TO_NAME.put(199, "key.home"); -+ BUTTON_ID_TO_NAME.put(23, "key.i"); -+ BUTTON_ID_TO_NAME.put(210, "key.insert"); -+ BUTTON_ID_TO_NAME.put(36, "key.j"); -+ BUTTON_ID_TO_NAME.put(37, "key.k"); -+ BUTTON_ID_TO_NAME.put(82, "key.keypad.0"); -+ BUTTON_ID_TO_NAME.put(79, "key.keypad.1"); -+ BUTTON_ID_TO_NAME.put(80, "key.keypad.2"); -+ BUTTON_ID_TO_NAME.put(81, "key.keypad.3"); -+ BUTTON_ID_TO_NAME.put(75, "key.keypad.4"); -+ BUTTON_ID_TO_NAME.put(76, "key.keypad.5"); -+ BUTTON_ID_TO_NAME.put(77, "key.keypad.6"); -+ BUTTON_ID_TO_NAME.put(71, "key.keypad.7"); -+ BUTTON_ID_TO_NAME.put(72, "key.keypad.8"); -+ BUTTON_ID_TO_NAME.put(73, "key.keypad.9"); -+ BUTTON_ID_TO_NAME.put(78, "key.keypad.add"); -+ BUTTON_ID_TO_NAME.put(83, "key.keypad.decimal"); -+ BUTTON_ID_TO_NAME.put(181, "key.keypad.divide"); -+ BUTTON_ID_TO_NAME.put(156, "key.keypad.enter"); -+ BUTTON_ID_TO_NAME.put(141, "key.keypad.equal"); -+ BUTTON_ID_TO_NAME.put(55, "key.keypad.multiply"); -+ BUTTON_ID_TO_NAME.put(74, "key.keypad.subtract"); -+ BUTTON_ID_TO_NAME.put(38, "key.l"); -+ BUTTON_ID_TO_NAME.put(203, "key.left"); -+ BUTTON_ID_TO_NAME.put(56, "key.left.alt"); -+ BUTTON_ID_TO_NAME.put(26, "key.left.bracket"); -+ BUTTON_ID_TO_NAME.put(29, "key.left.control"); -+ BUTTON_ID_TO_NAME.put(42, "key.left.shift"); -+ BUTTON_ID_TO_NAME.put(219, "key.left.win"); -+ BUTTON_ID_TO_NAME.put(50, "key.m"); -+ BUTTON_ID_TO_NAME.put(12, "key.minus"); -+ BUTTON_ID_TO_NAME.put(49, "key.n"); -+ BUTTON_ID_TO_NAME.put(69, "key.num.lock"); -+ BUTTON_ID_TO_NAME.put(24, "key.o"); -+ BUTTON_ID_TO_NAME.put(25, "key.p"); -+ BUTTON_ID_TO_NAME.put(209, "key.page.down"); -+ BUTTON_ID_TO_NAME.put(201, "key.page.up"); -+ BUTTON_ID_TO_NAME.put(197, "key.pause"); -+ BUTTON_ID_TO_NAME.put(52, "key.period"); -+ BUTTON_ID_TO_NAME.put(183, "key.print.screen"); -+ BUTTON_ID_TO_NAME.put(16, "key.q"); -+ BUTTON_ID_TO_NAME.put(19, "key.r"); -+ BUTTON_ID_TO_NAME.put(205, "key.right"); -+ BUTTON_ID_TO_NAME.put(184, "key.right.alt"); -+ BUTTON_ID_TO_NAME.put(27, "key.right.bracket"); -+ BUTTON_ID_TO_NAME.put(157, "key.right.control"); -+ BUTTON_ID_TO_NAME.put(54, "key.right.shift"); -+ BUTTON_ID_TO_NAME.put(220, "key.right.win"); -+ BUTTON_ID_TO_NAME.put(31, "key.s"); -+ BUTTON_ID_TO_NAME.put(70, "key.scroll.lock"); -+ BUTTON_ID_TO_NAME.put(39, "key.semicolon"); -+ BUTTON_ID_TO_NAME.put(53, "key.slash"); -+ BUTTON_ID_TO_NAME.put(57, "key.space"); -+ BUTTON_ID_TO_NAME.put(20, "key.t"); -+ BUTTON_ID_TO_NAME.put(15, "key.tab"); -+ BUTTON_ID_TO_NAME.put(22, "key.u"); -+ BUTTON_ID_TO_NAME.put(200, "key.up"); -+ BUTTON_ID_TO_NAME.put(47, "key.v"); -+ BUTTON_ID_TO_NAME.put(17, "key.w"); -+ BUTTON_ID_TO_NAME.put(45, "key.x"); -+ BUTTON_ID_TO_NAME.put(21, "key.y"); -+ BUTTON_ID_TO_NAME.put(44, "key.z"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ for (final String key : data.keys()) { -+ if (!key.startsWith("key_")) { -+ continue; -+ } -+ final String value = data.getString(key); -+ final int code; -+ try { -+ code = Integer.parseInt(value); -+ } catch (final NumberFormatException ex) { -+ continue; -+ } -+ -+ final String newEntry; -+ -+ if (code < 0) { -+ final int mouseCode = code + 100; -+ switch (mouseCode) { -+ case 0: -+ newEntry = "key.mouse.left"; -+ break; -+ case 1: -+ newEntry = "key.mouse.right"; -+ break; -+ case 2: -+ newEntry = "key.mouse.middle"; -+ break; -+ default: -+ newEntry = "key.mouse." + (mouseCode + 1); -+ break; -+ } -+ } else { -+ newEntry = BUTTON_ID_TO_NAME.getOrDefault(code, "key.unknown"); -+ } -+ -+ // No CMEs occur for existing entries in maps. -+ data.setString(key, newEntry); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V1344() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java -new file mode 100644 -index 0000000000000000000000000000000000000000..764a56fda5ee909ac47a0c1b3b581c8c26deb591 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V135.java -@@ -0,0 +1,61 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V135 { -+ -+ protected static final int VERSION = MCVersions.V15W40B + 1; -+ -+ public static void register() { -+ // In this update they changed the "Riding" value to be "Passengers", which is now a list. So it added -+ // support for multiple entities riding. Of course, Riding and Passenger are opposites - so it also will -+ // switch the data layout to be from highest rider to lowest rider, in terms of depth. -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(MapType data, final long sourceVersion, final long toVersion) { -+ MapType ret = null; -+ while (data.hasKey("Riding", ObjectType.MAP)) { -+ final MapType riding = data.getMap("Riding"); -+ data.remove("Riding"); -+ -+ final ListType passengers = Types.NBT.createEmptyList(); -+ riding.setList("Passengers", passengers); -+ passengers.addMap(data); -+ -+ ret = data = riding; -+ } -+ -+ return ret; -+ } -+ }); -+ -+ -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle != null) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, rootVehicle, "Entity", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Passengers", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ } -+ -+ private V135() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java -new file mode 100644 -index 0000000000000000000000000000000000000000..451e837349e10f1e76ac7d9f5d49cbe0ff630f4d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V143.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+ -+public final class V143 { -+ -+ protected static final int VERSION = MCVersions.V15W44B; -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, (final String input) -> { -+ return "TippedArrow".equals(input) ? "Arrow" : null; -+ }); -+ } -+ -+ private V143() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fc0ece569baed94bbf3cbbaa21a397fdc37e51e8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1446.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1446 { -+ -+ protected static final int VERSION = MCVersions.V17W43B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ for (final String key : data.keys()) { -+ if (!key.startsWith("key_")) { -+ continue; -+ } -+ -+ final String value = data.getString(key); -+ -+ if (value.startsWith("key.mouse") || value.startsWith("scancode.")) { -+ continue; -+ } -+ -+ data.setString(key, "key.keyboard." + value.substring("key.".length())); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V1446() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java -new file mode 100644 -index 0000000000000000000000000000000000000000..711222cd33ee557b7f3d1f6ae73ad45d1caf6768 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1450.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1450 { -+ -+ protected static final int VERSION = MCVersions.V17W46A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType ret = HelperBlockFlatteningV1450.flattenNBT(data); -+ return ret == data ? null : ret.copy(); // copy to avoid problems with later state datafixers -+ } -+ }); -+ } -+ -+ private V1450() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a1f270f55617e1608bf7821951fa589e611b6f7d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1451.java -@@ -0,0 +1,512 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataHook; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.chunk.ConverterFlattenChunk; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperBlockFlatteningV1450; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenItemStack; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemstack.ConverterFlattenSpawnEgg; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterFlattenStats; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterFlattenEntity; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.base.Splitter; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.util.datafix.fixes.BlockStateData; -+import net.minecraft.util.datafix.fixes.EntityBlockStateFix; -+import org.apache.commons.lang3.math.NumberUtils; -+import java.util.Iterator; -+import java.util.List; -+import java.util.stream.Collectors; -+import java.util.stream.StreamSupport; -+ -+public final class V1451 { -+ -+ protected static final int VERSION = MCVersions.V17W47A; -+ -+ public static void register() { -+ // V0 -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 0, "minecraft:trapped_chest", new DataWalkerItemLists("Items")); -+ -+ // V1 -+ MCTypeRegistry.CHUNK.addStructureConverter(new ConverterFlattenChunk()); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, 1, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ -+ // V2 -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:piston", new DataConverter<>(VERSION, 2) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int blockId = data.getInt("blockId"); -+ final int blockData = data.getInt("blockData") & 15; -+ -+ data.remove("blockId"); -+ data.remove("blockData"); -+ -+ data.setMap("blockState", HelperBlockFlatteningV1450.getNBTForId((blockId << 4) | blockData).copy()); // copy to avoid problems with later state datafixers -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, 2, "minecraft:piston", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "blockState")); -+ -+ // V3 -+ ConverterFlattenEntity.register(); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:filled_map", new DataConverter<>(VERSION, 3) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("map", ObjectType.NUMBER)) { // This if is from CB. as usual, no documentation from CB. I'm guessing it just wants to avoid possibly overwriting it. seems fine. -+ tag.setInt("map", data.getInt("Damage")); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:potion", new DataWalkerItems("Potion")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:enderman", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "carriedBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "BlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spectral_arrow", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:commandblock_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:furnace_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, 3, "minecraft:tnt_minecart", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "DisplayState")); -+ -+ // V4 -+ MCTypeRegistry.BLOCK_NAME.addConverter(new DataConverter<>(VERSION, 4) { -+ @Override -+ public Object convert(final Object data, final long sourceVersion, final long toVersion) { -+ if (data instanceof Number) { -+ return HelperBlockFlatteningV1450.getNameForId(((Number)data).intValue()); -+ } else if (data instanceof String) { -+ return HelperBlockFlatteningV1450.getNewBlockName((String)data); // structure hook ensured data is namespaced -+ } -+ return null; -+ } -+ }); -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new ConverterFlattenItemStack()); -+ -+ // V5 -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:spawn_egg", new ConverterFlattenSpawnEgg()); -+ /* This datafixer has been disabled because the collar colour handler did not change from 1.12 -> 1.13 at all. -+ // So clearly somebody fucked up. This fixes wolf colours incorrectly converting between versions -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:wolf", new DataConverter<>(VERSION, 5) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number colour = data.getNumber("CollarColor"); -+ -+ if (colour != null) { -+ data.setByte("CollarColor", (byte)(15 - colour.intValue())); -+ } -+ -+ return null; -+ } -+ }); -+ */ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION, 5) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number base = data.getNumber("Base"); -+ if (base != null) { -+ data.setInt("Base", 15 - base.intValue()); -+ } -+ -+ final ListType patterns = data.getList("Patterns", ObjectType.MAP); -+ if (patterns != null) { -+ for (int i = 0, len = patterns.size(); i < len; ++i) { -+ final MapType pattern = patterns.getMap(i); -+ final Number colour = pattern.getNumber("Color"); -+ if (colour != null) { -+ pattern.setInt("Color", 15 - colour.intValue()); -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION, 5) { -+ private final Splitter SPLITTER = Splitter.on(';').limit(5); -+ private final Splitter LAYER_SPLITTER = Splitter.on(','); -+ private final Splitter OLD_AMOUNT_SPLITTER = Splitter.on('x').limit(2); -+ private final Splitter AMOUNT_SPLITTER = Splitter.on('*').limit(2); -+ private final Splitter BLOCK_SPLITTER = Splitter.on(':').limit(3); -+ -+ // idk man i just copy and pasted this one -+ private String fixGeneratorSettings(final String generatorSettings) { -+ if (generatorSettings.isEmpty()) { -+ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; -+ } else { -+ Iterator iterator = SPLITTER.split(generatorSettings).iterator(); -+ String string2 = (String)iterator.next(); -+ int j; -+ String string4; -+ if (iterator.hasNext()) { -+ j = NumberUtils.toInt(string2, 0); -+ string4 = (String)iterator.next(); -+ } else { -+ j = 0; -+ string4 = string2; -+ } -+ -+ if (j >= 0 && j <= 3) { -+ StringBuilder stringBuilder = new StringBuilder(); -+ Splitter splitter = j < 3 ? OLD_AMOUNT_SPLITTER : AMOUNT_SPLITTER; -+ stringBuilder.append((String) StreamSupport.stream(LAYER_SPLITTER.split(string4).spliterator(), false).map((stringx) -> { -+ List list = splitter.splitToList(stringx); -+ int k; -+ String string3; -+ if (list.size() == 2) { -+ k = NumberUtils.toInt((String)list.get(0)); -+ string3 = (String)list.get(1); -+ } else { -+ k = 1; -+ string3 = (String)list.get(0); -+ } -+ -+ List list2 = BLOCK_SPLITTER.splitToList(string3); -+ int l = ((String)list2.get(0)).equals("minecraft") ? 1 : 0; -+ String string5 = (String)list2.get(l); -+ int m = j == 3 ? EntityBlockStateFix.getBlockId("minecraft:" + string5) : NumberUtils.toInt(string5, 0); -+ int n = l + 1; -+ int o = list2.size() > n ? NumberUtils.toInt((String)list2.get(n), 0) : 0; -+ return (k == 1 ? "" : k + "*") + BlockStateData.getTag(m << 4 | o).get("Name").asString(""); -+ }).collect(Collectors.joining(","))); -+ -+ while(iterator.hasNext()) { -+ stringBuilder.append(';').append((String)iterator.next()); -+ } -+ -+ return stringBuilder.toString(); -+ } else { -+ return "minecraft:bedrock,2*minecraft:dirt,minecraft:grass_block;1;village"; -+ } -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"flat".equalsIgnoreCase(data.getString("generatorName"))) { -+ return null; -+ } -+ -+ final String generatorOptions = data.getString("generatorOptions"); -+ if (generatorOptions == null) { -+ return null; -+ } -+ -+ data.setString("generatorOptions", this.fixGeneratorSettings(generatorOptions)); -+ -+ return null; -+ } -+ }); -+ -+ // V6 -+ MCTypeRegistry.STATS.addStructureConverter(new ConverterFlattenStats()); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jukebox", new DataConverter<>(VERSION, 6) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int record = data.getInt("Record"); -+ if (record <= 0) { -+ return null; -+ } -+ -+ data.remove("Record"); -+ -+ final String newItemId = ConverterFlattenItemStack.flattenItem(HelperItemNameV102.getNameFromId(record), 0); -+ if (newItemId == null) { -+ return null; -+ } -+ -+ final MapType recordItem = Types.NBT.createEmptyMap(); -+ data.setMap("RecordItem", recordItem); -+ -+ recordItem.setString("id", newItemId); -+ recordItem.setByte("Count", (byte)1); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STATS.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType stats = data.getMap("stats"); -+ if (stats == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.BLOCK_NAME, stats, "minecraft:mined", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:crafted", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:used", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:broken", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:picked_up", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ITEM_NAME, stats, "minecraft:dropped", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed", fromVersion, toVersion); -+ WalkerUtils.convertKeys(MCTypeRegistry.ENTITY_NAME, stats, "minecraft:killed_by", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureHook(VERSION, 6, new DataHook<>() { -+ private static String packWithDot(final String string) { -+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(string); -+ return resourceLocation != null ? resourceLocation.getNamespace() + "." + resourceLocation.getPath() : string; -+ } -+ -+ @Override -+ public MapType preHook(final MapType data, final long fromVersion, final long toVersion) { -+ // unpack -+ final String criteriaName = data.getString("CriteriaName"); -+ String type; -+ String id; -+ -+ if (criteriaName != null) { -+ final int index = criteriaName.indexOf(':'); -+ if (index < 0) { -+ type = "_special"; -+ id = criteriaName; -+ } else { -+ try { -+ type = ResourceLocation.of(criteriaName.substring(0, index), '.').toString(); -+ id = ResourceLocation.of(criteriaName.substring(index + 1), '.').toString(); -+ } catch (final Exception ex) { -+ type = "_special"; -+ id = criteriaName; -+ } -+ } -+ } else { -+ type = null; -+ id = null; -+ } -+ -+ if (type != null && id != null) { -+ final MapType criteriaType = Types.NBT.createEmptyMap(); -+ data.setMap("CriteriaType", criteriaType); -+ -+ criteriaType.setString("type", type); -+ criteriaType.setString("id", id); -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public MapType postHook(final MapType data, final long fromVersion, final long toVersion) { -+ // repack -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ -+ final String newName; -+ if (criteriaType == null) { -+ newName = null; -+ } else { -+ final String type = criteriaType.getString("type"); -+ final String id = criteriaType.getString("id"); -+ if (type != null && id != null) { -+ if ("_special".equals(type)) { -+ newName = id; -+ } else { -+ newName = packWithDot(type) + ":" + packWithDot(id); -+ } -+ } else { -+ newName = null; -+ } -+ } -+ -+ if (newName != null) { -+ data.remove("CriteriaType"); -+ data.setString("CriteriaName", newName); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureWalker(VERSION, 6, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType criteriaType = data.getMap("CriteriaType"); -+ if (criteriaType == null) { -+ return null; -+ } -+ -+ final String type = criteriaType.getString("type"); -+ -+ if (type == null) { -+ return null; -+ } -+ -+ switch (type) { -+ case "minecraft:mined": { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:crafted": -+ case "minecraft:used": -+ case "minecraft:broken": -+ case "minecraft:picked_up": -+ case "minecraft:dropped": { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:killed": -+ case "minecraft:killed_by": { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY_NAME, criteriaType, "id", fromVersion, toVersion); -+ break; -+ } -+ } -+ -+ return null; -+ }); -+ -+ -+ // V7 -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION, 7) { -+ private static void convertToBlockState(final MapType data, final String path) { -+ final Number number = data.getNumber(path); -+ if (number == null) { -+ return; -+ } -+ -+ data.setMap(path, HelperBlockFlatteningV1450.getNBTForId(number.intValue() << 4).copy()); // copy to avoid problems with later state datafixers -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ if (children == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ final String id = child.getString("id"); -+ -+ switch (id) { -+ case "ViF": -+ convertToBlockState(child, "CA"); -+ convertToBlockState(child, "CB"); -+ break; -+ case "ViDF": -+ convertToBlockState(child, "CA"); -+ convertToBlockState(child, "CB"); -+ convertToBlockState(child, "CC"); -+ convertToBlockState(child, "CD"); -+ break; -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ // convert villagers to trade with pumpkins and not the carved pumpkin -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION, 7) { -+ private static void convertPumpkin(final MapType data, final String path) { -+ final MapType item = data.getMap(path); -+ if (item == null) { -+ return; -+ } -+ -+ final String id = item.getString("id"); -+ -+ if (id.equals("minecraft:carved_pumpkin")) { -+ item.setString("id", "minecraft:pumpkin"); -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ -+ convertPumpkin(recipe, "buy"); -+ convertPumpkin(recipe, "buyB"); -+ convertPumpkin(recipe, "sell"); -+ } -+ } -+ } -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, 7, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType list = data.getList("Children", ObjectType.MAP); -+ if (list == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType child = list.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); -+ } -+ -+ return null; -+ }); -+ } -+ -+ private V1451() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8ca5b9d7292ba9c81f7f0fdfb6ca8fd17f796990 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1456.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1456 { -+ -+ protected static final int VERSION = MCVersions.V17W49B + 1; -+ -+ protected static byte direction2dTo3d(final byte old) { -+ switch (old) { -+ case 0: -+ return 3; -+ case 1: -+ return 4; -+ case 2: -+ default: -+ return 2; -+ case 3: -+ return 5; -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item_frame", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setByte("Facing", direction2dTo3d(data.getByte("Facing"))); -+ return null; -+ } -+ }); -+ } -+ -+ private V1456() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1de3a47887f29134e3e0ae6467afb54c8ab7ef68 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1458.java -@@ -0,0 +1,88 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import net.minecraft.network.chat.Component; -+ -+public final class V1458 { -+ -+ protected static final int VERSION = MCVersions.V17W50A + 1; -+ -+ public static MapType updateCustomName(final MapType data) { -+ final String customName = data.getString("CustomName", ""); -+ -+ if (customName.isEmpty()) { -+ data.remove("CustomName"); -+ } else { -+ data.setString("CustomName", Component.Serializer.toJson(Component.literal(customName))); -+ } -+ -+ return null; -+ } -+ -+ public static void register() { -+ // From CB -+ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if ("minecraft:commandblock_minecart".equals(data.getString("id"))) { -+ return null; -+ } -+ -+ return updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ if (display == null) { -+ return null; -+ } -+ -+ final String name = display.getString("Name"); -+ if (name != null) { -+ display.setString("Name", Component.Serializer.toJson(Component.literal(name))); -+ } else { -+ final String localisedName = display.getString("LocName"); -+ if (localisedName != null) { -+ display.setString("Name", Component.Serializer.toJson(Component.translatable(localisedName))); -+ display.remove("LocName"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if ("minecraft:command_block".equals(data.getString("id"))) { -+ return null; -+ } -+ -+ return updateCustomName(data); -+ } -+ }); -+ -+ } -+ -+ private V1458() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f68b561b2bb750d5f632f17e538337fa38108472 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1460.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import net.minecraft.resources.ResourceLocation; -+import java.util.HashMap; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V1460 { -+ -+ private static final Map MOTIVE_REMAP = new HashMap<>(); -+ -+ static { -+ MOTIVE_REMAP.put("donkeykong", "donkey_kong"); -+ MOTIVE_REMAP.put("burningskull", "burning_skull"); -+ MOTIVE_REMAP.put("skullandroses", "skull_and_roses"); -+ }; -+ -+ protected static final int VERSION = MCVersions.V18W01A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private static void registerThrowableProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ String motive = data.getString("Motive"); -+ if (motive != null) { -+ motive = motive.toLowerCase(Locale.ROOT); -+ data.setString("Motive", new ResourceLocation(MOTIVE_REMAP.getOrDefault(motive, motive)).toString()); -+ } -+ return null; -+ } -+ }); -+ -+ // No idea why so many type redefines exist here in Vanilla. nothing about the data structure changed, it's literally a copy of -+ // the existing types. -+ } -+ -+ private V1460() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c4fa8e36fb68a610106cee8bae1af243e51fae2e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1466.java -@@ -0,0 +1,143 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1466 { -+ -+ protected static final int VERSION = MCVersions.V18W06A; -+ -+ protected static short packOffsetCoordinates(final int x, final int y, final int z) { -+ return (short)((x & 15) | ((y & 15) << 4) | ((z & 15) << 8)); -+ } -+ -+ public static void register() { -+ // There is a rather critical change I've made to this converter: changing the chunk status determination. -+ // In Vanilla, this is determined by whether the terrain has been populated and whether the chunk is lit. -+ // For reference, here is the full status progression (at the time of 18w06a): -+ // empty -> base -> carved -> decorated -> lighted -> mobs_spawned -> finalized -> fullchunk -> postprocessed -+ // So one of those must be picked. -+ // If the chunk is lit and terrain is populated, the Vanilla converter will set the status to "mobs_spawned." -+ // If it is anything else, it will be "empty" -+ // I've changed it to the following: if terrain is populated, it is set to at least decorated. If it is populated -+ // and lit, it is set to "mobs_spawned" -+ // But what if it is not populated? If it is not populated, ignore the lit field - obviously that's just broken. -+ // It can't be lit and not populated. -+ // Let's take a look at chunk generation logic for a chunk that is not populated, or even near a populated chunk. -+ // It actually will generate a chunk up to the "carved" stage. It generates the base terrain, (i.e using noise -+ // to figure out where stone is, dirt, grass) and it will generate caves. Nothing else though. No populators. -+ // So "carved" is the correct stage to use, not empty. Setting it to empty would clobber chunk data, when we don't -+ // need to. If it is populated, at least set it to decorated. If it is lit and populated, set it to mobs_spawned. Else, -+ // it is carved. -+ // This change also fixes the random light check "bug" (really this is Mojang's fault for fucking up the status conversion here) -+ // caused by spigot, which would not set the lit value for some chunks. Now those chunks will not be regenerated. -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final boolean terrainPopulated = level.getBoolean("TerrainPopulated"); -+ final boolean lightPopulated = level.getBoolean("LightPopulated") || level.getNumber("LightPopulated") == null; -+ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated"); -+ -+ level.setString("Status", newStatus); -+ level.setBoolean("hasLegacyStructureData", true); -+ -+ // convert biome byte[] into int[] -+ final byte[] biomes = level.getBytes("Biomes"); -+ if (biomes != null) { -+ final int[] newBiomes = new int[256]; -+ for (int i = 0, len = Math.min(newBiomes.length, biomes.length); i < len; ++i) { -+ newBiomes[i] = biomes[i] & 255; -+ } -+ level.setInts("Biomes", newBiomes); -+ } -+ -+ // ProtoChunks have their own dedicated tick list, so we must convert the TileTicks to that. -+ final ListType ticks = level.getList("TileTicks", ObjectType.MAP); -+ if (ticks != null) { -+ final ListType sections = Types.NBT.createEmptyList(); -+ final ListType[] sectionAccess = new ListType[16]; -+ for (int i = 0; i < sectionAccess.length; ++i) { -+ sections.addList(sectionAccess[i] = Types.NBT.createEmptyList()); -+ } -+ level.setList("ToBeTicked", sections); -+ -+ for (int i = 0, len = ticks.size(); i < len; ++i) { -+ final MapType tick = ticks.getMap(i); -+ -+ final int x = tick.getInt("x"); -+ final int y = tick.getInt("y"); -+ final int z = tick.getInt("z"); -+ final short coordinate = packOffsetCoordinates(x, y, z); -+ -+ sectionAccess[y >> 4].addShort(coordinate); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section, "Palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final ListType list = data.getList("Children", ObjectType.MAP); -+ if (list != null) { -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType child = list.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CA", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CC", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_STATE, child, "CD", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, data, "biome", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private V1466() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java -new file mode 100644 -index 0000000000000000000000000000000000000000..68dd3ce7709a998bc50a5080fe9c805b71a88365 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V147.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V147 { -+ -+ protected static final int VERSION = MCVersions.V15W46A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("ArmorStand", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("Silent") && !data.getBoolean("Marker")) { -+ data.remove("Silent"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V147() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java -new file mode 100644 -index 0000000000000000000000000000000000000000..669509286b18a173826938bae347c1aefffeed51 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1470.java -@@ -0,0 +1,31 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V1470 { -+ -+ protected static final int VERSION = MCVersions.V18W08A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:turtle"); -+ registerMob("minecraft:cod_mob"); -+ registerMob("minecraft:tropical_fish"); -+ registerMob("minecraft:salmon_mob"); -+ registerMob("minecraft:puffer_fish"); -+ registerMob("minecraft:phantom"); -+ registerMob("minecraft:dolphin"); -+ registerMob("minecraft:drowned"); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trident", new DataWalkerTypePaths<>(MCTypeRegistry.BLOCK_STATE, "inBlockState")); -+ } -+ -+ private V1470() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4cf1085b4392c9b348ebe65590cdbf287a908a38 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1474.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1474 { -+ -+ protected static final int VERSION = MCVersions.V18W10B; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getInt("Color") == 10) { -+ data.setByte("Color", (byte)16); -+ } -+ return null; -+ } -+ }); -+ // data hooks ensure the inputs are namespaced -+ ConverterAbstractBlockRename.register(VERSION, (final String old) -> { -+ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; -+ }); -+ ConverterAbstractItemRename.register(VERSION, (final String old) -> { -+ return "minecraft:purple_shulker_box".equals(old) ? "minecraft:shulker_box" : null; -+ }); -+ -+ } -+ -+ private V1474() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40b64efb8717b2de0ff13af87bcc99119c9f7c9d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1475.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1475 { -+ -+ protected static final int VERSION = MCVersions.V18W10B + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, -+ ImmutableMap.of( -+ "minecraft:flowing_water", "minecraft:water", -+ "minecraft:flowing_lava", "minecraft:lava" -+ )::get); -+ } -+ -+ private V1475() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e5373d4e6ca027749f634e9a508bd81b9b41ed3e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1480.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1480 { -+ -+ protected static final int VERSION = MCVersions.V18W14A + 1; -+ -+ public static final Map RENAMED_IDS = ImmutableMap.builder() -+ .put("minecraft:blue_coral", "minecraft:tube_coral_block") -+ .put("minecraft:pink_coral", "minecraft:brain_coral_block") -+ .put("minecraft:purple_coral", "minecraft:bubble_coral_block") -+ .put("minecraft:red_coral", "minecraft:fire_coral_block") -+ .put("minecraft:yellow_coral", "minecraft:horn_coral_block") -+ .put("minecraft:blue_coral_plant", "minecraft:tube_coral") -+ .put("minecraft:pink_coral_plant", "minecraft:brain_coral") -+ .put("minecraft:purple_coral_plant", "minecraft:bubble_coral") -+ .put("minecraft:red_coral_plant", "minecraft:fire_coral") -+ .put("minecraft:yellow_coral_plant", "minecraft:horn_coral") -+ .put("minecraft:blue_coral_fan", "minecraft:tube_coral_fan") -+ .put("minecraft:pink_coral_fan", "minecraft:brain_coral_fan") -+ .put("minecraft:purple_coral_fan", "minecraft:bubble_coral_fan") -+ .put("minecraft:red_coral_fan", "minecraft:fire_coral_fan") -+ .put("minecraft:yellow_coral_fan", "minecraft:horn_coral_fan") -+ .put("minecraft:blue_dead_coral", "minecraft:dead_tube_coral") -+ .put("minecraft:pink_dead_coral", "minecraft:dead_brain_coral") -+ .put("minecraft:purple_dead_coral", "minecraft:dead_bubble_coral") -+ .put("minecraft:red_dead_coral", "minecraft:dead_fire_coral") -+ .put("minecraft:yellow_dead_coral", "minecraft:dead_horn_coral") -+ .build(); -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_IDS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_IDS::get); -+ } -+ -+ private V1480() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e6fb5d6870514a509f7f1aa5343ed7e762af8a72 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1483.java -@@ -0,0 +1,31 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1483 { -+ -+ protected static final int VERSION = MCVersions.V18W16A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:puffer_fish", "minecraft:pufferfish" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:puffer_fish_spawn_egg", "minecraft:pufferfish_spawn_egg" -+ )::get); -+ -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:puffer_fish", "minecraft:pufferfish"); -+ } -+ -+ private V1483() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f5b9c166304930e095bfc00e8f6b93edb706df48 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1484.java -@@ -0,0 +1,73 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1484 { -+ -+ protected static final int VERSION = MCVersions.V18W19A; -+ -+ public static void register() { -+ final Map renamed = ImmutableMap.of( -+ "minecraft:sea_grass", "minecraft:seagrass", -+ "minecraft:tall_sea_grass", "minecraft:tall_seagrass" -+ ); -+ -+ ConverterAbstractItemRename.register(VERSION, renamed::get); -+ ConverterAbstractBlockRename.register(VERSION, renamed::get); -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType heightmaps = level.getMap("Heightmaps"); -+ -+ if (heightmaps == null) { -+ return null; -+ } -+ -+ final Object liquid = heightmaps.getGeneric("LIQUID"); -+ if (liquid != null) { -+ heightmaps.remove("LIQUID"); -+ heightmaps.setGeneric("WORLD_SURFACE_WG", liquid); -+ } -+ -+ final Object solid = heightmaps.getGeneric("SOLID"); -+ if (solid != null) { -+ heightmaps.remove("SOLID"); -+ heightmaps.setGeneric("OCEAN_FLOOR_WG", solid); -+ heightmaps.setGeneric("OCEAN_FLOOR", solid); -+ } -+ -+ final Object light = heightmaps.getGeneric("LIGHT"); -+ if (light != null) { -+ heightmaps.remove("LIGHT"); -+ heightmaps.setGeneric("LIGHT_BLOCKING", light); -+ } -+ -+ final Object rain = heightmaps.getGeneric("RAIN"); -+ if (rain != null) { -+ heightmaps.remove("RAIN"); -+ heightmaps.setGeneric("MOTION_BLOCKING", rain); -+ heightmaps.setGeneric("MOTION_BLOCKING_NO_LEAVES", rain); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1484() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf7b0a77b30312a32d5fdb10be3fb45d10ba7870 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1486.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1486 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static final Map RENAMED_ENTITY_IDS = ImmutableMap.builder() -+ .put("minecraft:salmon_mob", "minecraft:salmon") -+ .put("minecraft:cod_mob", "minecraft:cod") -+ .build(); -+ public static final Map RENAMED_ITEM_IDS = ImmutableMap.builder() -+ .put("minecraft:salmon_mob_spawn_egg", "minecraft:salmon_spawn_egg") -+ .put("minecraft:cod_mob_spawn_egg", "minecraft:cod_spawn_egg") -+ .build(); -+ -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:cod_mob", "minecraft:cod"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:salmon_mob", "minecraft:salmon"); -+ -+ ConverterAbstractEntityRename.register(VERSION, RENAMED_ENTITY_IDS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); -+ } -+ -+ private V1486() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dba4a64f61b27f1eb820e0e0a3fddb81517acc16 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1487.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1487 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 2; -+ -+ public static void register() { -+ final Map remap = ImmutableMap.of( -+ "minecraft:prismarine_bricks_slab", "minecraft:prismarine_brick_slab", -+ "minecraft:prismarine_bricks_stairs", "minecraft:prismarine_brick_stairs" -+ ); -+ -+ ConverterAbstractItemRename.register(VERSION, remap::get); -+ ConverterAbstractBlockRename.register(VERSION, remap::get); -+ } -+ -+ private V1487() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf3579524a9ba96f2065d98bca928bf920da081c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1488.java -@@ -0,0 +1,88 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1488 { -+ -+ protected static final int VERSION = MCVersions.V18W19B + 3; -+ -+ protected static boolean isIglooPiece(final MapType piece) { -+ return "Iglu".equals(piece.getString("id")); -+ } -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:kelp_top", "minecraft:kelp", -+ "minecraft:kelp", "minecraft:kelp_plant" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:kelp_top", "minecraft:kelp" -+ )::get); -+ -+ // Don't ask me why in V1458 they wrote the converter to NOT do command blocks and THEN in THIS version -+ // to ONLY do command blocks. I don't know. -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:command_block", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return V1458.updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:commandblock_minecart", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ return V1458.updateCustomName(data); -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ boolean isIgloo; -+ if (children != null) { -+ isIgloo = true; -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ if (!isIglooPiece(children.getMap(i))) { -+ isIgloo = false; -+ break; -+ } -+ } -+ } else { -+ isIgloo = false; -+ } -+ -+ if (isIgloo) { -+ data.remove("Children"); -+ data.setString("id", "Igloo"); -+ return null; -+ } -+ -+ if (children != null) { -+ for (int i = 0; i < children.size();) { -+ final MapType child = children.getMap(i); -+ if (isIglooPiece(child)) { -+ children.remove(i); -+ continue; -+ } -+ ++i; -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1488() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb3afa0634cb47cbd5b324a66d140375b1c23e07 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1490.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1490 { -+ -+ protected static final int VERSION = MCVersions.V18W20A + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:melon_block", "minecraft:melon" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:melon_block", "minecraft:melon", -+ "minecraft:melon", "minecraft:melon_slice", -+ "minecraft:speckled_melon", "minecraft:glistering_melon_slice" -+ )::get); -+ } -+ -+ private V1490() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8732c2035ee0659173a8299cc2b0a5f86ace7b0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1492.java -@@ -0,0 +1,152 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import com.mojang.datafixers.util.Pair; -+ -+public final class V1492 { -+ -+ private static final ImmutableMap>> RENAMES = ImmutableMap.>>builder() -+ .put("EndCity", Pair.of( -+ "ECP", -+ ImmutableMap.builder() -+ .put("second_floor", "second_floor_1") -+ .put("third_floor", "third_floor_1") -+ .put("third_floor_c", "third_floor_2") -+ .build() -+ ) -+ ) -+ -+ .put("Mansion", Pair.of( -+ "WMP", -+ ImmutableMap.builder() -+ .put("carpet_south", "carpet_south_1") -+ .put("carpet_west", "carpet_west_1") -+ .put("indoors_door", "indoors_door_1") -+ .put("indoors_wall", "indoors_wall_1") -+ .build() -+ ) -+ ) -+ -+ .put("Igloo", Pair.of( -+ "Iglu", -+ ImmutableMap.builder() -+ .put("minecraft:igloo/igloo_bottom", "minecraft:igloo/bottom") -+ .put("minecraft:igloo/igloo_middle", "minecraft:igloo/middle") -+ .put("minecraft:igloo/igloo_top", "minecraft:igloo/top") -+ .build() -+ ) -+ ) -+ .put("Ocean_Ruin", Pair.of( -+ "ORP", -+ ImmutableMap.builder() -+ .put("minecraft:ruin/big_ruin1_brick", "minecraft:underwater_ruin/big_brick_1") -+ .put("minecraft:ruin/big_ruin2_brick", "minecraft:underwater_ruin/big_brick_2") -+ .put("minecraft:ruin/big_ruin3_brick", "minecraft:underwater_ruin/big_brick_3") -+ .put("minecraft:ruin/big_ruin8_brick", "minecraft:underwater_ruin/big_brick_8") -+ .put("minecraft:ruin/big_ruin1_cracked", "minecraft:underwater_ruin/big_cracked_1") -+ .put("minecraft:ruin/big_ruin2_cracked", "minecraft:underwater_ruin/big_cracked_2") -+ .put("minecraft:ruin/big_ruin3_cracked", "minecraft:underwater_ruin/big_cracked_3") -+ .put("minecraft:ruin/big_ruin8_cracked", "minecraft:underwater_ruin/big_cracked_8") -+ .put("minecraft:ruin/big_ruin1_mossy", "minecraft:underwater_ruin/big_mossy_1") -+ .put("minecraft:ruin/big_ruin2_mossy", "minecraft:underwater_ruin/big_mossy_2") -+ .put("minecraft:ruin/big_ruin3_mossy", "minecraft:underwater_ruin/big_mossy_3") -+ .put("minecraft:ruin/big_ruin8_mossy", "minecraft:underwater_ruin/big_mossy_8") -+ .put("minecraft:ruin/big_ruin_warm4", "minecraft:underwater_ruin/big_warm_4") -+ .put("minecraft:ruin/big_ruin_warm5", "minecraft:underwater_ruin/big_warm_5") -+ .put("minecraft:ruin/big_ruin_warm6", "minecraft:underwater_ruin/big_warm_6") -+ .put("minecraft:ruin/big_ruin_warm7", "minecraft:underwater_ruin/big_warm_7") -+ .put("minecraft:ruin/ruin1_brick", "minecraft:underwater_ruin/brick_1") -+ .put("minecraft:ruin/ruin2_brick", "minecraft:underwater_ruin/brick_2") -+ .put("minecraft:ruin/ruin3_brick", "minecraft:underwater_ruin/brick_3") -+ .put("minecraft:ruin/ruin4_brick", "minecraft:underwater_ruin/brick_4") -+ .put("minecraft:ruin/ruin5_brick", "minecraft:underwater_ruin/brick_5") -+ .put("minecraft:ruin/ruin6_brick", "minecraft:underwater_ruin/brick_6") -+ .put("minecraft:ruin/ruin7_brick", "minecraft:underwater_ruin/brick_7") -+ .put("minecraft:ruin/ruin8_brick", "minecraft:underwater_ruin/brick_8") -+ .put("minecraft:ruin/ruin1_cracked", "minecraft:underwater_ruin/cracked_1") -+ .put("minecraft:ruin/ruin2_cracked", "minecraft:underwater_ruin/cracked_2") -+ .put("minecraft:ruin/ruin3_cracked", "minecraft:underwater_ruin/cracked_3") -+ .put("minecraft:ruin/ruin4_cracked", "minecraft:underwater_ruin/cracked_4") -+ .put("minecraft:ruin/ruin5_cracked", "minecraft:underwater_ruin/cracked_5") -+ .put("minecraft:ruin/ruin6_cracked", "minecraft:underwater_ruin/cracked_6") -+ .put("minecraft:ruin/ruin7_cracked", "minecraft:underwater_ruin/cracked_7") -+ .put("minecraft:ruin/ruin8_cracked", "minecraft:underwater_ruin/cracked_8") -+ .put("minecraft:ruin/ruin1_mossy", "minecraft:underwater_ruin/mossy_1") -+ .put("minecraft:ruin/ruin2_mossy", "minecraft:underwater_ruin/mossy_2") -+ .put("minecraft:ruin/ruin3_mossy", "minecraft:underwater_ruin/mossy_3") -+ .put("minecraft:ruin/ruin4_mossy", "minecraft:underwater_ruin/mossy_4") -+ .put("minecraft:ruin/ruin5_mossy", "minecraft:underwater_ruin/mossy_5") -+ .put("minecraft:ruin/ruin6_mossy", "minecraft:underwater_ruin/mossy_6") -+ .put("minecraft:ruin/ruin7_mossy", "minecraft:underwater_ruin/mossy_7") -+ .put("minecraft:ruin/ruin8_mossy", "minecraft:underwater_ruin/mossy_8") -+ .put("minecraft:ruin/ruin_warm1", "minecraft:underwater_ruin/warm_1") -+ .put("minecraft:ruin/ruin_warm2", "minecraft:underwater_ruin/warm_2") -+ .put("minecraft:ruin/ruin_warm3", "minecraft:underwater_ruin/warm_3") -+ .put("minecraft:ruin/ruin_warm4", "minecraft:underwater_ruin/warm_4") -+ .put("minecraft:ruin/ruin_warm5", "minecraft:underwater_ruin/warm_5") -+ .put("minecraft:ruin/ruin_warm6", "minecraft:underwater_ruin/warm_6") -+ .put("minecraft:ruin/ruin_warm7", "minecraft:underwater_ruin/warm_7") -+ .put("minecraft:ruin/ruin_warm8", "minecraft:underwater_ruin/warm_8") -+ .put("minecraft:ruin/big_brick_1", "minecraft:underwater_ruin/big_brick_1") -+ .put("minecraft:ruin/big_brick_2", "minecraft:underwater_ruin/big_brick_2") -+ .put("minecraft:ruin/big_brick_3", "minecraft:underwater_ruin/big_brick_3") -+ .put("minecraft:ruin/big_brick_8", "minecraft:underwater_ruin/big_brick_8") -+ .put("minecraft:ruin/big_mossy_1", "minecraft:underwater_ruin/big_mossy_1") -+ .put("minecraft:ruin/big_mossy_2", "minecraft:underwater_ruin/big_mossy_2") -+ .put("minecraft:ruin/big_mossy_3", "minecraft:underwater_ruin/big_mossy_3") -+ .put("minecraft:ruin/big_mossy_8", "minecraft:underwater_ruin/big_mossy_8") -+ .put("minecraft:ruin/big_cracked_1", "minecraft:underwater_ruin/big_cracked_1") -+ .put("minecraft:ruin/big_cracked_2", "minecraft:underwater_ruin/big_cracked_2") -+ .put("minecraft:ruin/big_cracked_3", "minecraft:underwater_ruin/big_cracked_3") -+ .put("minecraft:ruin/big_cracked_8", "minecraft:underwater_ruin/big_cracked_8") -+ .put("minecraft:ruin/big_warm_4", "minecraft:underwater_ruin/big_warm_4") -+ .put("minecraft:ruin/big_warm_5", "minecraft:underwater_ruin/big_warm_5") -+ .put("minecraft:ruin/big_warm_6", "minecraft:underwater_ruin/big_warm_6") -+ .put("minecraft:ruin/big_warm_7", "minecraft:underwater_ruin/big_warm_7") -+ .build() -+ ) -+ ) -+ -+ .build(); -+ -+ protected static final int VERSION = MCVersions.V18W20B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ if (children == null) { -+ return null; -+ } -+ -+ final String id = data.getString("id"); -+ -+ final Pair> renames = RENAMES.get(id); -+ if (renames == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ if (renames.getFirst().equals(child.getString("id"))) { -+ final String template = child.getString("Template", ""); -+ child.setString("Template", renames.getSecond().getOrDefault(template, template)); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1492() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java -new file mode 100644 -index 0000000000000000000000000000000000000000..50411042a83d58c4c36768a8f5196b4b41b4d095 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1494.java -@@ -0,0 +1,89 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V1494 { -+ -+ protected static final int VERSION = MCVersions.V18W20C + 1; -+ -+ private static final Int2ObjectOpenHashMap ENCH_ID_TO_NAME = new Int2ObjectOpenHashMap<>(); -+ static { -+ ENCH_ID_TO_NAME.put(0, "minecraft:protection"); -+ ENCH_ID_TO_NAME.put(1, "minecraft:fire_protection"); -+ ENCH_ID_TO_NAME.put(2, "minecraft:feather_falling"); -+ ENCH_ID_TO_NAME.put(3, "minecraft:blast_protection"); -+ ENCH_ID_TO_NAME.put(4, "minecraft:projectile_protection"); -+ ENCH_ID_TO_NAME.put(5, "minecraft:respiration"); -+ ENCH_ID_TO_NAME.put(6, "minecraft:aqua_affinity"); -+ ENCH_ID_TO_NAME.put(7, "minecraft:thorns"); -+ ENCH_ID_TO_NAME.put(8, "minecraft:depth_strider"); -+ ENCH_ID_TO_NAME.put(9, "minecraft:frost_walker"); -+ ENCH_ID_TO_NAME.put(10, "minecraft:binding_curse"); -+ ENCH_ID_TO_NAME.put(16, "minecraft:sharpness"); -+ ENCH_ID_TO_NAME.put(17, "minecraft:smite"); -+ ENCH_ID_TO_NAME.put(18, "minecraft:bane_of_arthropods"); -+ ENCH_ID_TO_NAME.put(19, "minecraft:knockback"); -+ ENCH_ID_TO_NAME.put(20, "minecraft:fire_aspect"); -+ ENCH_ID_TO_NAME.put(21, "minecraft:looting"); -+ ENCH_ID_TO_NAME.put(22, "minecraft:sweeping"); -+ ENCH_ID_TO_NAME.put(32, "minecraft:efficiency"); -+ ENCH_ID_TO_NAME.put(33, "minecraft:silk_touch"); -+ ENCH_ID_TO_NAME.put(34, "minecraft:unbreaking"); -+ ENCH_ID_TO_NAME.put(35, "minecraft:fortune"); -+ ENCH_ID_TO_NAME.put(48, "minecraft:power"); -+ ENCH_ID_TO_NAME.put(49, "minecraft:punch"); -+ ENCH_ID_TO_NAME.put(50, "minecraft:flame"); -+ ENCH_ID_TO_NAME.put(51, "minecraft:infinity"); -+ ENCH_ID_TO_NAME.put(61, "minecraft:luck_of_the_sea"); -+ ENCH_ID_TO_NAME.put(62, "minecraft:lure"); -+ ENCH_ID_TO_NAME.put(65, "minecraft:loyalty"); -+ ENCH_ID_TO_NAME.put(66, "minecraft:impaling"); -+ ENCH_ID_TO_NAME.put(67, "minecraft:riptide"); -+ ENCH_ID_TO_NAME.put(68, "minecraft:channeling"); -+ ENCH_ID_TO_NAME.put(70, "minecraft:mending"); -+ ENCH_ID_TO_NAME.put(71, "minecraft:vanishing_curse"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final ListType enchants = tag.getList("ench", ObjectType.MAP); -+ if (enchants != null) { -+ tag.remove("ench"); -+ tag.setList("Enchantments", enchants); -+ -+ for (int i = 0, len = enchants.size(); i < len; ++i) { -+ final MapType enchant = enchants.getMap(i); -+ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); -+ } -+ } -+ -+ final ListType storedEnchants = tag.getList("StoredEnchantments", ObjectType.MAP); -+ if (storedEnchants != null) { -+ for (int i = 0, len = storedEnchants.size(); i < len; ++i) { -+ final MapType enchant = storedEnchants.getMap(i); -+ enchant.setString("id", ENCH_ID_TO_NAME.getOrDefault(enchant.getInt("id"), "null")); -+ } -+ } -+ -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V1494() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c56d50c552d4609474f5b3b6b0b8be8b575764ea ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1496.java -@@ -0,0 +1,370 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.datafixers.DataFixUtils; -+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import net.minecraft.util.datafix.PackedBitStorage; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V1496 { -+ -+ private V1496() {} -+ -+ protected static final int VERSION = MCVersions.V18W21B; -+ -+ private static final int[][] DIRECTIONS = new int[][] { -+ new int[] {-1, 0, 0}, -+ new int[] {1, 0, 0}, -+ new int[] {0, -1, 0}, -+ new int[] {0, 1, 0}, -+ new int[] {0, 0, -1}, -+ new int[] {0, 0, 1} -+ }; -+ -+ private static final Object2IntOpenHashMap LEAVES_TO_ID = new Object2IntOpenHashMap<>(); -+ static { -+ LEAVES_TO_ID.put("minecraft:acacia_leaves", 0); -+ LEAVES_TO_ID.put("minecraft:birch_leaves", 1); -+ LEAVES_TO_ID.put("minecraft:dark_oak_leaves", 2); -+ LEAVES_TO_ID.put("minecraft:jungle_leaves", 3); -+ LEAVES_TO_ID.put("minecraft:oak_leaves", 4); -+ LEAVES_TO_ID.put("minecraft:spruce_leaves", 5); -+ } -+ -+ private static final Set LOGS = new HashSet<>( -+ Arrays.asList( -+ "minecraft:acacia_bark", -+ "minecraft:birch_bark", -+ "minecraft:dark_oak_bark", -+ "minecraft:jungle_bark", -+ "minecraft:oak_bark", -+ "minecraft:spruce_bark", -+ "minecraft:acacia_log", -+ "minecraft:birch_log", -+ "minecraft:dark_oak_log", -+ "minecraft:jungle_log", -+ "minecraft:oak_log", -+ "minecraft:spruce_log", -+ "minecraft:stripped_acacia_log", -+ "minecraft:stripped_birch_log", -+ "minecraft:stripped_dark_oak_log", -+ "minecraft:stripped_jungle_log", -+ "minecraft:stripped_oak_log", -+ "minecraft:stripped_spruce_log" -+ ) -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sectionsNBT = level.getList("Sections", ObjectType.MAP); -+ if (sectionsNBT == null) { -+ return null; -+ } -+ -+ int newSides = 0; -+ -+ final LeavesSection[] sections = new LeavesSection[16]; -+ boolean skippable = true; -+ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { -+ final LeavesSection section = new LeavesSection(sectionsNBT.getMap(i)); -+ sections[section.sectionY] = section; -+ -+ skippable &= section.isSkippable(); -+ } -+ -+ if (skippable) { -+ return null; -+ } -+ -+ final IntOpenHashSet[] positionsByDistance = new IntOpenHashSet[7]; -+ for (int i = 0; i < positionsByDistance.length; ++i) { -+ positionsByDistance[i] = new IntOpenHashSet(); -+ } -+ -+ for (final LeavesSection section : sections) { -+ if (section == null || section.isSkippable()) { -+ continue; -+ } -+ -+ for (int index = 0; index < 4096; ++index) { -+ final int block = section.getBlock(index); -+ if (section.isLog(block)) { -+ positionsByDistance[0].add(section.getSectionY() << 12 | index); -+ } else if (section.isLeaf(block)) { -+ int x = getX(index); -+ int z = getZ(index); -+ newSides |= getSideMask(x == 0, x == 15, z == 0, z == 15); -+ } -+ } -+ } -+ -+ // this is basically supposed to recalculate the distances, because a higher cap was added -+ for (int distance = 1; distance < 7; ++distance) { -+ final IntOpenHashSet positionsLess = positionsByDistance[distance - 1]; -+ final IntOpenHashSet positionsEqual = positionsByDistance[distance]; -+ -+ for (final IntIterator iterator = positionsLess.iterator(); iterator.hasNext();) { -+ final int position = iterator.nextInt(); -+ final int fromX = getX(position); -+ final int fromY = getY(position); -+ final int fromZ = getZ(position); -+ -+ for (final int[] direction : DIRECTIONS) { -+ final int toX = fromX + direction[0]; -+ final int toY = fromY + direction[1]; -+ final int toZ = fromZ + direction[2]; -+ -+ if (!(toX >= 0 && toX <= 15 && toZ >= 0 && toZ <= 15 && toY >= 0 && toY <= 255)) { -+ continue; -+ } -+ -+ final LeavesSection toSection = sections[toY >> 4]; -+ if (toSection == null || toSection.isSkippable()) { -+ continue; -+ } -+ -+ final int sectionLocalIndex = getIndex(toX, toY & 15, toZ); -+ final int toBlock = toSection.getBlock(sectionLocalIndex); -+ -+ if (toSection.isLeaf(toBlock)) { -+ final int newDistance = toSection.getDistance(toBlock); -+ if (newDistance > distance) { -+ toSection.setDistance(sectionLocalIndex, toBlock, distance); -+ positionsEqual.add(getIndex(toX, toY, toZ)); -+ } -+ } -+ } -+ } -+ } -+ -+ // done updating blocks, now just update the blockstates and palette -+ for (int i = 0, len = sectionsNBT.size(); i < len; ++i) { -+ final MapType sectionNBT = sectionsNBT.getMap(i); -+ final int y = sectionNBT.getInt("Y"); -+ final LeavesSection section = sections[y]; -+ -+ section.writeInto(sectionNBT); -+ } -+ -+ // if sides changed during process, update it now -+ if (newSides != 0) { -+ MapType upgradeData = level.getMap("UpgradeData"); -+ if (upgradeData == null) { -+ level.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); -+ } -+ -+ upgradeData.setByte("Sides", (byte)(upgradeData.getByte("Sides") | newSides)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static int getIndex(final int x, final int y, final int z) { -+ return y << 8 | z << 4 | x; -+ } -+ -+ public static int getX(final int index) { -+ return index & 15; -+ } -+ -+ public static int getY(final int index) { -+ return index >> 8 & 255; -+ } -+ -+ public static int getZ(final int index) { -+ return index >> 4 & 15; -+ } -+ -+ public static int getSideMask(final boolean noLeft, final boolean noRight, final boolean noBack, final boolean noForward) { -+ final int ret; -+ -+ if (noBack) { -+ if (noRight) { -+ ret = 2; -+ } else if (noLeft) { -+ ret = 128; -+ } else { -+ ret = 1; -+ } -+ } else if (noForward) { -+ if (noLeft) { -+ ret = 32; -+ } else if (noRight) { -+ ret = 8; -+ } else { -+ ret = 16; -+ } -+ } else if (noRight) { -+ ret = 4; -+ } else if (noLeft) { -+ ret = 64; -+ } else { -+ ret = 0; -+ } -+ -+ return ret; -+ } -+ -+ public abstract static class Section { -+ protected final ListType palette; -+ protected final int sectionY; -+ protected PackedBitStorage storage; -+ -+ public Section(final MapType section) { -+ this.palette = section.getList("Palette", ObjectType.MAP); -+ this.sectionY = section.getInt("Y"); -+ this.readStorage(section); -+ } -+ -+ protected void readStorage(final MapType section) { -+ if (this.initSkippable()) { -+ this.storage = null; -+ } else { -+ final long[] states = section.getLongs("BlockStates"); -+ final int bits = Math.max(4, DataFixUtils.ceillog2(this.palette.size())); -+ this.storage = new PackedBitStorage(bits, 4096, states); -+ } -+ } -+ -+ public void writeInto(final MapType section) { -+ if (this.isSkippable()) { -+ return; -+ } -+ -+ section.setList("Palette", this.palette); -+ section.setLongs("BlockStates", this.storage.getRaw()); -+ } -+ -+ public boolean isSkippable() { -+ return this.storage == null; -+ } -+ -+ public int getBlock(final int index) { -+ return this.storage.get(index); -+ } -+ -+ protected int getStateId(final String name, final boolean persistent, final int distance) { -+ return LEAVES_TO_ID.getInt(name) << 5 | (persistent ? 16 : 0) | distance; -+ } -+ -+ protected int getSectionY() { -+ return this.sectionY; -+ } -+ -+ protected abstract boolean initSkippable(); -+ } -+ -+ public static final class LeavesSection extends Section { -+ private IntOpenHashSet leaveIds; -+ private IntOpenHashSet logIds; -+ private Int2IntOpenHashMap stateToIdMap; -+ -+ public LeavesSection(final MapType section) { -+ super(section); -+ } -+ -+ @Override -+ protected boolean initSkippable() { -+ this.leaveIds = new IntOpenHashSet(); -+ this.logIds = new IntOpenHashSet(); -+ this.stateToIdMap = new Int2IntOpenHashMap(); -+ this.stateToIdMap.defaultReturnValue(-1); -+ -+ for(int i = 0; i < this.palette.size(); ++i) { -+ final MapType blockState = this.palette.getMap(i); -+ final String name = blockState.getString("Name", ""); -+ if (LEAVES_TO_ID.containsKey(name)) { -+ final MapType properties = blockState.getMap("Properties"); -+ final boolean notDecayable = properties != null && "false".equals(properties.getString("decayable")); -+ -+ this.leaveIds.add(i); -+ this.stateToIdMap.put(this.getStateId(name, notDecayable, 7), i); -+ this.palette.setMap(i, this.makeNewLeafTag(name, notDecayable, 7)); -+ } -+ -+ if (LOGS.contains(name)) { -+ this.logIds.add(i); -+ } -+ } -+ -+ return this.leaveIds.isEmpty() && this.logIds.isEmpty(); -+ } -+ -+ private MapType makeNewLeafTag(final String name, final boolean notDecayable, final int distance) { -+ final MapType properties = Types.NBT.createEmptyMap(); -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("Name", name); -+ ret.setMap("Properties", properties); -+ -+ properties.setString("persistent", Boolean.toString(notDecayable)); -+ properties.setString("distance", Integer.toString(distance)); -+ -+ return ret; -+ } -+ -+ public boolean isLog(final int id) { -+ return this.logIds.contains(id); -+ } -+ -+ public boolean isLeaf(final int id) { -+ return this.leaveIds.contains(id); -+ } -+ -+ // only call for logs or leaves, will throw otherwise! -+ private int getDistance(final int id) { -+ if (this.isLog(id)) { -+ return 0; -+ } -+ -+ return Integer.parseInt(this.palette.getMap(id).getMap("Properties").getString("distance")); -+ } -+ -+ private void setDistance(final int index, final int id, final int distance) { -+ final MapType state = this.palette.getMap(id); -+ final String name = state.getString("Name"); -+ final boolean persistent = "true".equals(state.getMap("Properties").getString("persistent")); -+ final int newState = this.getStateId(name, persistent, distance); -+ int newStateId; -+ if ((newStateId = this.stateToIdMap.get(newState)) == -1) { -+ newStateId = this.palette.size(); -+ this.leaveIds.add(newStateId); -+ this.stateToIdMap.put(newState, newStateId); -+ this.palette.addMap(this.makeNewLeafTag(name, persistent, distance)); -+ } -+ -+ if (1 << this.storage.getBits() <= newStateId) { -+ // need to widen storage -+ final PackedBitStorage newStorage = new PackedBitStorage(this.storage.getBits() + 1, 4096); -+ -+ for(int i = 0; i < 4096; ++i) { -+ newStorage.set(i, this.storage.get(i)); -+ } -+ -+ this.storage = newStorage; -+ } -+ -+ this.storage.set(index, newStateId); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9208152e2a158470f37b0eb022478e8e5287c12b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1500.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1500 { -+ -+ protected static final int VERSION = MCVersions.V18W22C + 1; -+ -+ private V1500() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("DUMMY", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setBoolean("keepPacked", true); -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c497faa60c37f30ceb0d7c394446d6074599c1b7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1501.java -@@ -0,0 +1,75 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1501 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE1; -+ -+ private static final Map RENAMES = ImmutableMap.builder() -+ .put("minecraft:recipes/brewing/speckled_melon", "minecraft:recipes/brewing/glistering_melon_slice") -+ .put("minecraft:recipes/building_blocks/black_stained_hardened_clay", "minecraft:recipes/building_blocks/black_terracotta") -+ .put("minecraft:recipes/building_blocks/blue_stained_hardened_clay", "minecraft:recipes/building_blocks/blue_terracotta") -+ .put("minecraft:recipes/building_blocks/brown_stained_hardened_clay", "minecraft:recipes/building_blocks/brown_terracotta") -+ .put("minecraft:recipes/building_blocks/cyan_stained_hardened_clay", "minecraft:recipes/building_blocks/cyan_terracotta") -+ .put("minecraft:recipes/building_blocks/gray_stained_hardened_clay", "minecraft:recipes/building_blocks/gray_terracotta") -+ .put("minecraft:recipes/building_blocks/green_stained_hardened_clay", "minecraft:recipes/building_blocks/green_terracotta") -+ .put("minecraft:recipes/building_blocks/light_blue_stained_hardened_clay", "minecraft:recipes/building_blocks/light_blue_terracotta") -+ .put("minecraft:recipes/building_blocks/light_gray_stained_hardened_clay", "minecraft:recipes/building_blocks/light_gray_terracotta") -+ .put("minecraft:recipes/building_blocks/lime_stained_hardened_clay", "minecraft:recipes/building_blocks/lime_terracotta") -+ .put("minecraft:recipes/building_blocks/magenta_stained_hardened_clay", "minecraft:recipes/building_blocks/magenta_terracotta") -+ .put("minecraft:recipes/building_blocks/orange_stained_hardened_clay", "minecraft:recipes/building_blocks/orange_terracotta") -+ .put("minecraft:recipes/building_blocks/pink_stained_hardened_clay", "minecraft:recipes/building_blocks/pink_terracotta") -+ .put("minecraft:recipes/building_blocks/purple_stained_hardened_clay", "minecraft:recipes/building_blocks/purple_terracotta") -+ .put("minecraft:recipes/building_blocks/red_stained_hardened_clay", "minecraft:recipes/building_blocks/red_terracotta") -+ .put("minecraft:recipes/building_blocks/white_stained_hardened_clay", "minecraft:recipes/building_blocks/white_terracotta") -+ .put("minecraft:recipes/building_blocks/yellow_stained_hardened_clay", "minecraft:recipes/building_blocks/yellow_terracotta") -+ .put("minecraft:recipes/building_blocks/acacia_wooden_slab", "minecraft:recipes/building_blocks/acacia_slab") -+ .put("minecraft:recipes/building_blocks/birch_wooden_slab", "minecraft:recipes/building_blocks/birch_slab") -+ .put("minecraft:recipes/building_blocks/dark_oak_wooden_slab", "minecraft:recipes/building_blocks/dark_oak_slab") -+ .put("minecraft:recipes/building_blocks/jungle_wooden_slab", "minecraft:recipes/building_blocks/jungle_slab") -+ .put("minecraft:recipes/building_blocks/oak_wooden_slab", "minecraft:recipes/building_blocks/oak_slab") -+ .put("minecraft:recipes/building_blocks/spruce_wooden_slab", "minecraft:recipes/building_blocks/spruce_slab") -+ .put("minecraft:recipes/building_blocks/brick_block", "minecraft:recipes/building_blocks/bricks") -+ .put("minecraft:recipes/building_blocks/chiseled_stonebrick", "minecraft:recipes/building_blocks/chiseled_stone_bricks") -+ .put("minecraft:recipes/building_blocks/end_bricks", "minecraft:recipes/building_blocks/end_stone_bricks") -+ .put("minecraft:recipes/building_blocks/lit_pumpkin", "minecraft:recipes/building_blocks/jack_o_lantern") -+ .put("minecraft:recipes/building_blocks/magma", "minecraft:recipes/building_blocks/magma_block") -+ .put("minecraft:recipes/building_blocks/melon_block", "minecraft:recipes/building_blocks/melon") -+ .put("minecraft:recipes/building_blocks/mossy_stonebrick", "minecraft:recipes/building_blocks/mossy_stone_bricks") -+ .put("minecraft:recipes/building_blocks/nether_brick", "minecraft:recipes/building_blocks/nether_bricks") -+ .put("minecraft:recipes/building_blocks/pillar_quartz_block", "minecraft:recipes/building_blocks/quartz_pillar") -+ .put("minecraft:recipes/building_blocks/red_nether_brick", "minecraft:recipes/building_blocks/red_nether_bricks") -+ .put("minecraft:recipes/building_blocks/snow", "minecraft:recipes/building_blocks/snow_block") -+ .put("minecraft:recipes/building_blocks/smooth_red_sandstone", "minecraft:recipes/building_blocks/cut_red_sandstone") -+ .put("minecraft:recipes/building_blocks/smooth_sandstone", "minecraft:recipes/building_blocks/cut_sandstone") -+ .put("minecraft:recipes/building_blocks/stonebrick", "minecraft:recipes/building_blocks/stone_bricks") -+ .put("minecraft:recipes/building_blocks/stone_stairs", "minecraft:recipes/building_blocks/cobblestone_stairs") -+ .put("minecraft:recipes/building_blocks/string_to_wool", "minecraft:recipes/building_blocks/white_wool_from_string") -+ .put("minecraft:recipes/decorations/fence", "minecraft:recipes/decorations/oak_fence") -+ .put("minecraft:recipes/decorations/purple_shulker_box", "minecraft:recipes/decorations/shulker_box") -+ .put("minecraft:recipes/decorations/slime", "minecraft:recipes/decorations/slime_block") -+ .put("minecraft:recipes/decorations/snow_layer", "minecraft:recipes/decorations/snow") -+ .put("minecraft:recipes/misc/bone_meal_from_block", "minecraft:recipes/misc/bone_meal_from_bone_block") -+ .put("minecraft:recipes/misc/bone_meal_from_bone", "minecraft:recipes/misc/bone_meal") -+ .put("minecraft:recipes/misc/gold_ingot_from_block", "minecraft:recipes/misc/gold_ingot_from_gold_block") -+ .put("minecraft:recipes/misc/iron_ingot_from_block", "minecraft:recipes/misc/iron_ingot_from_iron_block") -+ .put("minecraft:recipes/redstone/fence_gate", "minecraft:recipes/redstone/oak_fence_gate") -+ .put("minecraft:recipes/redstone/noteblock", "minecraft:recipes/redstone/note_block") -+ .put("minecraft:recipes/redstone/trapdoor", "minecraft:recipes/redstone/oak_trapdoor") -+ .put("minecraft:recipes/redstone/wooden_button", "minecraft:recipes/redstone/oak_button") -+ .put("minecraft:recipes/redstone/wooden_door", "minecraft:recipes/redstone/oak_door") -+ .put("minecraft:recipes/redstone/wooden_pressure_plate", "minecraft:recipes/redstone/oak_pressure_plate") -+ .put("minecraft:recipes/transportation/boat", "minecraft:recipes/transportation/oak_boat") -+ .put("minecraft:recipes/transportation/golden_rail", "minecraft:recipes/transportation/powered_rail") -+ .build(); -+ -+ private V1501() {} -+ -+ public static void register() { -+ ConverterAbstractAdvancementsRename.register(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7db79b279a047ec5907a3fb2c6e1a75be14a2f68 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1502.java -@@ -0,0 +1,74 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1502 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE2; -+ -+ private static final Map RECIPES_UPDATES = ImmutableMap.builder() -+ .put("minecraft:acacia_wooden_slab", "minecraft:acacia_slab") -+ .put("minecraft:birch_wooden_slab", "minecraft:birch_slab") -+ .put("minecraft:black_stained_hardened_clay", "minecraft:black_terracotta") -+ .put("minecraft:blue_stained_hardened_clay", "minecraft:blue_terracotta") -+ .put("minecraft:boat", "minecraft:oak_boat") -+ .put("minecraft:bone_meal_from_block", "minecraft:bone_meal_from_bone_block") -+ .put("minecraft:bone_meal_from_bone", "minecraft:bone_meal") -+ .put("minecraft:brick_block", "minecraft:bricks") -+ .put("minecraft:brown_stained_hardened_clay", "minecraft:brown_terracotta") -+ .put("minecraft:chiseled_stonebrick", "minecraft:chiseled_stone_bricks") -+ .put("minecraft:cyan_stained_hardened_clay", "minecraft:cyan_terracotta") -+ .put("minecraft:dark_oak_wooden_slab", "minecraft:dark_oak_slab") -+ .put("minecraft:end_bricks", "minecraft:end_stone_bricks") -+ .put("minecraft:fence_gate", "minecraft:oak_fence_gate") -+ .put("minecraft:fence", "minecraft:oak_fence") -+ .put("minecraft:golden_rail", "minecraft:powered_rail") -+ .put("minecraft:gold_ingot_from_block", "minecraft:gold_ingot_from_gold_block") -+ .put("minecraft:gray_stained_hardened_clay", "minecraft:gray_terracotta") -+ .put("minecraft:green_stained_hardened_clay", "minecraft:green_terracotta") -+ .put("minecraft:iron_ingot_from_block", "minecraft:iron_ingot_from_iron_block") -+ .put("minecraft:jungle_wooden_slab", "minecraft:jungle_slab") -+ .put("minecraft:light_blue_stained_hardened_clay", "minecraft:light_blue_terracotta") -+ .put("minecraft:light_gray_stained_hardened_clay", "minecraft:light_gray_terracotta") -+ .put("minecraft:lime_stained_hardened_clay", "minecraft:lime_terracotta") -+ .put("minecraft:lit_pumpkin", "minecraft:jack_o_lantern") -+ .put("minecraft:magenta_stained_hardened_clay", "minecraft:magenta_terracotta") -+ .put("minecraft:magma", "minecraft:magma_block") -+ .put("minecraft:melon_block", "minecraft:melon") -+ .put("minecraft:mossy_stonebrick", "minecraft:mossy_stone_bricks") -+ .put("minecraft:noteblock", "minecraft:note_block") -+ .put("minecraft:oak_wooden_slab", "minecraft:oak_slab") -+ .put("minecraft:orange_stained_hardened_clay", "minecraft:orange_terracotta") -+ .put("minecraft:pillar_quartz_block", "minecraft:quartz_pillar") -+ .put("minecraft:pink_stained_hardened_clay", "minecraft:pink_terracotta") -+ .put("minecraft:purple_shulker_box", "minecraft:shulker_box") -+ .put("minecraft:purple_stained_hardened_clay", "minecraft:purple_terracotta") -+ .put("minecraft:red_nether_brick", "minecraft:red_nether_bricks") -+ .put("minecraft:red_stained_hardened_clay", "minecraft:red_terracotta") -+ .put("minecraft:slime", "minecraft:slime_block") -+ .put("minecraft:smooth_red_sandstone", "minecraft:cut_red_sandstone") -+ .put("minecraft:smooth_sandstone", "minecraft:cut_sandstone") -+ .put("minecraft:snow_layer", "minecraft:snow") -+ .put("minecraft:snow", "minecraft:snow_block") -+ .put("minecraft:speckled_melon", "minecraft:glistering_melon_slice") -+ .put("minecraft:spruce_wooden_slab", "minecraft:spruce_slab") -+ .put("minecraft:stonebrick", "minecraft:stone_bricks") -+ .put("minecraft:stone_stairs", "minecraft:cobblestone_stairs") -+ .put("minecraft:string_to_wool", "minecraft:white_wool_from_string") -+ .put("minecraft:trapdoor", "minecraft:oak_trapdoor") -+ .put("minecraft:white_stained_hardened_clay", "minecraft:white_terracotta") -+ .put("minecraft:wooden_button", "minecraft:oak_button") -+ .put("minecraft:wooden_door", "minecraft:oak_door") -+ .put("minecraft:wooden_pressure_plate", "minecraft:oak_pressure_plate") -+ .put("minecraft:yellow_stained_hardened_clay", "minecraft:yellow_terracotta") -+ .build(); -+ -+ private V1502() {} -+ -+ public static void register() { -+ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef679762aec326e5e1310390bca46971b548e7cd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1506.java -@@ -0,0 +1,219 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.json.JsonMapType; -+import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.common.base.Splitter; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.Lists; -+import com.google.common.collect.Maps; -+import com.mojang.datafixers.util.Pair; -+import com.mojang.serialization.Dynamic; -+import com.mojang.serialization.DynamicOps; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.NbtOps; -+import net.minecraft.nbt.Tag; -+import net.minecraft.util.GsonHelper; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+import java.util.stream.Collectors; -+ -+public final class V1506 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE4 + 2; -+ -+ static final Map MAP = new HashMap<>(); -+ static { -+ MAP.put("0", "minecraft:ocean"); -+ MAP.put("1", "minecraft:plains"); -+ MAP.put("2", "minecraft:desert"); -+ MAP.put("3", "minecraft:mountains"); -+ MAP.put("4", "minecraft:forest"); -+ MAP.put("5", "minecraft:taiga"); -+ MAP.put("6", "minecraft:swamp"); -+ MAP.put("7", "minecraft:river"); -+ MAP.put("8", "minecraft:nether"); -+ MAP.put("9", "minecraft:the_end"); -+ MAP.put("10", "minecraft:frozen_ocean"); -+ MAP.put("11", "minecraft:frozen_river"); -+ MAP.put("12", "minecraft:snowy_tundra"); -+ MAP.put("13", "minecraft:snowy_mountains"); -+ MAP.put("14", "minecraft:mushroom_fields"); -+ MAP.put("15", "minecraft:mushroom_field_shore"); -+ MAP.put("16", "minecraft:beach"); -+ MAP.put("17", "minecraft:desert_hills"); -+ MAP.put("18", "minecraft:wooded_hills"); -+ MAP.put("19", "minecraft:taiga_hills"); -+ MAP.put("20", "minecraft:mountain_edge"); -+ MAP.put("21", "minecraft:jungle"); -+ MAP.put("22", "minecraft:jungle_hills"); -+ MAP.put("23", "minecraft:jungle_edge"); -+ MAP.put("24", "minecraft:deep_ocean"); -+ MAP.put("25", "minecraft:stone_shore"); -+ MAP.put("26", "minecraft:snowy_beach"); -+ MAP.put("27", "minecraft:birch_forest"); -+ MAP.put("28", "minecraft:birch_forest_hills"); -+ MAP.put("29", "minecraft:dark_forest"); -+ MAP.put("30", "minecraft:snowy_taiga"); -+ MAP.put("31", "minecraft:snowy_taiga_hills"); -+ MAP.put("32", "minecraft:giant_tree_taiga"); -+ MAP.put("33", "minecraft:giant_tree_taiga_hills"); -+ MAP.put("34", "minecraft:wooded_mountains"); -+ MAP.put("35", "minecraft:savanna"); -+ MAP.put("36", "minecraft:savanna_plateau"); -+ MAP.put("37", "minecraft:badlands"); -+ MAP.put("38", "minecraft:wooded_badlands_plateau"); -+ MAP.put("39", "minecraft:badlands_plateau"); -+ MAP.put("40", "minecraft:small_end_islands"); -+ MAP.put("41", "minecraft:end_midlands"); -+ MAP.put("42", "minecraft:end_highlands"); -+ MAP.put("43", "minecraft:end_barrens"); -+ MAP.put("44", "minecraft:warm_ocean"); -+ MAP.put("45", "minecraft:lukewarm_ocean"); -+ MAP.put("46", "minecraft:cold_ocean"); -+ MAP.put("47", "minecraft:deep_warm_ocean"); -+ MAP.put("48", "minecraft:deep_lukewarm_ocean"); -+ MAP.put("49", "minecraft:deep_cold_ocean"); -+ MAP.put("50", "minecraft:deep_frozen_ocean"); -+ MAP.put("127", "minecraft:the_void"); -+ MAP.put("129", "minecraft:sunflower_plains"); -+ MAP.put("130", "minecraft:desert_lakes"); -+ MAP.put("131", "minecraft:gravelly_mountains"); -+ MAP.put("132", "minecraft:flower_forest"); -+ MAP.put("133", "minecraft:taiga_mountains"); -+ MAP.put("134", "minecraft:swamp_hills"); -+ MAP.put("140", "minecraft:ice_spikes"); -+ MAP.put("149", "minecraft:modified_jungle"); -+ MAP.put("151", "minecraft:modified_jungle_edge"); -+ MAP.put("155", "minecraft:tall_birch_forest"); -+ MAP.put("156", "minecraft:tall_birch_hills"); -+ MAP.put("157", "minecraft:dark_forest_hills"); -+ MAP.put("158", "minecraft:snowy_taiga_mountains"); -+ MAP.put("160", "minecraft:giant_spruce_taiga"); -+ MAP.put("161", "minecraft:giant_spruce_taiga_hills"); -+ MAP.put("162", "minecraft:modified_gravelly_mountains"); -+ MAP.put("163", "minecraft:shattered_savanna"); -+ MAP.put("164", "minecraft:shattered_savanna_plateau"); -+ MAP.put("165", "minecraft:eroded_badlands"); -+ MAP.put("166", "minecraft:modified_wooded_badlands_plateau"); -+ MAP.put("167", "minecraft:modified_badlands_plateau"); -+ } -+ -+ private V1506() {} -+ -+ public static void register() { -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String generatorOptions = data.getString("generatorOptions"); -+ final String generatorName = data.getString("generatorName"); -+ if ("flat".equalsIgnoreCase(generatorName)) { -+ data.setMap("generatorOptions", V1506.convert(generatorOptions == null ? "" : generatorOptions)); -+ } else if ("buffet".equalsIgnoreCase(generatorName) && generatorOptions != null) { -+ data.setMap("generatorOptions", JsonTypeUtil.convertJsonToNBT(new JsonMapType(GsonHelper.parse(generatorOptions, true), false))); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private static MapType convert(final String param0) { -+ final Dynamic dynamic = convert(param0, NbtOps.INSTANCE); -+ -+ return new NBTMapType((CompoundTag)dynamic.getValue()); -+ } -+ -+ // Yeah I ain't touching that. This is basically magic value hell. -+ private static Dynamic convert(final String generatorSettings, final DynamicOps ops) { -+ final Iterator splitSettings = Splitter.on(';').split(generatorSettings).iterator(); -+ String biome = "minecraft:plains"; -+ final Map> structures = Maps.newHashMap(); -+ final List> layers; -+ if (!generatorSettings.isEmpty() && splitSettings.hasNext()) { -+ layers = getLayersInfoFromString(splitSettings.next()); -+ if (!layers.isEmpty()) { -+ // biome is next -+ if (splitSettings.hasNext()) { -+ biome = MAP.getOrDefault(splitSettings.next(), "minecraft:plains"); -+ } -+ -+ // structures is next -+ if (splitSettings.hasNext()) { -+ final String[] structuresSplit = splitSettings.next().toLowerCase(Locale.ROOT).split(","); -+ -+ for (final String structureString : structuresSplit) { -+ final String[] structureInfo = structureString.split("\\(", 2); -+ if (!structureInfo[0].isEmpty()) { -+ structures.put(structureInfo[0], Maps.newHashMap()); -+ if (structureInfo.length > 1 && structureInfo[1].endsWith(")") && structureInfo[1].length() > 1) { -+ // I can't even guess the mappings for these. Not worth my time, it will work regardless of the mappings -+ final String[] var7 = structureInfo[1].substring(0, structureInfo[1].length() - 1).split(" "); -+ -+ for (final String var8 : var7) { -+ String[] var9 = var8.split("=", 2); -+ if (var9.length == 2) { -+ structures.get(structureInfo[0]).put(var9[0], var9[1]); -+ } -+ } -+ } -+ } -+ } -+ } else { -+ structures.put("village", Maps.newHashMap()); -+ } -+ } -+ } else { -+ layers = Lists.newArrayList(); -+ layers.add(Pair.of(1, "minecraft:bedrock")); -+ layers.add(Pair.of(2, "minecraft:dirt")); -+ layers.add(Pair.of(1, "minecraft:grass_block")); -+ structures.put("village", Maps.newHashMap()); -+ } -+ -+ final T layerTag = ops.createList(layers.stream().map((param1x) -> ops.createMap(ImmutableMap.of(ops.createString("height"), ops.createInt(param1x.getFirst()), ops.createString("block"), ops.createString(param1x.getSecond()))))); -+ final T structuresTag = ops.createMap(structures.entrySet().stream().map((param1x) -> Pair.of(ops.createString(param1x.getKey().toLowerCase(Locale.ROOT)), ops.createMap(param1x.getValue().entrySet().stream().map((param1xx) -> Pair.of(ops.createString(param1xx.getKey()), ops.createString(param1xx.getValue()))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))))).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond))); -+ return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("layers"), layerTag, ops.createString("biome"), ops.createString(biome), ops.createString("structures"), structuresTag))); -+ } -+ -+ private static Pair getLayerInfoFromString(final String layerString) { -+ final String[] split = layerString.split("\\*", 2); -+ int layerCount; -+ if (split.length == 2) { -+ try { -+ layerCount = Integer.parseInt(split[0]); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } else { -+ layerCount = 1; -+ } -+ -+ final String blockName = split[split.length - 1]; -+ return Pair.of(layerCount, blockName); -+ } -+ -+ private static List> getLayersInfoFromString(final String layersString) { -+ final List> ret = new ArrayList<>(); -+ final String[] layers = layersString.split(","); -+ -+ for (final String layerString : layers) { -+ final Pair layer = getLayerInfoFromString(layerString); -+ if (layer == null) { -+ return Collections.emptyList(); -+ } -+ -+ ret.add(layer); -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7dbc6ac66a29d3b5e4df5d0105c8fc03a6aea0e0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1510.java -@@ -0,0 +1,103 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+ -+import java.util.Map; -+ -+public final class V1510 { -+ -+ public static final Map RENAMED_ENTITY_IDS = ImmutableMap.builder() -+ .put("minecraft:commandblock_minecart", "minecraft:command_block_minecart") -+ .put("minecraft:ender_crystal", "minecraft:end_crystal") -+ .put("minecraft:snowman", "minecraft:snow_golem") -+ .put("minecraft:evocation_illager", "minecraft:evoker") -+ .put("minecraft:evocation_fangs", "minecraft:evoker_fangs") -+ .put("minecraft:illusion_illager", "minecraft:illusioner") -+ .put("minecraft:vindication_illager", "minecraft:vindicator") -+ .put("minecraft:villager_golem", "minecraft:iron_golem") -+ .put("minecraft:xp_orb", "minecraft:experience_orb") -+ .put("minecraft:xp_bottle", "minecraft:experience_bottle") -+ .put("minecraft:eye_of_ender_signal", "minecraft:eye_of_ender") -+ .put("minecraft:fireworks_rocket", "minecraft:firework_rocket") -+ .build(); -+ -+ public static final Map RENAMED_BLOCKS = ImmutableMap.builder() -+ .put("minecraft:portal", "minecraft:nether_portal") -+ .put("minecraft:oak_bark", "minecraft:oak_wood") -+ .put("minecraft:spruce_bark", "minecraft:spruce_wood") -+ .put("minecraft:birch_bark", "minecraft:birch_wood") -+ .put("minecraft:jungle_bark", "minecraft:jungle_wood") -+ .put("minecraft:acacia_bark", "minecraft:acacia_wood") -+ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") -+ .put("minecraft:stripped_oak_bark", "minecraft:stripped_oak_wood") -+ .put("minecraft:stripped_spruce_bark", "minecraft:stripped_spruce_wood") -+ .put("minecraft:stripped_birch_bark", "minecraft:stripped_birch_wood") -+ .put("minecraft:stripped_jungle_bark", "minecraft:stripped_jungle_wood") -+ .put("minecraft:stripped_acacia_bark", "minecraft:stripped_acacia_wood") -+ .put("minecraft:stripped_dark_oak_bark", "minecraft:stripped_dark_oak_wood") -+ .put("minecraft:mob_spawner", "minecraft:spawner") -+ .build(); -+ -+ public static final Map RENAMED_ITEMS = ImmutableMap.builder() -+ .putAll(RENAMED_BLOCKS) -+ .put("minecraft:clownfish", "minecraft:tropical_fish") -+ .put("minecraft:chorus_fruit_popped", "minecraft:popped_chorus_fruit") -+ .put("minecraft:evocation_illager_spawn_egg", "minecraft:evoker_spawn_egg") -+ .put("minecraft:vindication_illager_spawn_egg", "minecraft:vindicator_spawn_egg") -+ .build(); -+ -+ private static final Map RECIPES_UPDATES = ImmutableMap.builder() -+ .put("minecraft:acacia_bark", "minecraft:acacia_wood") -+ .put("minecraft:birch_bark", "minecraft:birch_wood") -+ .put("minecraft:dark_oak_bark", "minecraft:dark_oak_wood") -+ .put("minecraft:jungle_bark", "minecraft:jungle_wood") -+ .put("minecraft:oak_bark", "minecraft:oak_wood") -+ .put("minecraft:spruce_bark", "minecraft:spruce_wood") -+ .build(); -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE4 + 6; -+ -+ private V1510() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCKS::get); -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEMS::get); -+ ConverterAbstractRecipeRename.register(VERSION, RECIPES_UPDATES::get); -+ -+ ConverterAbstractEntityRename.register(VERSION, (String input) -> { -+ if (input.startsWith("minecraft:bred_")) { -+ input = "minecraft:".concat(input.substring("minecraft:bred_".length())); -+ } -+ -+ return RENAMED_ENTITY_IDS.get(input); -+ }); -+ -+ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:swim_one_cm", "minecraft:walk_on_water_one_cm", -+ "minecraft:dive_one_cm", "minecraft:walk_under_water_one_cm" -+ )::get); -+ -+ -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:commandblock_minecart", "minecraft:command_block_minecart"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:ender_crystal", "minecraft:end_crystal"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:snowman", "minecraft:snow_golem"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_illager", "minecraft:evoker"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:evocation_fangs", "minecraft:evoker_fangs"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:illusion_illager", "minecraft:illusioner"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:vindication_illager", "minecraft:vindicator"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:villager_golem", "minecraft:iron_golem"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_orb", "minecraft:experience_orb"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:xp_bottle", "minecraft:experience_bottle"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:eye_of_ender_signal", "minecraft:eye_of_ender"); -+ MCTypeRegistry.ENTITY.copyWalkers(VERSION, "minecraft:fireworks_rocket", "minecraft:firework_rocket"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86be7a09d19ca94a30186e69655dc164344ae74d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1514.java -@@ -0,0 +1,69 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import net.minecraft.network.chat.Component; -+import net.minecraft.world.scores.criteria.ObjectiveCriteria; -+ -+public final class V1514 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE7 + 1; -+ -+ private V1514() {} -+ -+ public static void register() { -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String displayName = data.getString("DisplayName"); -+ if (displayName == null) { -+ return null; -+ } -+ -+ final String update = Component.Serializer.toJson(Component.literal(displayName)); -+ -+ data.setString("DisplayName", update); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TEAM.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String displayName = data.getString("DisplayName"); -+ if (displayName == null) { -+ return null; -+ } -+ -+ final String update = Component.Serializer.toJson(Component.literal(displayName)); -+ -+ data.setString("DisplayName", update); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.OBJECTIVE.addStructureConverter(new DataConverter<>(VERSION) { -+ private static ObjectiveCriteria.RenderType getRenderType(String string) { -+ return string.equals("health") ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String renderType = data.getString("RenderType"); -+ if (renderType != null) { -+ return null; -+ } -+ -+ final String criteriaName = data.getString("CriteriaName", ""); -+ -+ data.setString("RenderType", getRenderType(criteriaName).getId()); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f7a2ff2d5a154f667da35215f0e1d0756dbe2a0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1515.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1515 { -+ -+ protected static final int VERSION = MCVersions.V1_13_PRE7 + 2; -+ -+ public static final Map RENAMED_BLOCK_IDS = ImmutableMap.builder() -+ .put("minecraft:tube_coral_fan", "minecraft:tube_coral_wall_fan") -+ .put("minecraft:brain_coral_fan", "minecraft:brain_coral_wall_fan") -+ .put("minecraft:bubble_coral_fan", "minecraft:bubble_coral_wall_fan") -+ .put("minecraft:fire_coral_fan", "minecraft:fire_coral_wall_fan") -+ .put("minecraft:horn_coral_fan", "minecraft:horn_coral_wall_fan") -+ .build(); -+ -+ private V1515() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, RENAMED_BLOCK_IDS::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7b304031e1e8af120c6535e599c2ee4fdbce1682 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1624.java -@@ -0,0 +1,110 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import org.slf4j.Logger; -+ -+public final class V1624 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V18W32A + 1; -+ -+ private V1624() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ final IntOpenHashSet positionsToLook = new IntOpenHashSet(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final TrappedChestSection section = new TrappedChestSection(sections.getMap(i)); -+ if (section.isSkippable()) { -+ continue; -+ } -+ -+ for (int index = 0; index < 4096; ++index) { -+ if (section.isTrappedChest(section.getBlock(index))) { -+ positionsToLook.add(section.getSectionY() << 12 | index); -+ } -+ } -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType tileEntities = level.getList("TileEntities", ObjectType.MAP); -+ -+ if (tileEntities != null) { -+ for (int i = 0, len = tileEntities.size(); i < len; ++i) { -+ final MapType tile = tileEntities.getMap(i); -+ -+ final int x = tile.getInt("x"); -+ final int y = tile.getInt("y"); -+ final int z = tile.getInt("z"); -+ -+ final int index = V1496.getIndex(x - (chunkX << 4), y, z - (chunkZ << 4)); -+ if (!positionsToLook.contains(index)) { -+ continue; -+ } -+ -+ final String id = tile.getString("id"); -+ if (!"minecraft:chest".equals(id)) { -+ LOGGER.warn("Block Entity ({},{},{}) was expected to be a chest (V1624)", x, y, z); -+ } -+ -+ tile.setString("id", "minecraft:trapped_chest"); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static final class TrappedChestSection extends V1496.Section { -+ -+ private IntOpenHashSet chestIds; -+ -+ public TrappedChestSection(final MapType section) { -+ super(section); -+ } -+ -+ @Override -+ protected boolean initSkippable() { -+ this.chestIds = new IntOpenHashSet(); -+ -+ for (int i = 0; i < this.palette.size(); ++i) { -+ final MapType blockState = this.palette.getMap(i); -+ final String name = blockState.getString("Name"); -+ if ("minecraft:trapped_chest".equals(name)) { -+ this.chestIds.add(i); -+ } -+ } -+ -+ return this.chestIds.isEmpty(); -+ } -+ -+ public boolean isTrappedChest(final int id) { -+ return this.chestIds.contains(id); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java -new file mode 100644 -index 0000000000000000000000000000000000000000..18c84de9e6015a40c189134cc5aaee84267f2669 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V165.java -@@ -0,0 +1,79 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.gson.JsonParseException; -+import net.minecraft.network.chat.CommonComponents; -+import net.minecraft.network.chat.Component; -+import net.minecraft.util.GsonHelper; -+import net.minecraft.util.datafix.fixes.BlockEntitySignTextStrictJsonFix; -+import org.apache.commons.lang3.StringUtils; -+ -+public final class V165 { -+ -+ protected static final int VERSION = MCVersions.V1_9_PRE2; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final ListType pages = tag.getList("pages", ObjectType.STRING); -+ if (pages == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = pages.size(); i < len; ++i) { -+ final String page = pages.getString(i); -+ Component component = null; -+ -+ if (!"null".equals(page) && !StringUtils.isEmpty(page)) { -+ if (page.charAt(0) == '"' && page.charAt(page.length() - 1) == '"' || page.charAt(0) == '{' && page.charAt(page.length() - 1) == '}') { -+ try { -+ component = GsonHelper.fromJson(BlockEntitySignTextStrictJsonFix.GSON, page, Component.class, true); -+ if (component == null) { -+ component = CommonComponents.EMPTY; -+ } -+ } catch (final JsonParseException ignored) {} -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJson(page); -+ } catch (final JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ try { -+ component = Component.Serializer.fromJsonLenient(page); -+ } catch (JsonParseException ignored) {} -+ } -+ -+ if (component == null) { -+ component = Component.literal(page); -+ } -+ } else { -+ component = Component.literal(page); -+ } -+ } else { -+ component = CommonComponents.EMPTY; -+ } -+ -+ pages.setString(i, Component.Serializer.toJson(component)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V165() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1362a7917243715305650c197a8e7e5689bcfe4a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1800.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1800 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 169; -+ -+ public static final Map RENAMED_ITEM_IDS = ImmutableMap.builder() -+ .put("minecraft:cactus_green", "minecraft:green_dye") -+ .put("minecraft:rose_red", "minecraft:red_dye") -+ .put("minecraft:dandelion_yellow", "minecraft:yellow_dye") -+ .build(); -+ -+ private V1800() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMED_ITEM_IDS::get); -+ -+ registerMob("minecraft:panda"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:pillager", new DataWalkerItemLists("Inventory", "ArmorItems", "HandItems")); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a4e2fa4ed6ede8d1783b5591ef1b5ebf3cd28bf0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1801.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V1801 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 170; -+ -+ private V1801() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:illager_beast"); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cd5110ef3c18662871020456b60edfb3aeb67166 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1802.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1802 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 171; -+ -+ private V1802() {} -+ -+ public static void register() { -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:stone_slab", "minecraft:smooth_stone_slab", -+ "minecraft:sign", "minecraft:oak_sign", "minecraft:wall_sign", "minecraft:oak_wall_sign" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:stone_slab", "minecraft:smooth_stone_slab", -+ "minecraft:sign", "minecraft:oak_sign" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5f22419287400399dfb31653a9208a54e0811f94 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1803.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import net.minecraft.network.chat.Component; -+ -+public final class V1803 { -+ -+ protected static final int VERSION = MCVersions.V1_13_2 + 172; -+ -+ private V1803() {} -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ -+ if (display == null) { -+ return null; -+ } -+ -+ final ListType lore = display.getList("Lore", ObjectType.STRING); -+ if (lore == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = lore.size(); i < len; ++i) { -+ lore.setString(i, Component.Serializer.toJson(Component.literal(lore.getString(i)))); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java -new file mode 100644 -index 0000000000000000000000000000000000000000..09955e0c2245d8d42ce6ae664ae81e97db8a85f2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1904.java -@@ -0,0 +1,44 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1904 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 1; -+ -+ private V1904() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ocelot", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int catType = data.getInt("CatType"); -+ -+ if (catType == 0) { -+ final String owner = data.getString("Owner"); -+ final String ownerUUID = data.getString("OwnerUUID"); -+ if ((owner != null && owner.length() > 0) || (ownerUUID != null && ownerUUID.length() > 0)) { -+ data.setBoolean("Trusting", true); -+ } -+ } else if (catType > 0 && catType < 4) { -+ data.setString("id", "minecraft:cat"); -+ data.setString("OwnerUUID", data.getString("OwnerUUID", "")); -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("minecraft:cat"); -+ } -+ -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2eeec0d9cbd35ff20ba239ea7fd9c2f52f7e4f9e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1905.java -@@ -0,0 +1,35 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1905 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 2; -+ -+ private V1905() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final String status = level.getString("Status"); -+ -+ if ("postprocessed".equals(status)) { -+ level.setString("Status", "fullchunk"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6358c2e0861a3743a3ea6d46a644870892256a79 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1906.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V1906 { -+ -+ protected static final int VERSION = MCVersions.V18W43C + 3; -+ -+ private V1906() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:barrel", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:smoker", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:blast_furnace", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:lectern", new DataWalkerItems("Book")); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b9cc2e4a2ae42e12ccf4e0b634fd74d3aad317ab ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1911.java -@@ -0,0 +1,48 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V1911 { -+ -+ protected static final int VERSION = MCVersions.V18W46A + 1; -+ -+ private static final Map CHUNK_STATUS_REMAP = ImmutableMap.builder() -+ .put("structure_references", "empty") -+ .put("biomes", "empty") -+ .put("base", "surface") -+ .put("carved", "carvers") -+ .put("liquid_carved", "liquid_carvers") -+ .put("decorated", "features") -+ .put("lighted", "light") -+ .put("mobs_spawned", "spawn") -+ .put("finalized", "heightmaps") -+ .put("fullchunk", "full") -+ .build(); -+ -+ -+ private V1911() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final String status = level.getString("Status", "empty"); -+ level.setString("Status", CHUNK_STATUS_REMAP.getOrDefault(status, "empty")); -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f5a48c4824080827d2dad057ae70dfd7a11818f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1914.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1914 { -+ -+ protected static final int VERSION = MCVersions.V18W48A; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:chest", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String lootTable = data.getString("LootTable"); -+ -+ if ("minecraft:chests/village_blacksmith".equals(lootTable)) { -+ data.setString("LootTable", "minecraft:chests/village/village_weaponsmith"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java -new file mode 100644 -index 0000000000000000000000000000000000000000..71538d858a681c91f7193003e0808cdb4fd1f847 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1917.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1917 { -+ -+ protected static final int VERSION = MCVersions.V18W49A + 1; -+ -+ private V1917() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getInt("CatType") == 9) { -+ data.setInt("CatType", 10); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java -new file mode 100644 -index 0000000000000000000000000000000000000000..28fc06da723792e9abc4999376c0941f9a835aff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1918.java -@@ -0,0 +1,65 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1918 { -+ -+ protected static final int VERSION = MCVersions.V18W49A + 2; -+ -+ private V1918() {} -+ -+ private static String getProfessionString(final int professionId, final int careerId) { -+ if (professionId == 0) { -+ if (careerId == 2) { -+ return "minecraft:fisherman"; -+ } else if (careerId == 3) { -+ return "minecraft:shepherd"; -+ } else { -+ return careerId == 4 ? "minecraft:fletcher" : "minecraft:farmer"; -+ } -+ } else if (professionId == 1) { -+ return careerId == 2 ? "minecraft:cartographer" : "minecraft:librarian"; -+ } else if (professionId == 2) { -+ return "minecraft:cleric"; -+ } else if (professionId == 3) { -+ if (careerId == 2) { -+ return "minecraft:weaponsmith"; -+ } else { -+ return careerId == 3 ? "minecraft:toolsmith" : "minecraft:armorer"; -+ } -+ } else if (professionId == 4) { -+ return careerId == 2 ? "minecraft:leatherworker" : "minecraft:butcher"; -+ } else { -+ return professionId == 5 ? "minecraft:nitwit" : "minecraft:none"; -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int profession = data.getInt("Profession"); -+ final int career = data.getInt("Career"); -+ final int careerLevel = data.getInt("CareerLevel", 1); -+ data.remove("Profession"); -+ data.remove("Career"); -+ data.remove("CareerLevel"); -+ -+ final MapType villagerData = Types.NBT.createEmptyMap(); -+ data.setMap("VillagerData", villagerData); -+ villagerData.setString("type", "minecraft:plains"); -+ villagerData.setString("profession", getProfessionString(profession, career)); -+ villagerData.setInt("level", careerLevel); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", converter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", converter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java -new file mode 100644 -index 0000000000000000000000000000000000000000..224d35620e9d9e65f0642fdb13f80fcb2667a2ee ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1920.java -@@ -0,0 +1,75 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+ -+public final class V1920 { -+ -+ protected static final int VERSION = MCVersions.V18W50A + 1; -+ -+ private V1920() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType structures = level.getMap("Structures"); -+ if (structures == null) { -+ return null; -+ } -+ -+ final MapType starts = structures.getMap("Starts"); -+ if (starts != null) { -+ final MapType village = starts.getMap("New_Village"); -+ if (village != null) { -+ starts.remove("New_Village"); -+ starts.setMap("Village", village); -+ } else { -+ starts.remove("Village"); -+ } -+ } -+ -+ final MapType references = structures.getMap("References"); -+ if (references != null) { -+ final MapType newVillage = references.getMap("New_Village"); -+ // I believe Mojang had a typo here, removing Village from references only made sense -+ // if the new village didn't exist. DFU removes it whether or not it exists, but still relocates -+ // New_Village to Village first. It doesn't make sense to me to relocate it just to remove it, so it -+ // must be a typo. -+ if (newVillage == null) { -+ references.remove("Village"); -+ } else { -+ references.remove("New_Village"); -+ references.setMap("Village", newVillage); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ -+ if ("minecraft:new_village".equals(NamespaceUtil.correctNamespace(id))) { -+ data.setString("id", "minecraft:village"); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:campfire", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java -new file mode 100644 -index 0000000000000000000000000000000000000000..19dc3d9b18d95d5f0e898d4c52c77a527066adf1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1925.java -@@ -0,0 +1,29 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1925 { -+ -+ protected static final int VERSION = MCVersions.V19W03C + 1; -+ -+ public static void register() { -+ MCTypeRegistry.SAVED_DATA.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setMap("data", root); -+ -+ return ret; -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java -new file mode 100644 -index 0000000000000000000000000000000000000000..86caefba615a917d3530112edcc083caaaea4bb9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1928.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V1928 { -+ -+ protected static final int VERSION = MCVersions.V19W04B + 1; -+ -+ private V1928() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:illager_beast", "minecraft:ravager" -+ )::get); -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:illager_beast_spawn_egg", "minecraft:ravager_spawn_egg" -+ )::get); -+ -+ registerMob("minecraft:ravager"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d58c32aa89e416e40cfef7c5840b772dd4991173 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1929.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V1929 { -+ -+ protected static final int VERSION = MCVersions.V19W04B + 2; -+ -+ private V1929() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:wandering_trader", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:trader_llama", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "SaddleItem", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, data, "DecorItem", fromVersion, toVersion); -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java -new file mode 100644 -index 0000000000000000000000000000000000000000..da845df91d42f1059490d749b235758850ce7254 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1931.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V1931 { -+ -+ protected static final int VERSION = MCVersions.V19W06A; -+ -+ private V1931() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:fox"); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4e7b22874f17f531b583146db3aa4e57bdd5f27c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1936.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1936 { -+ -+ protected static final int VERSION = MCVersions.V19W09A + 1; -+ -+ private V1936() {} -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String chatOpacity = data.getString("chatOpacity"); -+ if (chatOpacity != null) { -+ // Vanilla uses createDouble here, but options is always string -> string. I presume they made -+ // a mistake with this converter. -+ data.setString("textBackgroundOpacity", Double.toString(calculateBackground(chatOpacity))); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private static double calculateBackground(final String opacity) { -+ try { -+ final double d = 0.9D * Double.parseDouble(opacity) + 0.1D; -+ return d / 2.0D; -+ } catch (final NumberFormatException ex) { -+ return 0.5D; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java -new file mode 100644 -index 0000000000000000000000000000000000000000..00e4bd45b04feee990d9d3414c34c0966e65e4ea ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1946.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V1946 { -+ -+ protected static final int VERSION = MCVersions.V19W14B + 1; -+ -+ private V1946() {} -+ -+ public static void register() { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = Types.NBT.createEmptyMap(); -+ data.setMap("Sections", sections); -+ -+ for (int y = 0; y < 16; ++y) { -+ final String key = Integer.toString(y); -+ final Object records = data.getGeneric(key); -+ -+ if (records == null) { -+ continue; -+ } -+ -+ data.remove(key); -+ -+ final MapType section = Types.NBT.createEmptyMap(); -+ section.setGeneric("Records", records); -+ sections.setMap(key, section); // integer keys convert to string in DFU (at least for NBT ops) -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6b4af1fe7c53e6122d7db952770d14a753f8cab3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1948.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1948 { -+ -+ protected static final int VERSION = MCVersions.V1_14_PRE2; -+ -+ private V1948() {} -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:white_banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType display = tag.getMap("display"); -+ if (display == null) { -+ return null; -+ } -+ -+ final String name = display.getString("Name"); -+ if (name == null) { -+ return null; -+ } -+ -+ display.setString("Name", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a73471e8f3df3b22349b2f842c3e98c2ff8bb5e1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1953.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1953 { -+ -+ protected static final int VERSION = MCVersions.V1_14 + 1; -+ -+ private V1953() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String name = data.getString("CustomName"); -+ if (name != null) { -+ data.setString("CustomName", name.replace("\"translate\":\"block.minecraft.illager_banner\"", "\"translate\":\"block.minecraft.ominous_banner\"")); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java -new file mode 100644 -index 0000000000000000000000000000000000000000..33bfe82709b507c4fd57199f5d8a44d131718d7f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1955.java -@@ -0,0 +1,94 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.util.Mth; -+ -+public final class V1955 { -+ -+ protected static final int VERSION = MCVersions.V1_14_1_PRE1; -+ -+ private static final int[] LEVEL_XP_THRESHOLDS = new int[] { -+ 0, -+ 10, -+ 50, -+ 100, -+ 150 -+ }; -+ -+ private V1955() {} -+ -+ static int getMinXpPerLevel(final int level) { -+ return LEVEL_XP_THRESHOLDS[Mth.clamp(level - 1, 0, LEVEL_XP_THRESHOLDS.length - 1)]; -+ } -+ -+ static void addLevel(final MapType data, final int level) { -+ MapType villagerData = data.getMap("VillagerData"); -+ if (villagerData == null) { -+ villagerData = Types.NBT.createEmptyMap(); -+ data.setMap("VillagerData", villagerData); -+ } -+ villagerData.setInt("level", level); -+ } -+ -+ static void addXpFromLevel(final MapType data, final int level) { -+ data.setInt("Xp", getMinXpPerLevel(level)); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType villagerData = data.getMap("VillagerData"); -+ int level = villagerData == null ? 0 : villagerData.getInt("level"); -+ if (level == 0 || level == 1) { -+ // count recipes -+ final MapType offers = data.getMap("Offers"); -+ final ListType recipes = offers == null ? null : offers.getList("Recipes", ObjectType.MAP); -+ final int recipeCount; -+ if (recipes != null) { -+ recipeCount = recipes.size(); -+ } else { -+ recipeCount = 0; -+ } -+ -+ level = Mth.clamp(recipeCount / 2, 1, 5); -+ if (level > 1) { -+ addLevel(data, level); -+ } -+ } -+ -+ if (!data.hasKey("Xp", ObjectType.NUMBER)) { -+ addXpFromLevel(data, level); -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final Number xp = data.getNumber("Xp"); -+ if (xp == null) { -+ final int level; -+ final MapType villagerData = data.getMap("VillagerData"); -+ if (villagerData == null) { -+ level = 1; -+ } else { -+ level = villagerData.getInt("level", 1); -+ } -+ -+ data.setInt("Xp", getMinXpPerLevel(level)); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8bc6a8734034942a81b282b8766b21fddbe2b304 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1961.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V1961 { -+ -+ protected static final int VERSION = MCVersions.V1_14_2_PRE3 + 1; -+ -+ private V1961() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ level.remove("isLightOn"); -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e4e7299cec1d3809d1b55ae460e64ec6d2bb477 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V1963.java -@@ -0,0 +1,40 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V1963 { -+ -+ protected static final int VERSION = MCVersions.V1_14_2; -+ -+ private V1963() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType gossips = data.getList("Gossips", ObjectType.MAP); -+ if (gossips == null) { -+ return null; -+ } -+ -+ for (int i = 0; i < gossips.size();) { -+ final MapType gossip = gossips.getMap(i); -+ if ("golem".equals(gossip.getString("Type"))) { -+ gossips.remove(i); -+ continue; -+ } -+ -+ ++i; -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d905043c4f3071e8dc340ac2afe2b81aac345e1e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2100.java -@@ -0,0 +1,46 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.recipe.ConverterAbstractRecipeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2100 { -+ -+ protected static final int VERSION = MCVersions.V1_14_4 + 124; -+ protected static final Map RECIPE_RENAMES = ImmutableMap.of( -+ "minecraft:sugar", "sugar_from_sugar_cane" -+ ); -+ -+ private V2100() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractRecipeRename.register(VERSION, RECIPE_RENAMES::get); -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:recipes/misc/sugar", "minecraft:recipes/misc/sugar_from_sugar_cane" -+ )::get); -+ -+ registerMob("minecraft:bee"); -+ registerMob("minecraft:bee_stinger"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:beehive", (data, fromVersion, toVersion) -> { -+ final ListType bees = data.getList("Bees", ObjectType.MAP); -+ if (bees != null) { -+ for (int i = 0, len = bees.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, bees.getMap(i), "EntityData", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0bb378ac8e8d0a087359361281644a7f39cecfbe ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2202.java -@@ -0,0 +1,50 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2202 { -+ -+ protected static final int VERSION = MCVersions.V19W35A + 1; -+ -+ private V2202() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ final int[] oldBiomes = level.getInts("Biomes"); -+ -+ if (oldBiomes == null || oldBiomes.length != 256) { -+ return null; -+ } -+ -+ final int[] newBiomes = new int[1024]; -+ level.setInts("Biomes", newBiomes); -+ -+ int n; -+ for(n = 0; n < 4; ++n) { -+ for(int j = 0; j < 4; ++j) { -+ int k = (j << 2) + 2; -+ int l = (n << 2) + 2; -+ int m = l << 4 | k; -+ newBiomes[n << 2 | j] = oldBiomes[m]; -+ } -+ } -+ -+ for(n = 1; n < 64; ++n) { -+ System.arraycopy(newBiomes, 0, newBiomes, n * 16, 16); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6054cd31cb2e140f05b92736405a7d073be5cf5c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2209.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterAbstractPOIRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2209 { -+ -+ protected static final int VERSION = MCVersions.V19W40A + 1; -+ -+ private V2209() {} -+ -+ public static void register() { -+ final Map renamedIds = ImmutableMap.of( -+ "minecraft:bee_hive", "minecraft:beehive" -+ ); -+ -+ ConverterAbstractBlockRename.register(VERSION, renamedIds::get); -+ ConverterAbstractItemRename.register(VERSION, renamedIds::get); -+ ConverterAbstractPOIRename.register(VERSION, renamedIds::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6cff6d723616e0a38811872f7b5d28799240ddfe ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2211.java -@@ -0,0 +1,32 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2211 { -+ -+ protected static final int VERSION = MCVersions.V19W41A + 1; -+ -+ private V2211() {} -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("references", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ final int references = data.getInt("references"); -+ if (references <= 0) { -+ data.setInt("references", 1); -+ } -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e06a98a01086c9d6eb9fc80a151f0403247b0033 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2218.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2218 { -+ -+ protected static final int VERSION = MCVersions.V1_15_PRE1; -+ -+ private V2218() {} -+ -+ public static void register() { -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType sections = data.getMap("Sections"); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (final String key : sections.keys()) { -+ final MapType section = sections.getMap(key); -+ -+ section.remove("Valid"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c8901f95e1076ae8be220c03efd83ce9bd18d2a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2501.java -@@ -0,0 +1,65 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2501 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 271; -+ -+ private V2501() {} -+ -+ private static void registerFurnace(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, (data, fromVersion, toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Items", fromVersion, toVersion); -+ -+ WalkerUtils.convertKeys(MCTypeRegistry.RECIPE, data, "RecipesUsed", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> converter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int recipesUsedSize = data.getInt("RecipesUsedSize"); -+ data.remove("RecipesUsedSize"); -+ -+ if (recipesUsedSize <= 0) { -+ return null; -+ } -+ -+ final MapType newRecipes = Types.NBT.createEmptyMap(); -+ data.setMap("RecipesUsed", newRecipes); -+ -+ for (int i = 0; i < recipesUsedSize; ++i) { -+ final String recipeKey = data.getString("RecipeLocation" + i); -+ data.remove("RecipeLocation" + i); -+ final int recipeAmount = data.getInt("RecipeAmount" + i); -+ data.remove("RecipeAmount" + i); -+ -+ if (i <= 0 || recipeKey == null) { -+ continue; -+ } -+ -+ newRecipes.setInt(recipeKey, recipeAmount); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:furnace", converter); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:blast_furnace", converter); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:smoker", converter); -+ -+ registerFurnace("minecraft:furnace"); -+ registerFurnace("minecraft:smoker"); -+ registerFurnace("minecraft:blast_furnace"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7075f7beb8aacfdadd68f4353d2cf32edaa1905d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2502.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2502 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 272; -+ -+ private V2502() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:hoglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cd1bca807236b917244bbacd8df6f25fd3c42407 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2503.java -@@ -0,0 +1,67 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import com.google.common.collect.ImmutableSet; -+import java.util.Set; -+ -+public final class V2503 { -+ -+ protected static final int VERSION = MCVersions.V1_15_2 + 273; -+ -+ private static final Set WALL_BLOCKS = ImmutableSet.of( -+ "minecraft:andesite_wall", -+ "minecraft:brick_wall", -+ "minecraft:cobblestone_wall", -+ "minecraft:diorite_wall", -+ "minecraft:end_stone_brick_wall", -+ "minecraft:granite_wall", -+ "minecraft:mossy_cobblestone_wall", -+ "minecraft:mossy_stone_brick_wall", -+ "minecraft:nether_brick_wall", -+ "minecraft:prismarine_wall", -+ "minecraft:red_nether_brick_wall", -+ "minecraft:red_sandstone_wall", -+ "minecraft:sandstone_wall", -+ "minecraft:stone_brick_wall" -+ ); -+ -+ private V2503() {} -+ -+ private static void changeWallProperty(final MapType properties, final String path) { -+ final String property = properties.getString(path); -+ if (property != null) { -+ properties.setString(path, "true".equals(property) ? "low" : "none"); -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!WALL_BLOCKS.contains(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ if (properties == null) { -+ return null; -+ } -+ -+ changeWallProperty(properties, "east"); -+ changeWallProperty(properties, "west"); -+ changeWallProperty(properties, "north"); -+ changeWallProperty(properties, "south"); -+ -+ return null; -+ } -+ }); -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:recipes/misc/composter", "minecraft:recipes/decorations/composter" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java -new file mode 100644 -index 0000000000000000000000000000000000000000..350f5cca06d2a864b5a3cc028753fd6489a28ad5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2505.java -@@ -0,0 +1,49 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2505 { -+ -+ protected static final int VERSION = MCVersions.V20W06A + 1; -+ -+ private V2505() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType brain = data.getMap("Brain"); -+ if (brain == null) { -+ return null; -+ } -+ -+ final MapType memories = brain.getMap("memories"); -+ if (memories == null) { -+ return null; -+ } -+ -+ for (final String key : memories.keys()) { -+ final Object value = memories.getGeneric(key); -+ -+ final MapType wrapped = Types.NBT.createEmptyMap(); -+ wrapped.setGeneric("value", value); -+ -+ memories.setMap(key, wrapped); -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("minecraft:piglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8cfd380e870ceea4892b8cd7bf855a6e5dc8cc6f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2508.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2508 { -+ -+ protected static final int VERSION = MCVersions.V20W08A + 1; -+ -+ private V2508() {} -+ -+ public static void register() { -+ final Map remap = ImmutableMap.of( -+ "minecraft:warped_fungi", "minecraft:warped_fungus", -+ "minecraft:crimson_fungi", "minecraft:crimson_fungus" -+ ); -+ -+ ConverterAbstractBlockRename.register(VERSION, remap::get); -+ ConverterAbstractItemRename.register(VERSION, remap::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1fa93dec8a81719a19c0f7db589778935ce44d56 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2509.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2509 { -+ -+ protected static final int VERSION = MCVersions.V20W08A + 2; -+ -+ private V2509() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:zombie_pigman_spawn_egg", "minecraft:zombified_piglin_spawn_egg" -+ )::get); -+ ConverterAbstractEntityRename.register(VERSION, ImmutableMap.of( -+ "minecraft:zombie_pigman", "minecraft:zombified_piglin" -+ )::get); -+ -+ registerMob("minecraft:zombified_piglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java -new file mode 100644 -index 0000000000000000000000000000000000000000..183ab7ed77e30bf87e71e5f682a59fc3a64a7672 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2511.java -@@ -0,0 +1,97 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2511 { -+ -+ protected static final int VERSION = MCVersions.V20W09A + 1; -+ -+ private V2511() {} -+ -+ private static int[] createUUIDArray(final long most, final long least) { -+ return new int[] { -+ (int)(most >>> 32), -+ (int)most, -+ (int)(least >>> 32), -+ (int)least -+ }; -+ } -+ -+ private static void setUUID(final MapType data, final long most, final long least) { -+ if (most != 0L && least != 0L) { -+ data.setInts("OwnerUUID", createUUIDArray(most, least)); -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> throwableConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("owner"); -+ data.remove("owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ setUUID(data, owner.getLong("M"), owner.getLong("L")); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> potionConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType potion = data.getMap("Potion"); -+ data.remove("Potion"); -+ -+ data.setMap("Item", potion == null ? Types.NBT.createEmptyMap() : potion); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> llamaSpitConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("Owner"); -+ data.remove("Owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ setUUID(data, owner.getLong("OwnerUUIDMost"), owner.getLong("OwnerUUIDLeast")); -+ -+ return null; -+ } -+ }; -+ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ setUUID(data, data.getLong("OwnerUUIDMost"), data.getLong("OwnerUUIDLeast")); -+ -+ data.remove("OwnerUUIDMost"); -+ data.remove("OwnerUUIDLeast"); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:egg", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:ender_pearl", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:experience_bottle", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:snowball", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", throwableConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:potion", potionConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:llama_spit", llamaSpitConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); -+ -+ // Vanilla migrates the potion item but does not change the schema. -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Item")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d494fac0900f61e60c01617e631a0431bbda9438 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2514.java -@@ -0,0 +1,590 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.Sets; -+import java.util.Set; -+import java.util.UUID; -+ -+public final class V2514 { -+ -+ protected static final int VERSION = MCVersions.V20W11A + 1; -+ -+ private static final Set ABSTRACT_HORSES = Sets.newHashSet(); -+ private static final Set TAMEABLE_ANIMALS = Sets.newHashSet(); -+ private static final Set ANIMALS = Sets.newHashSet(); -+ private static final Set MOBS = Sets.newHashSet(); -+ private static final Set LIVING_ENTITIES = Sets.newHashSet(); -+ private static final Set PROJECTILES = Sets.newHashSet(); -+ static { -+ ABSTRACT_HORSES.add("minecraft:donkey"); -+ ABSTRACT_HORSES.add("minecraft:horse"); -+ ABSTRACT_HORSES.add("minecraft:llama"); -+ ABSTRACT_HORSES.add("minecraft:mule"); -+ ABSTRACT_HORSES.add("minecraft:skeleton_horse"); -+ ABSTRACT_HORSES.add("minecraft:trader_llama"); -+ ABSTRACT_HORSES.add("minecraft:zombie_horse"); -+ -+ TAMEABLE_ANIMALS.add("minecraft:cat"); -+ TAMEABLE_ANIMALS.add("minecraft:parrot"); -+ TAMEABLE_ANIMALS.add("minecraft:wolf"); -+ -+ ANIMALS.add("minecraft:bee"); -+ ANIMALS.add("minecraft:chicken"); -+ ANIMALS.add("minecraft:cow"); -+ ANIMALS.add("minecraft:fox"); -+ ANIMALS.add("minecraft:mooshroom"); -+ ANIMALS.add("minecraft:ocelot"); -+ ANIMALS.add("minecraft:panda"); -+ ANIMALS.add("minecraft:pig"); -+ ANIMALS.add("minecraft:polar_bear"); -+ ANIMALS.add("minecraft:rabbit"); -+ ANIMALS.add("minecraft:sheep"); -+ ANIMALS.add("minecraft:turtle"); -+ ANIMALS.add("minecraft:hoglin"); -+ -+ MOBS.add("minecraft:bat"); -+ MOBS.add("minecraft:blaze"); -+ MOBS.add("minecraft:cave_spider"); -+ MOBS.add("minecraft:cod"); -+ MOBS.add("minecraft:creeper"); -+ MOBS.add("minecraft:dolphin"); -+ MOBS.add("minecraft:drowned"); -+ MOBS.add("minecraft:elder_guardian"); -+ MOBS.add("minecraft:ender_dragon"); -+ MOBS.add("minecraft:enderman"); -+ MOBS.add("minecraft:endermite"); -+ MOBS.add("minecraft:evoker"); -+ MOBS.add("minecraft:ghast"); -+ MOBS.add("minecraft:giant"); -+ MOBS.add("minecraft:guardian"); -+ MOBS.add("minecraft:husk"); -+ MOBS.add("minecraft:illusioner"); -+ MOBS.add("minecraft:magma_cube"); -+ MOBS.add("minecraft:pufferfish"); -+ MOBS.add("minecraft:zombified_piglin"); -+ MOBS.add("minecraft:salmon"); -+ MOBS.add("minecraft:shulker"); -+ MOBS.add("minecraft:silverfish"); -+ MOBS.add("minecraft:skeleton"); -+ MOBS.add("minecraft:slime"); -+ MOBS.add("minecraft:snow_golem"); -+ MOBS.add("minecraft:spider"); -+ MOBS.add("minecraft:squid"); -+ MOBS.add("minecraft:stray"); -+ MOBS.add("minecraft:tropical_fish"); -+ MOBS.add("minecraft:vex"); -+ MOBS.add("minecraft:villager"); -+ MOBS.add("minecraft:iron_golem"); -+ MOBS.add("minecraft:vindicator"); -+ MOBS.add("minecraft:pillager"); -+ MOBS.add("minecraft:wandering_trader"); -+ MOBS.add("minecraft:witch"); -+ MOBS.add("minecraft:wither"); -+ MOBS.add("minecraft:wither_skeleton"); -+ MOBS.add("minecraft:zombie"); -+ MOBS.add("minecraft:zombie_villager"); -+ MOBS.add("minecraft:phantom"); -+ MOBS.add("minecraft:ravager"); -+ MOBS.add("minecraft:piglin"); -+ -+ LIVING_ENTITIES.add("minecraft:armor_stand"); -+ -+ PROJECTILES.add("minecraft:arrow"); -+ PROJECTILES.add("minecraft:dragon_fireball"); -+ PROJECTILES.add("minecraft:firework_rocket"); -+ PROJECTILES.add("minecraft:fireball"); -+ PROJECTILES.add("minecraft:llama_spit"); -+ PROJECTILES.add("minecraft:small_fireball"); -+ PROJECTILES.add("minecraft:snowball"); -+ PROJECTILES.add("minecraft:spectral_arrow"); -+ PROJECTILES.add("minecraft:egg"); -+ PROJECTILES.add("minecraft:ender_pearl"); -+ PROJECTILES.add("minecraft:experience_bottle"); -+ PROJECTILES.add("minecraft:potion"); -+ PROJECTILES.add("minecraft:trident"); -+ PROJECTILES.add("minecraft:wither_skull"); -+ } -+ -+ static int[] createUUIDArray(final long most, final long least) { -+ return new int[] { -+ (int)(most >>> 32), -+ (int)most, -+ (int)(least >>> 32), -+ (int)least -+ }; -+ } -+ -+ static int[] createUUIDFromString(final MapType data, final String path) { -+ if (data == null) { -+ return null; -+ } -+ -+ final String uuidString = data.getString(path); -+ if (uuidString == null) { -+ return null; -+ } -+ -+ try { -+ final UUID uuid = UUID.fromString(uuidString); -+ return createUUIDArray(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); -+ } catch (final IllegalArgumentException ignore) { -+ return null; -+ } -+ } -+ -+ static int[] createUUIDFromLongs(final MapType data, final String most, final String least) { -+ if (data == null) { -+ return null; -+ } -+ -+ final long mostBits = data.getLong(most); -+ final long leastBits = data.getLong(least); -+ -+ return (mostBits != 0 || leastBits != 0) ? createUUIDArray(mostBits, leastBits) : null; -+ } -+ -+ static void replaceUUIDString(final MapType data, final String oldPath, final String newPath) { -+ final int[] newUUID = createUUIDFromString(data, oldPath); -+ if (newUUID != null) { -+ data.remove(oldPath); -+ data.setInts(newPath, newUUID); -+ } -+ } -+ -+ static void replaceUUIDMLTag(final MapType data, final String oldPath, final String newPath) { -+ final int[] uuid = createUUIDFromLongs(data.getMap(oldPath), "M", "L"); -+ if (uuid != null) { -+ data.remove(oldPath); -+ data.setInts(newPath, uuid); -+ } -+ } -+ -+ static void replaceUUIDLeastMost(final MapType data, final String prefix, final String newPath) { -+ final String mostPath = prefix.concat("Most"); -+ final String leastPath = prefix.concat("Least"); -+ -+ final int[] uuid = createUUIDFromLongs(data, mostPath, leastPath); -+ if (uuid != null) { -+ data.remove(mostPath); -+ data.remove(leastPath); -+ data.setInts(newPath, uuid); -+ } -+ } -+ -+ private V2514() {} -+ -+ private static void updatePiglin(final MapType data) { -+ final MapType brain = data.getMap("Brain"); -+ if (brain == null) { -+ return; -+ } -+ -+ final MapType memories = brain.getMap("memories"); -+ if (memories == null) { -+ return; -+ } -+ -+ final MapType angryAt = memories.getMap("minecraft:angry_at"); -+ -+ replaceUUIDString(angryAt, "value", "value"); -+ } -+ -+ private static void updateEvokerFangs(final MapType data) { -+ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateZombieVillager(final MapType data) { -+ replaceUUIDLeastMost(data, "ConversionPlayer", "ConversionPlayer"); -+ } -+ -+ private static void updateAreaEffectCloud(final MapType data) { -+ replaceUUIDLeastMost(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateShulkerBullet(final MapType data) { -+ replaceUUIDMLTag(data, "Owner", "Owner"); -+ replaceUUIDMLTag(data, "Target", "Target"); -+ } -+ -+ private static void updateItem(final MapType data) { -+ replaceUUIDMLTag(data, "Owner", "Owner"); -+ replaceUUIDMLTag(data, "Thrower", "Thrower"); -+ } -+ -+ private static void updateFox(final MapType data) { -+ final ListType trustedUUIDS = data.getList("TrustedUUIDs", ObjectType.MAP); -+ if (trustedUUIDS == null) { -+ return; -+ } -+ -+ final ListType newUUIDs = Types.NBT.createEmptyList(); -+ data.remove("TrustedUUIDs"); -+ data.setList("Trusted", newUUIDs); -+ -+ for (int i = 0, len = trustedUUIDS.size(); i < len; ++i) { -+ final MapType uuid = trustedUUIDS.getMap(i); -+ final int[] newUUID = createUUIDFromLongs(uuid, "M", "L"); -+ if (newUUID != null) { -+ newUUIDs.addIntArray(newUUID); -+ } -+ } -+ } -+ -+ private static void updateHurtBy(final MapType data) { -+ replaceUUIDString(data, "HurtBy", "HurtBy"); -+ } -+ -+ private static void updateAnimalOwner(final MapType data) { -+ updateAnimal(data); -+ -+ replaceUUIDString(data, "OwnerUUID", "Owner"); -+ } -+ -+ private static void updateAnimal(final MapType data) { -+ updateMob(data); -+ -+ replaceUUIDLeastMost(data, "LoveCause", "LoveCause"); -+ } -+ -+ private static void updateMob(final MapType data) { -+ updateLivingEntity(data); -+ -+ final MapType leash = data.getMap("Leash"); -+ if (leash == null) { -+ return; -+ } -+ -+ replaceUUIDLeastMost(leash, "UUID", "UUID"); -+ } -+ -+ private static void updateLivingEntity(final MapType data) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ if (attributes == null) { -+ return; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ final MapType attribute = attributes.getMap(i); -+ -+ final ListType modifiers = attribute.getList("Modifiers", ObjectType.MAP); -+ if (modifiers == null) { -+ continue; -+ } -+ -+ for (int k = 0; k < modifiers.size(); ++k) { -+ replaceUUIDLeastMost(modifiers.getMap(k), "UUID", "UUID"); -+ } -+ } -+ } -+ -+ private static void updateProjectile(final MapType data) { -+ final Object ownerUUID = data.getGeneric("OwnerUUID"); -+ if (ownerUUID != null) { -+ data.remove("OwnerUUID"); -+ data.setGeneric("Owner", ownerUUID); -+ } -+ } -+ -+ private static void updateEntityUUID(final MapType data) { -+ replaceUUIDLeastMost(data, "UUID", "UUID"); -+ } -+ -+ public static void register() { -+ // Entity UUID fixes -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateEntityUUID(data); -+ return null; -+ } -+ }); -+ -+ final DataConverter, MapType> animalOwnerConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAnimalOwner(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> animalConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAnimal(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> mobConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateMob(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> livingEntityConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLivingEntity(data); -+ return null; -+ } -+ }; -+ final DataConverter, MapType> projectileConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateProjectile(data); -+ return null; -+ } -+ }; -+ for (final String id : ABSTRACT_HORSES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); -+ } -+ for (final String id : TAMEABLE_ANIMALS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalOwnerConverter); -+ } -+ for (final String id : ANIMALS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, animalConverter); -+ } -+ for (final String id : MOBS) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, mobConverter); -+ } -+ for (final String id : LIVING_ENTITIES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, livingEntityConverter); -+ } -+ for (final String id : PROJECTILES) { -+ MCTypeRegistry.ENTITY.addConverterForId(id, projectileConverter); -+ } -+ -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:bee", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateHurtBy(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombified_piglin", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateHurtBy(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:fox", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateFox(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:item", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateItem(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker_bullet", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateShulkerBullet(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:area_effect_cloud", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateAreaEffectCloud(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateZombieVillager(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:evoker_fangs", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateEvokerFangs(data); -+ return null; -+ } -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:piglin", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updatePiglin(data); -+ return null; -+ } -+ }); -+ -+ -+ // Update TE -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:conduit", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ replaceUUIDMLTag(data, "target_uuid", "Target"); -+ return null; -+ } -+ }); -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:skull", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType owner = data.getMap("Owner"); -+ if (owner == null) { -+ return null; -+ } -+ -+ data.remove("Owner"); -+ -+ replaceUUIDString(owner, "Id", "Id"); -+ -+ data.setMap("SkullOwner", owner); -+ -+ return null; -+ } -+ }); -+ -+ // Player UUID -+ MCTypeRegistry.PLAYER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ updateLivingEntity(data); -+ updateEntityUUID(data); -+ -+ final MapType rootVehicle = data.getMap("RootVehicle"); -+ if (rootVehicle == null) { -+ return null; -+ } -+ -+ replaceUUIDLeastMost(rootVehicle, "Attach", "Attach"); -+ -+ return null; -+ } -+ }); -+ -+ // Level.dat -+ MCTypeRegistry.LEVEL.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ replaceUUIDString(data, "WanderingTraderId", "WanderingTraderId"); -+ -+ final MapType dimensionData = data.getMap("DimensionData"); -+ if (dimensionData != null) { -+ for (final String key : dimensionData.keys()) { -+ final MapType dimension = dimensionData.getMap(key); -+ -+ final MapType dragonFight = dimension.getMap("DragonFight"); -+ if (dragonFight == null) { -+ continue; -+ } -+ -+ replaceUUIDLeastMost(dragonFight, "DragonUUID", "Dragon"); -+ } -+ } -+ -+ final MapType customBossEvents = data.getMap("CustomBossEvents"); -+ if (customBossEvents != null) { -+ for (final String key : customBossEvents.keys()) { -+ final MapType customBossEvent = customBossEvents.getMap(key); -+ -+ final ListType players = customBossEvent.getList("Players", ObjectType.MAP); -+ if (players == null) { -+ continue; -+ } -+ -+ final ListType newPlayers = Types.NBT.createEmptyList(); -+ customBossEvent.setList("Players", newPlayers); -+ -+ for (int i = 0, len = players.size(); i < len; ++i) { -+ final int[] newUUID = createUUIDFromLongs(players.getMap(i), "M", "L"); -+ if (newUUID != null) { -+ newPlayers.addIntArray(newUUID); -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.SAVED_DATA.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ final ListType raids = data.getList("Raids", ObjectType.MAP); -+ if (raids == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = raids.size(); i < len; ++i) { -+ final MapType raid = raids.getMap(i); -+ -+ final ListType heros = raid.getList("HeroesOfTheVillage", ObjectType.MAP); -+ -+ if (heros == null) { -+ continue; -+ } -+ -+ final ListType newHeros = Types.NBT.createEmptyList(); -+ raid.setList("HeroesOfTheVillage", newHeros); -+ -+ for (int k = 0, klen = heros.size(); k < klen; ++k) { -+ final MapType uuidOld = heros.getMap(i); -+ final int[] uuidNew = createUUIDFromLongs(uuidOld, "UUIDMost", "UUIDLeast"); -+ if (uuidNew != null) { -+ newHeros.addIntArray(uuidNew); -+ } -+ } -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ updateAttributeModifiers(tag); -+ -+ if ("minecraft:player_head".equals(data.getString("id"))) { -+ updateSkullOwner(tag); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static void updateAttributeModifiers(final MapType tag) { -+ final ListType attributes = tag.getList("AttributeModifiers", ObjectType.MAP); -+ if (attributes == null) { -+ return; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ replaceUUIDLeastMost(attributes.getMap(i), "UUID", "UUID"); -+ } -+ } -+ -+ private static void updateSkullOwner(final MapType tag) { -+ replaceUUIDString(tag.getMap("SkullOwner"), "Id", "Id"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40bf0a1788520bbf1d66da53b6532bdd8af246f4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2516.java -@@ -0,0 +1,37 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2516 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 1; -+ -+ private V2516() {} -+ -+ public static void register() { -+ final DataConverter, MapType> gossipUUIDConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType gossips = data.getList("Gossips", ObjectType.MAP); -+ -+ if (gossips == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = gossips.size(); i < len; ++i) { -+ V2514.replaceUUIDLeastMost(gossips.getMap(i), "Target", "Target"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", gossipUUIDConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:zombie_villager", gossipUUIDConverter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7a55eeb02fb99289e4c8bfe2d28fc4a0c716719 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2518.java -@@ -0,0 +1,63 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2518 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 3; -+ -+ private static final Map FACING_RENAMES = ImmutableMap.builder() -+ .put("down", "down_south") -+ .put("up", "up_north") -+ .put("north", "north_up") -+ .put("south", "south_up") -+ .put("west", "west_up") -+ .put("east", "east_up") -+ .build(); -+ -+ -+ private V2518() {} -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:jigsaw", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String type = data.getString("attachement_type", "minecraft:empty"); -+ final String pool = data.getString("target_pool", "minecraft:empty"); -+ data.remove("attachement_type"); -+ data.remove("target_pool"); -+ -+ data.setString("name", type); -+ data.setString("target", type); -+ data.setString("pool", pool); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:jigsaw".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ if (properties == null) { -+ return null; -+ } -+ -+ final String facing = properties.getString("facing", "north"); -+ properties.remove("facing"); -+ properties.setString("orientation", FACING_RENAMES.getOrDefault(facing, facing)); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java -new file mode 100644 -index 0000000000000000000000000000000000000000..47a8bb340e72e48fd237d4001b55c81b1182a72f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2519.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2519 { -+ -+ protected static final int VERSION = MCVersions.V20W12A + 4; -+ -+ private V2519() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:strider"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3a91600427cb013cdfc03b084017b6f32d9e05fa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2522.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2522 { -+ -+ protected static final int VERSION = MCVersions.V20W13B + 1; -+ -+ private V2522() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:zoglin"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5d3726bb7670bc89feb8ebeed5c097a77e909f5a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2523.java -@@ -0,0 +1,92 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2523 { -+ -+ protected static final int VERSION = MCVersions.V20W13B + 2; -+ -+ private static final Map RENAMES = ImmutableMap.builder() -+ .put("generic.maxHealth", "generic.max_health") -+ .put("Max Health", "generic.max_health") -+ .put("zombie.spawnReinforcements", "zombie.spawn_reinforcements") -+ .put("Spawn Reinforcements Chance", "zombie.spawn_reinforcements") -+ .put("horse.jumpStrength", "horse.jump_strength") -+ .put("Jump Strength", "horse.jump_strength") -+ .put("generic.followRange", "generic.follow_range") -+ .put("Follow Range", "generic.follow_range") -+ .put("generic.knockbackResistance", "generic.knockback_resistance") -+ .put("Knockback Resistance", "generic.knockback_resistance") -+ .put("generic.movementSpeed", "generic.movement_speed") -+ .put("Movement Speed", "generic.movement_speed") -+ .put("generic.flyingSpeed", "generic.flying_speed") -+ .put("Flying Speed", "generic.flying_speed") -+ .put("generic.attackDamage", "generic.attack_damage") -+ .put("generic.attackKnockback", "generic.attack_knockback") -+ .put("generic.attackSpeed", "generic.attack_speed") -+ .put("generic.armorToughness", "generic.armor_toughness") -+ .build(); -+ -+ private V2523() {} -+ -+ private static void updateName(final MapType data, final String path) { -+ if (data == null) { -+ return; -+ } -+ -+ final String name = data.getString(path); -+ if (name != null) { -+ final String renamed = RENAMES.get(name); -+ if (renamed != null) { -+ data.setString(path, renamed); -+ } -+ } -+ } -+ -+ public static void register() { -+ final DataConverter, MapType> entityConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ updateName(attributes.getMap(i), "Name"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addStructureConverter(entityConverter); -+ MCTypeRegistry.PLAYER.addStructureConverter(entityConverter); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("AttributeModifiers", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ updateName(attributes.getMap(i), "AttributeName"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5e951f91d03f95ed671bb7403592960690c65879 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2527.java -@@ -0,0 +1,123 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.mojang.datafixers.DataFixUtils; -+import net.minecraft.util.Mth; -+ -+public final class V2527 { -+ -+ protected static final int VERSION = MCVersions.V20W16A + 1; -+ -+ private V2527() {} -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ -+ if (palette == null) { -+ continue; -+ } -+ -+ final int bits = Math.max(4, DataFixUtils.ceillog2(palette.size())); -+ -+ if (Mth.isPowerOfTwo(bits)) { -+ // fits perfectly -+ continue; -+ } -+ -+ final long[] states = section.getLongs("BlockStates"); -+ if (states == null) { -+ // wat -+ continue; -+ } -+ -+ section.setLongs("BlockStates", addPadding(4096, bits, states)); -+ } -+ } -+ -+ final MapType heightMaps = level.getMap("Heightmaps"); -+ if (heightMaps != null) { -+ for (final String key : heightMaps.keys()) { -+ final long[] old = heightMaps.getLongs(key); -+ heightMaps.setLongs(key, addPadding(256, 9, old)); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static long[] addPadding(final int indices, final int bits, final long[] old) { -+ int k = old.length; -+ if (k == 0) { -+ return old; -+ } else { -+ long l = (1L << bits) - 1L; -+ int m = 64 / bits; -+ int n = (indices + m - 1) / m; -+ long[] padded = new long[n]; -+ int o = 0; -+ int p = 0; -+ long q = 0L; -+ int r = 0; -+ long s = old[0]; -+ long t = k > 1 ? old[1] : 0L; -+ -+ for(int u = 0; u < indices; ++u) { -+ int v = u * bits; -+ int w = v >> 6; -+ int x = (u + 1) * bits - 1 >> 6; -+ int y = v ^ w << 6; -+ if (w != r) { -+ s = t; -+ t = w + 1 < k ? old[w + 1] : 0L; -+ r = w; -+ } -+ -+ long ab; -+ int ac; -+ if (w == x) { -+ ab = s >>> y & l; -+ } else { -+ ac = 64 - y; -+ ab = (s >>> y | t << ac) & l; -+ } -+ -+ ac = p + bits; -+ if (ac >= 64) { -+ padded[o++] = q; -+ q = ab; -+ p = bits; -+ } else { -+ q |= ab << p; -+ p = ac; -+ } -+ } -+ -+ if (q != 0L) { -+ padded[o] = q; -+ } -+ -+ return padded; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f7f2e3f75a9f8a5533fe010bc23e8384878d7ce8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2528.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2528 { -+ -+ protected static final int VERSION = MCVersions.V20W16A + 2; -+ -+ private V2528() {} -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:soul_fire_torch", "minecraft:soul_torch", -+ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" -+ )::get); -+ ConverterAbstractBlockRename.register(VERSION, ImmutableMap.of( -+ "minecraft:soul_fire_torch", "minecraft:soul_torch", -+ "minecraft:soul_fire_wall_torch", "minecraft:soul_wall_torch", -+ "minecraft:soul_fire_lantern", "minecraft:soul_lantern" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f239dbf4beb87efd1fff4e5d8d6f041fa8687f01 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2529.java -@@ -0,0 +1,25 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2529 { -+ -+ protected static final int VERSION = MCVersions.V20W17A; -+ -+ private V2529() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:strider", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("NoGravity")) { -+ data.setBoolean("NoGravity", false); -+ } -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7783d75578d29e09029b26c8c8c0b053c5526eb9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2531.java -@@ -0,0 +1,63 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2531 { -+ -+ protected static final int VERSION = MCVersions.V20W17A + 2; -+ -+ private V2531() {} -+ -+ private static boolean isConnected(final String facing) { -+ return !"none".equals(facing); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:redstone_wire".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ -+ if (properties == null) { -+ return null; -+ } -+ -+ -+ final String east = properties.getString("east", "none"); -+ final String west = properties.getString("west", "none"); -+ final String north = properties.getString("north", "none"); -+ final String south = properties.getString("south", "none"); -+ -+ final boolean connectedX = isConnected(east) || isConnected(west); -+ final boolean connectedZ = isConnected(north) || isConnected(south); -+ -+ final String newEast = !isConnected(east) && !connectedZ ? "side" : east; -+ final String newWest = !isConnected(west) && !connectedZ ? "side" : west; -+ final String newNorth = !isConnected(north) && !connectedX ? "side" : north; -+ final String newSouth = !isConnected(south) && !connectedX ? "side" : south; -+ -+ if (properties.hasKey("east")) { -+ properties.setString("east", newEast); -+ } -+ if (properties.hasKey("west")) { -+ properties.setString("west", newWest); -+ } -+ if (properties.hasKey("north")) { -+ properties.setString("north", newNorth); -+ } -+ if (properties.hasKey("south")) { -+ properties.setString("south", newSouth); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ece1cd5afab80a8271b2ebac95dcc0a6239cd42c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2533.java -@@ -0,0 +1,43 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2533 { -+ -+ protected static final int VERSION = MCVersions.V20W18A + 1; -+ -+ private V2533() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:villager", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType attributes = data.getList("Attributes", ObjectType.MAP); -+ -+ if (attributes == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = attributes.size(); i < len; ++i) { -+ final MapType attribute = attributes.getMap(i); -+ -+ if (!"generic.follow_range".equals(attribute.getString("Name"))) { -+ continue; -+ } -+ -+ if (attribute.getDouble("Base") == 16.0) { -+ attribute.setDouble("Base", 48.0); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9648299bb96c20c783bb7c7010173a0f007584e0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2535.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2535 { -+ -+ protected static final int VERSION = MCVersions.V20W19A + 1; -+ -+ private V2535() {} -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // Mojang uses doubles for whatever reason... rotation is in FLOAT. by using double here -+ // the entity load will just ignore rotation and set it to 0... -+ final ListType rotation = data.getList("Rotation", ObjectType.FLOAT); -+ -+ if (rotation == null || rotation.size() == 0) { -+ return null; -+ } -+ -+ rotation.setFloat(0, rotation.getFloat(0) - 180.0F); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b9a50d44982abe228c5e7d58a4b917d0fbfda6b9 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2550.java -@@ -0,0 +1,342 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import org.apache.commons.lang3.math.NumberUtils; -+import java.util.HashMap; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V2550 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 13; -+ -+ private static final ImmutableMap DEFAULTS = ImmutableMap.builder() -+ .put("minecraft:village", new StructureFeatureConfiguration(32, 8, 10387312)) -+ .put("minecraft:desert_pyramid", new StructureFeatureConfiguration(32, 8, 14357617)) -+ .put("minecraft:igloo", new StructureFeatureConfiguration(32, 8, 14357618)) -+ .put("minecraft:jungle_pyramid", new StructureFeatureConfiguration(32, 8, 14357619)) -+ .put("minecraft:swamp_hut", new StructureFeatureConfiguration(32, 8, 14357620)) -+ .put("minecraft:pillager_outpost", new StructureFeatureConfiguration(32, 8, 165745296)) -+ .put("minecraft:monument", new StructureFeatureConfiguration(32, 5, 10387313)) -+ .put("minecraft:endcity", new StructureFeatureConfiguration(20, 11, 10387313)) -+ .put("minecraft:mansion", new StructureFeatureConfiguration(80, 20, 10387319)) -+ .build(); -+ -+ record StructureFeatureConfiguration(int spacing, int separation, int salt) { -+ -+ public MapType serialize() { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setInt("spacing", this.spacing); -+ ret.setInt("separation", this.separation); -+ ret.setInt("salt", this.salt); -+ -+ return ret; -+ } -+ } -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final long seed = data.getLong("RandomSeed"); -+ String generatorName = data.getString("generatorName"); -+ if (generatorName != null) { -+ generatorName = generatorName.toLowerCase(Locale.ROOT); -+ } -+ String legacyCustomOptions = data.getString("legacy_custom_options"); -+ if (legacyCustomOptions == null) { -+ legacyCustomOptions = "customized".equals(generatorName) ? data.getString("generatorOptions") : null; -+ } -+ -+ final MapType generator; -+ boolean caves = false; -+ -+ if ("customized".equals(generatorName) || generatorName == null) { -+ generator = defaultOverworld(seed); -+ } else { -+ switch (generatorName) { -+ case "flat": { -+ final MapType generatorOptions = data.getMap("generatorOptions"); -+ -+ final MapType structures = fixFlatStructures(generatorOptions); -+ final MapType settings = Types.NBT.createEmptyMap(); -+ generator = Types.NBT.createEmptyMap(); -+ generator.setString("type", "minecraft:flat"); -+ generator.setMap("settings", settings); -+ -+ settings.setMap("structures", structures); -+ -+ ListType layers = generatorOptions.getList("layers", ObjectType.MAP); -+ if (layers == null) { -+ layers = Types.NBT.createEmptyList(); -+ -+ final int[] heights = new int[] { 1, 2, 1 }; -+ final String[] blocks = new String[] { "minecraft:bedrock", "minecraft:dirt", "minecraft:grass_block" }; -+ for (int i = 0; i < 3; ++i) { -+ final MapType layer = Types.NBT.createEmptyMap(); -+ layer.setInt("height", heights[i]); -+ layer.setString("block", blocks[i]); -+ layers.addMap(layer); -+ } -+ } -+ -+ settings.setList("layers", layers); -+ settings.setString("biome", generatorOptions.getString("biome", "minecraft:plains")); -+ -+ break; -+ } -+ -+ case "debug_all_block_states": { -+ generator = Types.NBT.createEmptyMap(); -+ generator.setString("type", "minecraft:debug"); -+ break; -+ } -+ -+ case "buffet": { -+ final MapType generatorOptions = data.getMap("generatorOptions"); -+ final MapType chunkGenerator = generatorOptions == null ? null : generatorOptions.getMap("chunk_generator"); -+ final String chunkGeneratorType = chunkGenerator == null ? null : chunkGenerator.getString("type"); -+ -+ final String newType; -+ if ("minecraft:caves".equals(chunkGeneratorType)) { -+ newType = "minecraft:caves"; -+ caves = true; -+ } else if ("minecraft:floating_islands".equals(chunkGeneratorType)) { -+ newType = "minecraft:floating_islands"; -+ } else { -+ newType = "minecraft:overworld"; -+ } -+ -+ MapType biomeSource = generatorOptions == null ? null : generatorOptions.getMap("biome_source"); -+ if (biomeSource == null) { -+ biomeSource = Types.NBT.createEmptyMap(); -+ biomeSource.setString("type", "minecraft:fixed"); -+ } -+ -+ if ("minecraft:fixed".equals(biomeSource.getString("type"))) { -+ final MapType options = biomeSource.getMap("options"); -+ final ListType biomes = options == null ? null : options.getList("biomes", ObjectType.STRING); -+ final String biome = biomes == null || biomes.size() == 0 ? "minecraft:ocean" : biomes.getString(0); -+ biomeSource.remove("options"); -+ biomeSource.setString("biome", biome); -+ } -+ -+ generator = noise(seed, newType, biomeSource); -+ break; -+ } -+ -+ default: { -+ boolean defaultGen = generatorName.equals("default"); -+ boolean default11Gen = generatorName.equals("default_1_1") || defaultGen && data.getInt("generatorVersion") == 0; -+ boolean amplified = generatorName.equals("amplified"); -+ boolean largeBiomes = generatorName.equals("largebiomes"); -+ -+ generator = noise(seed, amplified ? "minecraft:amplified" : "minecraft:overworld", -+ vanillaBiomeSource(seed, default11Gen, largeBiomes)); -+ break; -+ } -+ } -+ } -+ -+ final boolean mapFeatures = data.getBoolean("MapFeatures", true); -+ final boolean bonusChest = data.getBoolean("BonusChest", false); -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setLong("seed", seed); -+ ret.setBoolean("generate_features", mapFeatures); -+ ret.setBoolean("bonus_chest", bonusChest); -+ ret.setMap("dimensions", vanillaLevels(seed, generator, caves)); -+ if (legacyCustomOptions != null) { -+ ret.setString("legacy_custom_options", legacyCustomOptions); -+ } -+ -+ return ret; -+ } -+ }); -+ } -+ -+ public static MapType noise(final long seed, final String worldType, final MapType biomeSource) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("type", "minecraft:noise"); -+ ret.setMap("biome_source", biomeSource); -+ ret.setLong("seed", seed); -+ ret.setString("settings", worldType); -+ -+ return ret; -+ } -+ -+ public static MapType vanillaBiomeSource(final long seed, final boolean default11Gen, final boolean largeBiomes) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("type", "minecraft:vanilla_layered"); -+ ret.setLong("seed", seed); -+ ret.setBoolean("large_biomes", largeBiomes); -+ if (default11Gen) { -+ ret.setBoolean("legacy_biome_init_layer", default11Gen); -+ } -+ -+ return ret; -+ } -+ -+ public static MapType fixFlatStructures(final MapType generatorOptions) { -+ int distance = 32; -+ int spread = 3; -+ int count = 128; -+ boolean stronghold = false; -+ final Map newStructures = new HashMap<>(); -+ -+ if (generatorOptions == null) { -+ stronghold = true; -+ newStructures.put("minecraft:village", DEFAULTS.get("minecraft:village")); -+ } -+ -+ final MapType oldStructures = generatorOptions == null ? null : generatorOptions.getMap("structures"); -+ if (oldStructures != null) { -+ for (final String structureName : oldStructures.keys()) { -+ final MapType structureValues = oldStructures.getMap(structureName); -+ if (structureValues == null) { -+ continue; -+ } -+ -+ for (final String structureValueKey : structureValues.keys()) { -+ final String structureValue = structureValues.getString(structureValueKey); -+ -+ if ("stronghold".equals(structureName)) { -+ stronghold = true; -+ switch (structureValueKey) { -+ case "distance": -+ distance = getInt(structureValue, distance, 1); -+ break; -+ case "spread": -+ spread = getInt(structureValue, spread, 1); -+ break; -+ case "count": -+ count = getInt(structureValue, count, 1); -+ break; -+ } -+ } else { -+ switch (structureValueKey) { -+ case "distance": -+ switch (structureName) { -+ case "village": -+ setSpacing(newStructures, "minecraft:village", structureValue, 9); -+ break; -+ case "biome_1": -+ setSpacing(newStructures, "minecraft:desert_pyramid", structureValue, 9); -+ setSpacing(newStructures, "minecraft:igloo", structureValue, 9); -+ setSpacing(newStructures, "minecraft:jungle_pyramid", structureValue, 9); -+ setSpacing(newStructures, "minecraft:swamp_hut", structureValue, 9); -+ setSpacing(newStructures, "minecraft:pillager_outpost", structureValue, 9); -+ break; -+ case "endcity": -+ setSpacing(newStructures, "minecraft:endcity", structureValue, 1); -+ break; -+ case "mansion": -+ setSpacing(newStructures, "minecraft:mansion", structureValue, 1); -+ break; -+ default: -+ break; -+ } -+ case "separation": -+ if ("oceanmonument".equals(structureName)) { -+ final StructureFeatureConfiguration structure = newStructures.getOrDefault("minecraft:monument", DEFAULTS.get("minecraft:monument")); -+ final int newSpacing = getInt(structureValue, structure.separation, 1); -+ newStructures.put("minecraft:monument", new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); -+ } -+ -+ break; -+ case "spacing": -+ if ("oceanmonument".equals(structureName)) { -+ setSpacing(newStructures, "minecraft:monument", structureValue, 1); -+ } -+ -+ break; -+ } -+ } -+ } -+ } -+ } -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ final MapType structuresSerialized = Types.NBT.createEmptyMap(); -+ ret.setMap("structures", structuresSerialized); -+ for (final String key : newStructures.keySet()) { -+ structuresSerialized.setMap(key, newStructures.get(key).serialize()); -+ } -+ -+ if (stronghold) { -+ final MapType strongholdData = Types.NBT.createEmptyMap(); -+ ret.setMap("stronghold", strongholdData); -+ -+ strongholdData.setInt("distance", distance); -+ strongholdData.setInt("spread", spread); -+ strongholdData.setInt("count", count); -+ } -+ -+ return ret; -+ } -+ -+ public static MapType vanillaLevels(final long seed, final MapType generator, final boolean caves) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ final MapType overworld = Types.NBT.createEmptyMap(); -+ final MapType nether = Types.NBT.createEmptyMap(); -+ final MapType end = Types.NBT.createEmptyMap(); -+ -+ ret.setMap("minecraft:overworld", overworld); -+ ret.setMap("minecraft:the_nether", nether); -+ ret.setMap("minecraft:the_end", end); -+ -+ // overworld -+ overworld.setString("type", caves ? "minecraft:overworld_caves" : "minecraft:overworld"); -+ overworld.setMap("generator", generator); -+ -+ // nether -+ nether.setString("type", "minecraft:the_nether"); -+ final MapType netherBiomeSource = Types.NBT.createEmptyMap(); -+ netherBiomeSource.setString("type", "minecraft:multi_noise"); -+ netherBiomeSource.setLong("seed", seed); -+ netherBiomeSource.setString("preset", "minecraft:nether"); -+ -+ nether.setMap("generator", noise(seed, "minecraft:nether", netherBiomeSource)); -+ -+ // end -+ end.setString("type", "minecraft:the_end"); -+ final MapType endBiomeSource = Types.NBT.createEmptyMap(); -+ endBiomeSource.setString("type", "minecraft:the_end"); -+ endBiomeSource.setLong("seed", seed); -+ end.setMap("generator", noise(seed,"minecraft:end", endBiomeSource)); -+ -+ return ret; -+ } -+ -+ public static MapType defaultOverworld(final long seed) { -+ return noise(seed, "minecraft:overworld", vanillaBiomeSource(seed, false, false)); -+ } -+ -+ private static int getInt(final String value, final int dfl) { -+ return NumberUtils.toInt(value, dfl); -+ } -+ -+ private static int getInt(final String value, final int dfl, final int minVal) { -+ return Math.max(minVal, getInt(value, dfl)); -+ } -+ -+ private static void setSpacing(final Map structures, final String structureName, -+ final String value, final int minVal) { -+ final StructureFeatureConfiguration structure = structures.getOrDefault(structureName, DEFAULTS.get(structureName)); -+ final int newSpacing = getInt(value, structure.spacing, minVal); -+ -+ structures.put(structureName, new StructureFeatureConfiguration(newSpacing, structure.separation, structure.salt)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ac0c4475556fe5202a6aa5724cb47b35c0cc9c00 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2551.java -@@ -0,0 +1,101 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2551 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 14; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final String type = generator.getString("type"); -+ if (type == null) { -+ continue; -+ } -+ -+ switch (type) { -+ case "minecraft:flat": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); -+ -+ final ListType layers = settings.getList("layers", ObjectType.MAP); -+ if (layers != null) { -+ for (int i = 0, len = layers.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:noise": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings != null) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); -+ } -+ -+ final MapType biomeSource = generator.getMap("biome_source"); -+ if (biomeSource != null) { -+ final String biomeSourceType = biomeSource.getString("type", ""); -+ switch (biomeSourceType) { -+ case "minecraft:fixed": { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:multi_noise": { -+ // Vanilla's schema is wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) -+ // But it just contains the list part. That obviously can never be the case, because -+ // the root object is a compound, not a list. -+ -+ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); -+ if (biomes != null) { -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); -+ } -+ } -+ break; -+ } -+ -+ case "minecraft:checkerboard": { -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); -+ break; -+ } -+ } -+ } -+ -+ break; -+ } -+ } -+ } -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c06aa2099494f82bad2c1f212b2db07405f8f6ff ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2552.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2552 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 15; -+ -+ private V2552() {} -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, ImmutableMap.of( -+ "minecraft:nether", "minecraft:nether_wastes" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java -new file mode 100644 -index 0000000000000000000000000000000000000000..20c57f66e92922a8843887fb032f01c873c45679 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2553.java -@@ -0,0 +1,75 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import java.util.Map; -+ -+public final class V2553 { -+ -+ protected static final int VERSION = MCVersions.V20W20B + 16; -+ -+ public static final Map BIOME_RENAMES = ImmutableMap.builder() -+ .put("minecraft:extreme_hills", "minecraft:mountains") -+ .put("minecraft:swampland", "minecraft:swamp") -+ .put("minecraft:hell", "minecraft:nether_wastes") -+ .put("minecraft:sky", "minecraft:the_end") -+ .put("minecraft:ice_flats", "minecraft:snowy_tundra") -+ .put("minecraft:ice_mountains", "minecraft:snowy_mountains") -+ .put("minecraft:mushroom_island", "minecraft:mushroom_fields") -+ .put("minecraft:mushroom_island_shore", "minecraft:mushroom_field_shore") -+ .put("minecraft:beaches", "minecraft:beach") -+ .put("minecraft:forest_hills", "minecraft:wooded_hills") -+ .put("minecraft:smaller_extreme_hills", "minecraft:mountain_edge") -+ .put("minecraft:stone_beach", "minecraft:stone_shore") -+ .put("minecraft:cold_beach", "minecraft:snowy_beach") -+ .put("minecraft:roofed_forest", "minecraft:dark_forest") -+ .put("minecraft:taiga_cold", "minecraft:snowy_taiga") -+ .put("minecraft:taiga_cold_hills", "minecraft:snowy_taiga_hills") -+ .put("minecraft:redwood_taiga", "minecraft:giant_tree_taiga") -+ .put("minecraft:redwood_taiga_hills", "minecraft:giant_tree_taiga_hills") -+ .put("minecraft:extreme_hills_with_trees", "minecraft:wooded_mountains") -+ .put("minecraft:savanna_rock", "minecraft:savanna_plateau") -+ .put("minecraft:mesa", "minecraft:badlands") -+ .put("minecraft:mesa_rock", "minecraft:wooded_badlands_plateau") -+ .put("minecraft:mesa_clear_rock", "minecraft:badlands_plateau") -+ .put("minecraft:sky_island_low", "minecraft:small_end_islands") -+ .put("minecraft:sky_island_medium", "minecraft:end_midlands") -+ .put("minecraft:sky_island_high", "minecraft:end_highlands") -+ .put("minecraft:sky_island_barren", "minecraft:end_barrens") -+ .put("minecraft:void", "minecraft:the_void") -+ .put("minecraft:mutated_plains", "minecraft:sunflower_plains") -+ .put("minecraft:mutated_desert", "minecraft:desert_lakes") -+ .put("minecraft:mutated_extreme_hills", "minecraft:gravelly_mountains") -+ .put("minecraft:mutated_forest", "minecraft:flower_forest") -+ .put("minecraft:mutated_taiga", "minecraft:taiga_mountains") -+ .put("minecraft:mutated_swampland", "minecraft:swamp_hills") -+ .put("minecraft:mutated_ice_flats", "minecraft:ice_spikes") -+ .put("minecraft:mutated_jungle", "minecraft:modified_jungle") -+ .put("minecraft:mutated_jungle_edge", "minecraft:modified_jungle_edge") -+ .put("minecraft:mutated_birch_forest", "minecraft:tall_birch_forest") -+ .put("minecraft:mutated_birch_forest_hills", "minecraft:tall_birch_hills") -+ .put("minecraft:mutated_roofed_forest", "minecraft:dark_forest_hills") -+ .put("minecraft:mutated_taiga_cold", "minecraft:snowy_taiga_mountains") -+ .put("minecraft:mutated_redwood_taiga", "minecraft:giant_spruce_taiga") -+ .put("minecraft:mutated_redwood_taiga_hills", "minecraft:giant_spruce_taiga_hills") -+ .put("minecraft:mutated_extreme_hills_with_trees", "minecraft:modified_gravelly_mountains") -+ .put("minecraft:mutated_savanna", "minecraft:shattered_savanna") -+ .put("minecraft:mutated_savanna_rock", "minecraft:shattered_savanna_plateau") -+ .put("minecraft:mutated_mesa", "minecraft:eroded_badlands") -+ .put("minecraft:mutated_mesa_rock", "minecraft:modified_wooded_badlands_plateau") -+ .put("minecraft:mutated_mesa_clear_rock", "minecraft:modified_badlands_plateau") -+ .put("minecraft:warm_deep_ocean", "minecraft:deep_warm_ocean") -+ .put("minecraft:lukewarm_deep_ocean", "minecraft:deep_lukewarm_ocean") -+ .put("minecraft:cold_deep_ocean", "minecraft:deep_cold_ocean") -+ .put("minecraft:frozen_deep_ocean", "minecraft:deep_frozen_ocean") -+ .build(); -+ -+ -+ private V2553() {} -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0e228fd642cbca13e8682950b5f0ec4e3e8a4da7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2558.java -@@ -0,0 +1,45 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.options.ConverterAbstractOptionsRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2558 { -+ -+ protected static final int VERSION = MCVersions.V1_16_PRE2 + 1; -+ -+ private V2558() {} -+ -+ public static void register() { -+ ConverterAbstractOptionsRename.register(VERSION, ImmutableMap.of( -+ "key_key.swapHands", "key_key.swapOffhand" -+ )::get); -+ -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType dimensions = data.getMap("dimensions"); -+ if (dimensions == null) { -+ dimensions = Types.NBT.createEmptyMap(); -+ data.setMap("dimensions", dimensions); -+ } -+ -+ if (dimensions.isEmpty()) { -+ data.setMap("dimensions", recreateSettings(data)); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static MapType recreateSettings(final MapType data) { -+ final long seed = data.getLong("seed"); -+ -+ return V2550.vanillaLevels(seed, V2550.defaultOverworld(seed), false); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5835159d7016fb4f05d137acba709fa7d8e8e752 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2568.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2568 { -+ -+ protected static final int VERSION = MCVersions.V1_16_1 + 1; -+ -+ private V2568() {} -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:piglin_brute"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java -new file mode 100644 -index 0000000000000000000000000000000000000000..374d24db80f3ed8cd241e18d776c39a8da72d8fd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2671.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2671 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 85; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:goat"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6c788f51be0439797bf9fc8711d4cf8e382f5c11 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2679.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2679 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 93; -+ -+ public static void register() { -+ MCTypeRegistry.BLOCK_STATE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!"minecraft:cauldron".equals(data.getString("Name"))) { -+ return null; -+ } -+ -+ final MapType properties = data.getMap("Properties"); -+ -+ if (properties == null) { -+ return null; -+ } -+ -+ if (properties.getString("level", "0").equals("0")) { -+ data.remove("Properties"); -+ return null; -+ } else { -+ data.setString("Name", "minecraft:water_cauldron"); -+ return null; -+ } -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java -new file mode 100644 -index 0000000000000000000000000000000000000000..232a28c07c6341a996253282d9872d76b3fce0e3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2680.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2680 { -+ -+ protected static final int VERSION = MCVersions.V1_16_5 + 94; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:grass_path", "minecraft:dirt_path" -+ )::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( -+ "minecraft:grass_path", "minecraft:dirt_path" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4706a7cfb97d3d5c521914f8dfc894db277bcfe0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2684.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+ -+public final class V2684 { -+ -+ protected static final int VERSION = MCVersions.V20W48A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_sensor", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f6a6f33d4f701f4188828994c8e56dea21950366 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2686.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2686 { -+ -+ protected static final int VERSION = MCVersions.V20W49A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:axolotl"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6fcfcb66e1fd9291abad47e41ee076a7816b4244 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2688.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+ -+public final class V2688 { -+ -+ protected static final int VERSION = MCVersions.V20W51A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:glow_squid"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:glow_item_frame", new DataWalkerItems("Item")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1638f04efd4063c23807b29bc226ad33eed27e7b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2690.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2690 { -+ -+ protected static final int VERSION = MCVersions.V21W05A; -+ -+ protected static final ImmutableMap RENAMES = ImmutableMap.builder() -+ .put("minecraft:weathered_copper_block", "minecraft:oxidized_copper_block") -+ .put("minecraft:semi_weathered_copper_block", "minecraft:weathered_copper_block") -+ .put("minecraft:lightly_weathered_copper_block", "minecraft:exposed_copper_block") -+ .put("minecraft:weathered_cut_copper", "minecraft:oxidized_cut_copper") -+ .put("minecraft:semi_weathered_cut_copper", "minecraft:weathered_cut_copper") -+ .put("minecraft:lightly_weathered_cut_copper", "minecraft:exposed_cut_copper") -+ .put("minecraft:weathered_cut_copper_stairs", "minecraft:oxidized_cut_copper_stairs") -+ .put("minecraft:semi_weathered_cut_copper_stairs", "minecraft:weathered_cut_copper_stairs") -+ .put("minecraft:lightly_weathered_cut_copper_stairs", "minecraft:exposed_cut_copper_stairs") -+ .put("minecraft:weathered_cut_copper_slab", "minecraft:oxidized_cut_copper_slab") -+ .put("minecraft:semi_weathered_cut_copper_slab", "minecraft:weathered_cut_copper_slab") -+ .put("minecraft:lightly_weathered_cut_copper_slab", "minecraft:exposed_cut_copper_slab") -+ .put("minecraft:waxed_semi_weathered_copper", "minecraft:waxed_weathered_copper") -+ .put("minecraft:waxed_lightly_weathered_copper", "minecraft:waxed_exposed_copper") -+ .put("minecraft:waxed_semi_weathered_cut_copper", "minecraft:waxed_weathered_cut_copper") -+ .put("minecraft:waxed_lightly_weathered_cut_copper", "minecraft:waxed_exposed_cut_copper") -+ .put("minecraft:waxed_semi_weathered_cut_copper_stairs", "minecraft:waxed_weathered_cut_copper_stairs") -+ .put("minecraft:waxed_lightly_weathered_cut_copper_stairs", "minecraft:waxed_exposed_cut_copper_stairs") -+ .put("minecraft:waxed_semi_weathered_cut_copper_slab", "minecraft:waxed_weathered_cut_copper_slab") -+ .put("minecraft:waxed_lightly_weathered_cut_copper_slab", "minecraft:waxed_exposed_cut_copper_slab") -+ .build(); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3841780d52c2e242609fc076efa5902c063b7b48 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2691.java -@@ -0,0 +1,23 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2691 { -+ -+ protected static final int VERSION = MCVersions.V21W05A + 1; -+ -+ protected static final ImmutableMap RENAMES = ImmutableMap.builder() -+ .put("minecraft:waxed_copper", "minecraft:waxed_copper_block") -+ .put("minecraft:oxidized_copper_block", "minecraft:oxidized_copper") -+ .put("minecraft:weathered_copper_block", "minecraft:weathered_copper") -+ .put("minecraft:exposed_copper_block", "minecraft:exposed_copper") -+ .build(); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java -new file mode 100644 -index 0000000000000000000000000000000000000000..deac34afe6a3681db9a7630ad6526f71d4dd6e1f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2693.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V2693 { -+ -+ protected static final int VERSION = MCVersions.V21W05B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0094154c1da7cb95120e01bceeb836ca7ab68e25 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2696.java -@@ -0,0 +1,36 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2696 { -+ -+ protected static final int VERSION = MCVersions.V21W07A + 1; -+ -+ protected static final ImmutableMap RENAMES = ImmutableMap.builder() -+ .put("minecraft:grimstone", "minecraft:deepslate") -+ .put("minecraft:grimstone_slab", "minecraft:cobbled_deepslate_slab") -+ .put("minecraft:grimstone_stairs", "minecraft:cobbled_deepslate_stairs") -+ .put("minecraft:grimstone_wall", "minecraft:cobbled_deepslate_wall") -+ .put("minecraft:polished_grimstone", "minecraft:polished_deepslate") -+ .put("minecraft:polished_grimstone_slab", "minecraft:polished_deepslate_slab") -+ .put("minecraft:polished_grimstone_stairs", "minecraft:polished_deepslate_stairs") -+ .put("minecraft:polished_grimstone_wall", "minecraft:polished_deepslate_wall") -+ .put("minecraft:grimstone_tiles", "minecraft:deepslate_tiles") -+ .put("minecraft:grimstone_tile_slab", "minecraft:deepslate_tile_slab") -+ .put("minecraft:grimstone_tile_stairs", "minecraft:deepslate_tile_stairs") -+ .put("minecraft:grimstone_tile_wall", "minecraft:deepslate_tile_wall") -+ .put("minecraft:grimstone_bricks", "minecraft:deepslate_bricks") -+ .put("minecraft:grimstone_brick_slab", "minecraft:deepslate_brick_slab") -+ .put("minecraft:grimstone_brick_stairs", "minecraft:deepslate_brick_stairs") -+ .put("minecraft:grimstone_brick_wall", "minecraft:deepslate_brick_wall") -+ .put("minecraft:chiseled_grimstone", "minecraft:chiseled_deepslate") -+ .build(); -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, RENAMES::get); -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, RENAMES::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c37142033061a3e4865686ee64d8f15f040d7d41 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2700.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2700 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 1; -+ -+ public static void register() { -+ ConverterAbstractBlockRename.registerAndFixJigsaw(VERSION, ImmutableMap.of( -+ "minecraft:cave_vines_head", "minecraft:cave_vines", -+ "minecraft:cave_vines_body", "minecraft:cave_vines_plant" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9d6b03410c4665e19a2a35226d11f77b2cae3bbf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2701.java -@@ -0,0 +1,203 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import com.google.common.collect.Sets; -+import java.util.Set; -+import java.util.regex.Matcher; -+import java.util.regex.Pattern; -+ -+public final class V2701 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 2; -+ -+ private static final Pattern INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]"); -+ -+ private static final Set PIECE_TYPE = Sets.newHashSet( -+ "minecraft:jigsaw", -+ "minecraft:nvi", -+ "minecraft:pcp", -+ "minecraft:bastionremnant", -+ "minecraft:runtime" -+ ); -+ private static final Set FEATURES = Sets.newHashSet( -+ "minecraft:tree", -+ "minecraft:flower", -+ "minecraft:block_pile", -+ "minecraft:random_patch" -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.STRUCTURE_FEATURE.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final ListType children = data.getList("Children", ObjectType.MAP); -+ -+ if (children == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final MapType child = children.getMap(i); -+ -+ if (!PIECE_TYPE.contains(child.getString("id"))) { -+ continue; -+ } -+ -+ final String poolElement = child.getString("pool_element"); -+ if (!"minecraft:feature_pool_element".equals(poolElement)) { -+ continue; -+ } -+ -+ final MapType feature = child.getMap("feature"); -+ if (feature == null) { -+ continue; -+ } -+ -+ final String replacement = convertToString(feature); -+ -+ if (replacement != null) { -+ child.setString("feature", replacement); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static String getNestedString(final MapType root, final String... paths) { -+ if (paths.length == 0) { -+ throw new IllegalArgumentException("Missing path"); -+ } -+ -+ Object current = root.getGeneric(paths[0]); -+ -+ for (int i = 1; i < paths.length; ++i) { -+ final String path = paths[i]; -+ -+ final Matcher indexMatcher = INDEX_PATTERN.matcher(path); -+ if (!indexMatcher.matches()) { -+ current = (current instanceof MapType) ? ((MapType)current).getGeneric(path) : null; -+ if (current == null) { -+ break; -+ } -+ continue; -+ } -+ -+ final int index = Integer.parseInt(indexMatcher.group(1)); -+ if (!(current instanceof ListType)) { -+ current = null; -+ break; -+ } else { -+ final ListType list = (ListType)current; -+ if (index >= 0 && index < list.size()) { -+ current = list.getGeneric(index); -+ } else { -+ current = null; -+ break; -+ } -+ } -+ } -+ -+ return current instanceof String ? (String)current : ""; -+ } -+ -+ protected static String convertToString(final MapType feature) { -+ return getReplacement( -+ getNestedString(feature, "type"), -+ getNestedString(feature, "name"), -+ getNestedString(feature, "config", "state_provider", "type"), -+ getNestedString(feature, "config", "state_provider", "state", "Name"), -+ getNestedString(feature, "config", "state_provider", "entries", "[0]", "data", "Name"), -+ getNestedString(feature, "config", "foliage_placer", "type"), -+ getNestedString(feature, "config", "leaves_provider", "state", "Name") -+ ); -+ } -+ -+ private static String getReplacement(final String type, final String name, final String stateType, final String stateName, -+ final String firstEntryName, final String foliageName, final String leavesName) { -+ final String actualType; -+ if (!type.isEmpty()) { -+ actualType = type; -+ } else { -+ if (name.isEmpty()) { -+ return null; -+ } -+ -+ if ("minecraft:normal_tree".equals(name)) { -+ actualType = "minecraft:tree"; -+ } else { -+ actualType = name; -+ } -+ } -+ -+ if (FEATURES.contains(actualType)) { -+ if ("minecraft:random_patch".equals(actualType)) { -+ if ("minecraft:simple_state_provider".equals(stateType)) { -+ if ("minecraft:sweet_berry_bush".equals(stateName)) { -+ return "minecraft:patch_berry_bush"; -+ } -+ -+ if ("minecraft:cactus".equals(stateName)) { -+ return "minecraft:patch_cactus"; -+ } -+ } else if ("minecraft:weighted_state_provider".equals(stateType) && ("minecraft:grass".equals(firstEntryName) || "minecraft:fern".equals(firstEntryName))) { -+ return "minecraft:patch_taiga_grass"; -+ } -+ } else if ("minecraft:block_pile".equals(actualType)) { -+ if (!"minecraft:simple_state_provider".equals(stateType) && !"minecraft:rotated_block_provider".equals(stateType)) { -+ if ("minecraft:weighted_state_provider".equals(stateType)) { -+ if ("minecraft:packed_ice".equals(firstEntryName) || "minecraft:blue_ice".equals(firstEntryName)) { -+ return "minecraft:pile_ice"; -+ } -+ -+ if ("minecraft:jack_o_lantern".equals(firstEntryName) || "minecraft:pumpkin".equals(firstEntryName)) { -+ return "minecraft:pile_pumpkin"; -+ } -+ } -+ } else { -+ if ("minecraft:hay_block".equals(stateName)) { -+ return "minecraft:pile_hay"; -+ } -+ -+ if ("minecraft:melon".equals(stateName)) { -+ return "minecraft:pile_melon"; -+ } -+ -+ if ("minecraft:snow".equals(stateName)) { -+ return "minecraft:pile_snow"; -+ } -+ } -+ } else { -+ if ("minecraft:flower".equals(actualType)) { -+ return "minecraft:flower_plain"; -+ } -+ -+ if ("minecraft:tree".equals(actualType)) { -+ if ("minecraft:acacia_foliage_placer".equals(foliageName)) { -+ return "minecraft:acacia"; -+ } -+ -+ if ("minecraft:blob_foliage_placer".equals(foliageName) && "minecraft:oak_leaves".equals(leavesName)) { -+ return "minecraft:oak"; -+ } -+ -+ if ("minecraft:pine_foliage_placer".equals(foliageName)) { -+ return "minecraft:pine"; -+ } -+ -+ if ("minecraft:spruce_foliage_placer".equals(foliageName)) { -+ return "minecraft:spruce"; -+ } -+ } -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java -new file mode 100644 -index 0000000000000000000000000000000000000000..53e45b14c05dab35cd5725998458d47e28718075 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2702.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2702 { -+ -+ protected static final int VERSION = MCVersions.V21W10A + 3; -+ -+ public static void register() { -+ final DataConverter, MapType> arrowConverter = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.hasKey("pickup")) { -+ return null; -+ } -+ -+ final boolean player = data.getBoolean("player", true); -+ data.remove("player"); -+ -+ data.setByte("pickup", player ? (byte)1 : (byte)0); -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:spectral_arrow", arrowConverter); -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:trident", arrowConverter); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java -new file mode 100644 -index 0000000000000000000000000000000000000000..74c1df97036059b3a5147f7cf94752ef4516a33d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2707.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V2707 { -+ -+ protected static final int VERSION = MCVersions.V21W14A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", true)); -+ -+ registerMob("minecraft:marker"); // ????????????? -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d31b10a4f93c0eb8bff66dd062ffb950b2182ec ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2710.java -@@ -0,0 +1,17 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.stats.ConverterAbstractStatsRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2710 { -+ -+ protected static final int VERSION = MCVersions.V21W15A + 1; -+ -+ public static void register() { -+ ConverterAbstractStatsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:play_one_minute", "minecraft:play_time" -+ )::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8678ba95b5abe96b399a310623078f8827dfa0f0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2717.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.blockname.ConverterAbstractBlockRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2717 { -+ -+ protected static final int VERSION = MCVersions.V1_17_PRE1 + 1; -+ -+ public static void register() { -+ final ImmutableMap rename = ImmutableMap.of( -+ "minecraft:azalea_leaves_flowers", "minecraft:flowering_azalea_leaves" -+ ); -+ ConverterAbstractItemRename.register(VERSION, rename::get); -+ ConverterAbstractBlockRename.register(VERSION, rename::get); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c2d2b7c10e5b988b1111b20b778c475a12bef353 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2825.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.AddFlagIfAbsent; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+ -+public final class V2825 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 95; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new AddFlagIfAbsent(VERSION, "has_increased_height_already", false)); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d28ade80499dce882a9a84309a2a0da527fe01a0 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2831.java -@@ -0,0 +1,69 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V2831 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 101; -+ -+ public static void register() { -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { -+ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, spawnPotential.getMap("data"), "entity", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, root.getMap("SpawnData"), "entity", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.UNTAGGED_SPAWNER.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType spawnData = root.getMap("SpawnData"); -+ if (spawnData != null) { -+ final MapType wrapped = Types.NBT.createEmptyMap(); -+ root.setMap("SpawnData", wrapped); -+ -+ wrapped.setMap("entity", spawnData); -+ } -+ -+ final ListType spawnPotentials = root.getList("SpawnPotentials", ObjectType.MAP); -+ if (spawnPotentials != null) { -+ for (int i = 0, len = spawnPotentials.size(); i < len; ++i) { -+ final MapType spawnPotential = spawnPotentials.getMap(i); -+ -+ // new format of weighted list (SpawnPotentials): -+ // root.data -> data -+ // root.weight -> weight -+ -+ final MapType entity = spawnPotential.getMap("Entity"); -+ final int weight = spawnPotential.getInt("Weight", 1); -+ spawnPotential.remove("Entity"); -+ spawnPotential.remove("Weight"); -+ spawnPotential.setInt("weight", weight); -+ -+ final MapType data = Types.NBT.createEmptyMap(); -+ spawnPotential.setMap("data", data); -+ -+ data.setMap("entity", entity); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java -new file mode 100644 -index 0000000000000000000000000000000000000000..95a306d6cc5b4ac8161d5bed80b6a7073b3e914e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2832.java -@@ -0,0 +1,920 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.mojang.logging.LogUtils; -+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.ints.IntIterator; -+import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -+import org.apache.commons.lang3.mutable.MutableBoolean; -+import org.slf4j.Logger; -+import java.util.Arrays; -+import java.util.BitSet; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V2832 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 102; -+ -+ private static final String[] BIOMES_BY_ID = new String[256]; // rip datapacks -+ static { -+ BIOMES_BY_ID[0] = "minecraft:ocean"; -+ BIOMES_BY_ID[1] = "minecraft:plains"; -+ BIOMES_BY_ID[2] = "minecraft:desert"; -+ BIOMES_BY_ID[3] = "minecraft:mountains"; -+ BIOMES_BY_ID[4] = "minecraft:forest"; -+ BIOMES_BY_ID[5] = "minecraft:taiga"; -+ BIOMES_BY_ID[6] = "minecraft:swamp"; -+ BIOMES_BY_ID[7] = "minecraft:river"; -+ BIOMES_BY_ID[8] = "minecraft:nether_wastes"; -+ BIOMES_BY_ID[9] = "minecraft:the_end"; -+ BIOMES_BY_ID[10] = "minecraft:frozen_ocean"; -+ BIOMES_BY_ID[11] = "minecraft:frozen_river"; -+ BIOMES_BY_ID[12] = "minecraft:snowy_tundra"; -+ BIOMES_BY_ID[13] = "minecraft:snowy_mountains"; -+ BIOMES_BY_ID[14] = "minecraft:mushroom_fields"; -+ BIOMES_BY_ID[15] = "minecraft:mushroom_field_shore"; -+ BIOMES_BY_ID[16] = "minecraft:beach"; -+ BIOMES_BY_ID[17] = "minecraft:desert_hills"; -+ BIOMES_BY_ID[18] = "minecraft:wooded_hills"; -+ BIOMES_BY_ID[19] = "minecraft:taiga_hills"; -+ BIOMES_BY_ID[20] = "minecraft:mountain_edge"; -+ BIOMES_BY_ID[21] = "minecraft:jungle"; -+ BIOMES_BY_ID[22] = "minecraft:jungle_hills"; -+ BIOMES_BY_ID[23] = "minecraft:jungle_edge"; -+ BIOMES_BY_ID[24] = "minecraft:deep_ocean"; -+ BIOMES_BY_ID[25] = "minecraft:stone_shore"; -+ BIOMES_BY_ID[26] = "minecraft:snowy_beach"; -+ BIOMES_BY_ID[27] = "minecraft:birch_forest"; -+ BIOMES_BY_ID[28] = "minecraft:birch_forest_hills"; -+ BIOMES_BY_ID[29] = "minecraft:dark_forest"; -+ BIOMES_BY_ID[30] = "minecraft:snowy_taiga"; -+ BIOMES_BY_ID[31] = "minecraft:snowy_taiga_hills"; -+ BIOMES_BY_ID[32] = "minecraft:giant_tree_taiga"; -+ BIOMES_BY_ID[33] = "minecraft:giant_tree_taiga_hills"; -+ BIOMES_BY_ID[34] = "minecraft:wooded_mountains"; -+ BIOMES_BY_ID[35] = "minecraft:savanna"; -+ BIOMES_BY_ID[36] = "minecraft:savanna_plateau"; -+ BIOMES_BY_ID[37] = "minecraft:badlands"; -+ BIOMES_BY_ID[38] = "minecraft:wooded_badlands_plateau"; -+ BIOMES_BY_ID[39] = "minecraft:badlands_plateau"; -+ BIOMES_BY_ID[40] = "minecraft:small_end_islands"; -+ BIOMES_BY_ID[41] = "minecraft:end_midlands"; -+ BIOMES_BY_ID[42] = "minecraft:end_highlands"; -+ BIOMES_BY_ID[43] = "minecraft:end_barrens"; -+ BIOMES_BY_ID[44] = "minecraft:warm_ocean"; -+ BIOMES_BY_ID[45] = "minecraft:lukewarm_ocean"; -+ BIOMES_BY_ID[46] = "minecraft:cold_ocean"; -+ BIOMES_BY_ID[47] = "minecraft:deep_warm_ocean"; -+ BIOMES_BY_ID[48] = "minecraft:deep_lukewarm_ocean"; -+ BIOMES_BY_ID[49] = "minecraft:deep_cold_ocean"; -+ BIOMES_BY_ID[50] = "minecraft:deep_frozen_ocean"; -+ BIOMES_BY_ID[127] = "minecraft:the_void"; -+ BIOMES_BY_ID[129] = "minecraft:sunflower_plains"; -+ BIOMES_BY_ID[130] = "minecraft:desert_lakes"; -+ BIOMES_BY_ID[131] = "minecraft:gravelly_mountains"; -+ BIOMES_BY_ID[132] = "minecraft:flower_forest"; -+ BIOMES_BY_ID[133] = "minecraft:taiga_mountains"; -+ BIOMES_BY_ID[134] = "minecraft:swamp_hills"; -+ BIOMES_BY_ID[140] = "minecraft:ice_spikes"; -+ BIOMES_BY_ID[149] = "minecraft:modified_jungle"; -+ BIOMES_BY_ID[151] = "minecraft:modified_jungle_edge"; -+ BIOMES_BY_ID[155] = "minecraft:tall_birch_forest"; -+ BIOMES_BY_ID[156] = "minecraft:tall_birch_hills"; -+ BIOMES_BY_ID[157] = "minecraft:dark_forest_hills"; -+ BIOMES_BY_ID[158] = "minecraft:snowy_taiga_mountains"; -+ BIOMES_BY_ID[160] = "minecraft:giant_spruce_taiga"; -+ BIOMES_BY_ID[161] = "minecraft:giant_spruce_taiga_hills"; -+ BIOMES_BY_ID[162] = "minecraft:modified_gravelly_mountains"; -+ BIOMES_BY_ID[163] = "minecraft:shattered_savanna"; -+ BIOMES_BY_ID[164] = "minecraft:shattered_savanna_plateau"; -+ BIOMES_BY_ID[165] = "minecraft:eroded_badlands"; -+ BIOMES_BY_ID[166] = "minecraft:modified_wooded_badlands_plateau"; -+ BIOMES_BY_ID[167] = "minecraft:modified_badlands_plateau"; -+ BIOMES_BY_ID[168] = "minecraft:bamboo_jungle"; -+ BIOMES_BY_ID[169] = "minecraft:bamboo_jungle_hills"; -+ BIOMES_BY_ID[170] = "minecraft:soul_sand_valley"; -+ BIOMES_BY_ID[171] = "minecraft:crimson_forest"; -+ BIOMES_BY_ID[172] = "minecraft:warped_forest"; -+ BIOMES_BY_ID[173] = "minecraft:basalt_deltas"; -+ BIOMES_BY_ID[174] = "minecraft:dripstone_caves"; -+ BIOMES_BY_ID[175] = "minecraft:lush_caves"; -+ BIOMES_BY_ID[177] = "minecraft:meadow"; -+ BIOMES_BY_ID[178] = "minecraft:grove"; -+ BIOMES_BY_ID[179] = "minecraft:snowy_slopes"; -+ BIOMES_BY_ID[180] = "minecraft:snowcapped_peaks"; -+ BIOMES_BY_ID[181] = "minecraft:lofty_peaks"; -+ BIOMES_BY_ID[182] = "minecraft:stony_peaks"; -+ } -+ -+ private static final String[] HEIGHTMAP_TYPES = new String[] { -+ "WORLD_SURFACE_WG", -+ "WORLD_SURFACE", -+ "WORLD_SURFACE_IGNORE_SNOW", -+ "OCEAN_FLOOR_WG", -+ "OCEAN_FLOOR", -+ "MOTION_BLOCKING", -+ "MOTION_BLOCKING_NO_LEAVES" -+ }; -+ -+ private static final Set STATUS_IS_OR_AFTER_SURFACE = new HashSet<>(Arrays.asList( -+ "surface", -+ "carvers", -+ "liquid_carvers", -+ "features", -+ "light", -+ "spawn", -+ "heightmaps", -+ "full" -+ )); -+ private static final Set STATUS_IS_OR_AFTER_NOISE = new HashSet<>(Arrays.asList( -+ "noise", -+ "surface", -+ "carvers", -+ "liquid_carvers", -+ "features", -+ "light", -+ "spawn", -+ "heightmaps", -+ "full" -+ )); -+ private static final Set BLOCKS_BEFORE_FEATURE_STATUS = new HashSet<>(Arrays.asList( -+ "minecraft:air", -+ "minecraft:basalt", -+ "minecraft:bedrock", -+ "minecraft:blackstone", -+ "minecraft:calcite", -+ "minecraft:cave_air", -+ "minecraft:coarse_dirt", -+ "minecraft:crimson_nylium", -+ "minecraft:dirt", -+ "minecraft:end_stone", -+ "minecraft:grass_block", -+ "minecraft:gravel", -+ "minecraft:ice", -+ "minecraft:lava", -+ "minecraft:mycelium", -+ "minecraft:nether_wart_block", -+ "minecraft:netherrack", -+ "minecraft:orange_terracotta", -+ "minecraft:packed_ice", -+ "minecraft:podzol", -+ "minecraft:powder_snow", -+ "minecraft:red_sand", -+ "minecraft:red_sandstone", -+ "minecraft:sand", -+ "minecraft:sandstone", -+ "minecraft:snow_block", -+ "minecraft:soul_sand", -+ "minecraft:soul_soil", -+ "minecraft:stone", -+ "minecraft:terracotta", -+ "minecraft:warped_nylium", -+ "minecraft:warped_wart_block", -+ "minecraft:water", -+ "minecraft:white_terracotta" -+ )); -+ -+ private static int getObjectsPerValue(final long[] val) { -+ return (4096 + val.length - 1) / (val.length); // expression is invalid if it returns > 64 -+ } -+ -+ private static long[] resize(final long[] val, final int oldBitsPerObject, final int newBitsPerObject) { -+ final long oldMask = (1L << oldBitsPerObject) - 1; // works even if bitsPerObject == 64 -+ final long newMask = (1L << newBitsPerObject) - 1; -+ final int oldObjectsPerValue = 64 / oldBitsPerObject; -+ final int newObjectsPerValue = 64 / newBitsPerObject; -+ -+ if (newBitsPerObject == oldBitsPerObject) { -+ return val; -+ } -+ -+ final int items = 4096; -+ -+ final long[] ret = new long[(items + newObjectsPerValue - 1) / newObjectsPerValue]; -+ -+ final int expectedSize = ((items + oldObjectsPerValue - 1) / oldObjectsPerValue); -+ if (val.length != expectedSize) { -+ throw new IllegalStateException("Expected size: " + expectedSize + ", got: " + val.length); -+ } -+ -+ int shift = 0; -+ int idx = 0; -+ long newCurr = 0L; -+ -+ int currItem = 0; -+ for (int i = 0; i < val.length; ++i) { -+ final long oldCurr = val[i]; -+ -+ for (int objIdx = 0; currItem < items && objIdx + oldBitsPerObject <= 64; objIdx += oldBitsPerObject, ++currItem) { -+ final long value = (oldCurr >> objIdx) & oldMask; -+ -+ if ((value & newMask) != value) { -+ throw new IllegalStateException("Old data storage has values that cannot be moved into new palette (would erase data)!"); -+ } -+ -+ newCurr |= value << shift; -+ shift += newBitsPerObject; -+ -+ if (shift + newBitsPerObject > 64) { // will next write overflow? -+ // must move to next idx -+ ret[idx++] = newCurr; -+ shift = 0; -+ newCurr = 0L; -+ } -+ } -+ } -+ -+ // don't forget to write the last one -+ if (shift != 0) { -+ ret[idx] = newCurr; -+ } -+ -+ return ret; -+ } -+ -+ private static void fixLithiumChunks(final MapType data) { -+ // See https://github.com/CaffeineMC/lithium-fabric/issues/279 -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return; -+ } -+ -+ final int chunkX = level.getInt("xPos"); -+ final int chunkZ = level.getInt("zPos"); -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ return; -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int sectionY = section.getInt("Y"); -+ -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ final long[] blockStates = section.getLongs("BlockStates"); -+ -+ if (palette == null || blockStates == null) { -+ continue; -+ } -+ -+ final int expectedBits = Math.max(4, ceilLog2(palette.size())); -+ final int gotObjectsPerValue = getObjectsPerValue(blockStates); -+ final int gotBits = 64 / gotObjectsPerValue; -+ -+ if (expectedBits == gotBits) { -+ continue; -+ } -+ -+ try { -+ section.setLongs("BlockStates", resize(blockStates, gotBits, expectedBits)); -+ } catch (final Exception ex) { -+ LOGGER.error("Failed to rewrite mismatched palette and data storage for section y: " + sectionY -+ + " for chunk [" + chunkX + "," + chunkZ + "], palette entries: " + palette.size() + ", data storage size: " -+ + blockStates.length, -+ ex -+ ); -+ } -+ } -+ } -+ -+ public static void register() { -+ // See V2551 for the layout of world gen settings -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // converters were added to older versions note whether the world has increased height already or not -+ final boolean noHeightFlag = !data.hasKey("has_increased_height_already"); -+ final boolean hasIncreasedHeight = data.getBoolean("has_increased_height_already", true); -+ data.remove("has_increased_height_already"); -+ -+ final MapType dimensions = data.getMap("dimensions"); -+ if (dimensions == null) { -+ // wat -+ return null; -+ } -+ -+ // only care about overworld -+ final MapType overworld = dimensions.getMap("minecraft:overworld"); -+ if (overworld == null) { -+ // wat -+ return null; -+ } -+ -+ final MapType generator = overworld.getMap("generator"); -+ if (generator == null) { -+ // wat -+ return null; -+ } -+ -+ final String type = generator.getString("type", ""); -+ switch (type) { -+ case "minecraft:noise": { -+ final MapType biomeSource = generator.getMap("biome_source"); -+ final String sourceType = biomeSource.getString("type"); -+ -+ boolean largeBiomes = false; -+ -+ if ("minecraft:vanilla_layered".equals(sourceType) || (noHeightFlag && "minecraft:multi_noise".equals(sourceType))) { -+ largeBiomes = biomeSource.getBoolean("large_biomes"); -+ -+ final MapType newBiomeSource = Types.NBT.createEmptyMap(); -+ generator.setMap("biome_source", newBiomeSource); -+ -+ newBiomeSource.setString("preset", "minecraft:overworld"); -+ newBiomeSource.setString("type", "minecraft:multi_noise"); -+ } -+ -+ if (largeBiomes) { -+ if ("minecraft:overworld".equals(generator.getString("settings"))) { -+ generator.setString("settings", "minecraft:large_biomes"); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:flat": { -+ if (!hasIncreasedHeight) { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ break; -+ } -+ -+ updateLayers(settings.getList("layers", ObjectType.MAP)); -+ } -+ break; -+ } -+ default: -+ // do nothing -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ -+ // It looks like DFU will only support worlds in the old height format or the new one, any custom one isn't supported -+ // and by not supported I mean it will just treat it as the old format... maybe at least throw in that case? -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // The below covers padPaletteEntries - this was written BEFORE that code was added to the datafixer - -+ // and this still works, so I'm keeping it. Don't fix what isn't broken. -+ fixLithiumChunks(data); // See https://github.com/CaffeineMC/lithium-fabric/issues/279 -+ -+ final MapType level = data.getMap("Level"); -+ -+ if (level == null) { -+ return null; -+ } -+ -+ final MapType context = data.getMap("__context"); // Passed through by ChunkStorage -+ final String dimension = context == null ? "" : context.getString("dimension", ""); -+ final String generator = context == null ? "" : context.getString("generator", ""); -+ final boolean isOverworld = "minecraft:overworld".equals(dimension); -+ final int minSection = isOverworld ? -4 : 0; -+ final MutableBoolean isAlreadyExtended = new MutableBoolean(); -+ -+ final MapType[] newBiomes = createBiomeSections(level, isOverworld, minSection, isAlreadyExtended); -+ final MapType wrappedEmptyBlockPalette = getEmptyBlockPalette(); -+ -+ ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections == null) { -+ level.setList("Sections", sections = Types.NBT.createEmptyList()); -+ } -+ -+ // must update sections for two things: -+ // 1. the biomes are now stored per section, so we must insert the biomes palette into each section (and create them if they don't exist) -+ // 2. each section must now have block states (or at least DFU is ensuring they do, but current code does not require) -+ V2841.SimplePaletteReader bottomSection = null; -+ final Set allBlocks = new HashSet<>(); -+ final IntOpenHashSet existingSections = new IntOpenHashSet(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int y = section.getInt("Y"); -+ final int sectionIndex = y - minSection; -+ -+ existingSections.add(y); -+ -+ // add in relevant biome section -+ if (sectionIndex >= 0 && sectionIndex < newBiomes.length) { -+ // exclude out of bounds sections (i.e the light sections above and below the world) -+ section.setMap("biomes", newBiomes[sectionIndex]); -+ } -+ -+ // update palette -+ final ListType palette = section.getList("Palette", ObjectType.MAP); -+ final long[] blockStates = section.getLongs("BlockStates"); -+ -+ section.remove("Palette"); -+ section.remove("BlockStates"); -+ -+ if (palette != null) { -+ for (int j = 0, len2 = palette.size(); j < len2; ++j) { -+ allBlocks.add(V2841.getBlockId(palette.getMap(j))); -+ } -+ } -+ -+ final MapType palettedContainer; -+ if (palette != null && blockStates != null) { -+ // only if both exist, same as DFU, same as legacy chunk loading code -+ section.setMap("block_states", palettedContainer = wrapPaletteOptimised(palette, blockStates)); -+ } else { -+ section.setMap("block_states", palettedContainer = wrappedEmptyBlockPalette.copy()); // must write a palette now, copy so that later edits do not edit them all -+ } -+ -+ if (section.getInt("Y", Integer.MAX_VALUE) == 0) { -+ bottomSection = new V2841.SimplePaletteReader(palettedContainer.getList("palette", ObjectType.MAP), palettedContainer.getLongs("data")); -+ } -+ } -+ -+ // all existing sections updated, now we must create new sections just for the biomes migration -+ for (int sectionIndex = 0; sectionIndex < newBiomes.length; ++sectionIndex) { -+ final int sectionY = sectionIndex + minSection; -+ if (!existingSections.add(sectionY)) { -+ // exists already -+ continue; -+ } -+ -+ final MapType newSection = Types.NBT.createEmptyMap(); -+ sections.addMap(newSection); -+ -+ newSection.setByte("Y", (byte)sectionY); -+ // must write a palette now, copy so that later edits do not edit them all -+ newSection.setMap("block_states", wrappedEmptyBlockPalette.copy()); -+ -+ newSection.setGeneric("biomes", newBiomes[sectionIndex]); -+ } -+ -+ // update status so interpolation can take place -+ predictChunkStatusBeforeSurface(level, allBlocks); -+ -+ // done with sections, update the rest of the chunk -+ updateChunkData(level, isOverworld, isAlreadyExtended.getValue(), "minecraft:noise".equals(generator), bottomSection); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final String type = generator.getString("type"); -+ if (type == null) { -+ continue; -+ } -+ -+ switch (type) { -+ case "minecraft:flat": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.BIOME, settings, "biome", fromVersion, toVersion); -+ -+ final ListType layers = settings.getList("layers", ObjectType.MAP); -+ if (layers != null) { -+ for (int i = 0, len = layers.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, layers.getMap(i), "block", fromVersion, toVersion); -+ } -+ } -+ -+ break; -+ } -+ case "minecraft:noise": { -+ final MapType settings = generator.getMap("settings"); -+ if (settings != null) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_block", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, settings, "default_fluid", fromVersion, toVersion); -+ } -+ -+ final MapType biomeSource = generator.getMap("biome_source"); -+ if (biomeSource != null) { -+ final String biomeSourceType = biomeSource.getString("type", ""); -+ switch (biomeSourceType) { -+ case "minecraft:fixed": { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomeSource, "biome", fromVersion, toVersion); -+ break; -+ } -+ -+ case "minecraft:multi_noise": { -+ // preset is absent, no type for namespaced string -+ -+ // Vanilla's schema is _still_ wrong. It should be DSL.fields("biomes", DSL.list(DSL.fields("biome"))) -+ // But it just contains the list part. That obviously can never be the case, because -+ // the root object is a compound, not a list. -+ -+ final ListType biomes = biomeSource.getList("biomes", ObjectType.MAP); -+ if (biomes != null) { -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BIOME, biomes.getMap(i), "biome", fromVersion, toVersion); -+ } -+ } -+ break; -+ } -+ -+ case "minecraft:checkerboard": { -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, biomeSource, "biomes", fromVersion, toVersion); -+ break; -+ } -+ } -+ } -+ -+ break; -+ } -+ } -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, level.getMap("Structures"), "Starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+ -+ private static void predictChunkStatusBeforeSurface(final MapType level, final Set chunkBlocks) { -+ final String status = level.getString("Status", "empty"); -+ if (STATUS_IS_OR_AFTER_SURFACE.contains(status)) { -+ return; -+ } -+ -+ chunkBlocks.remove("minecraft:air"); -+ final boolean chunkNotEmpty = !chunkBlocks.isEmpty(); -+ chunkBlocks.removeAll(BLOCKS_BEFORE_FEATURE_STATUS); -+ final boolean chunkFeatureStatus = !chunkBlocks.isEmpty(); -+ -+ final String update; -+ if (chunkFeatureStatus) { -+ update = "liquid_carvers"; -+ } else if (!"noise".equals(status) && !chunkNotEmpty) { -+ update = "biomes".equals(status) ? "structure_references" : status; -+ } else { -+ update = "noise"; -+ } -+ -+ level.setString("Status", update); -+ } -+ -+ private static MapType getEmptyBlockPalette() { -+ final MapType airBlockState = Types.NBT.createEmptyMap(); -+ airBlockState.setString("Name", "minecraft:air"); -+ -+ final ListType emptyBlockPalette = Types.NBT.createEmptyList(); -+ emptyBlockPalette.addMap(airBlockState); -+ -+ return V2832.wrapPalette(emptyBlockPalette); -+ } -+ -+ private static void shiftUpgradeData(final MapType upgradeData, final int shift) { -+ if (upgradeData == null) { -+ return; -+ } -+ -+ final MapType indices = upgradeData.getMap("Indices"); -+ if (indices == null) { -+ return; -+ } -+ -+ RenameHelper.renameKeys(indices, (final String input) -> { -+ return Integer.toString(Integer.parseInt(input) + shift); -+ }); -+ } -+ -+ private static void updateChunkData(final MapType level, final boolean wantExtendedHeight, final boolean isAlreadyExtended, -+ final boolean onNoiseGenerator, final V2841.SimplePaletteReader bottomSection) { -+ level.remove("Biomes"); -+ if (!wantExtendedHeight) { -+ padCarvingMasks(level, 16, 0); -+ return; -+ } -+ -+ if (isAlreadyExtended) { -+ padCarvingMasks(level, 24, 0); -+ return; -+ } -+ -+ offsetHeightmaps(level); -+ addEmptyListPadding(level, "Lights"); -+ addEmptyListPadding(level, "LiquidsToBeTicked"); -+ addEmptyListPadding(level, "PostProcessing"); -+ addEmptyListPadding(level, "ToBeTicked"); -+ shiftUpgradeData(level.getMap("UpgradeData"), 4); // https://bugs.mojang.com/browse/MC-238076 - fixed now, Mojang fix is identical. No change required. -+ padCarvingMasks(level, 24, 4); -+ -+ if (!onNoiseGenerator) { -+ return; -+ } -+ -+ final String status = level.getString("Status"); -+ if (status == null || "empty".equals(status)) { -+ return; -+ } -+ -+ final MapType blendingData = Types.NBT.createEmptyMap(); -+ level.setMap("blending_data", blendingData); -+ -+ blendingData.setBoolean("old_noise", STATUS_IS_OR_AFTER_NOISE.contains(status)); -+ -+ if (bottomSection == null) { -+ return; -+ } -+ -+ final BitSet missingBedrock = new BitSet(256); -+ boolean hasBedrock = status.equals("noise"); -+ -+ for (int z = 0; z <= 15; ++z) { -+ for (int x = 0; x <= 15; ++x) { -+ final MapType state = bottomSection.getState(x, 0, z); -+ final String blockId = V2841.getBlockId(state); -+ final boolean isBedrock = state != null && "minecraft:bedrock".equals(blockId); -+ final boolean isAir = state != null && "minecraft:air".equals(blockId); -+ if (isAir) { -+ missingBedrock.set((z << 4) | x); -+ } -+ -+ hasBedrock |= isBedrock; -+ } -+ } -+ -+ if (hasBedrock && missingBedrock.cardinality() != missingBedrock.size()) { -+ final String targetStatus = "full".equals(status) ? "heightmaps" : status; -+ -+ final MapType belowZeroRetrogen = Types.NBT.createEmptyMap(); -+ level.setMap("below_zero_retrogen", belowZeroRetrogen); -+ -+ belowZeroRetrogen.setString("target_status", targetStatus); -+ belowZeroRetrogen.setLongs("missing_bedrock", missingBedrock.toLongArray()); -+ -+ level.setString("Status", "empty"); -+ } -+ -+ level.setBoolean("isLightOn", false); -+ } -+ -+ private static void padCarvingMasks(final MapType level, final int newSize, final int offset) { -+ final MapType carvingMasks = level.getMap("CarvingMasks"); -+ if (carvingMasks == null) { -+ // if empty, DFU still writes -+ level.setMap("CarvingMasks", Types.NBT.createEmptyMap()); -+ return; -+ } -+ -+ for (final String key : carvingMasks.keys()) { -+ final long[] old = BitSet.valueOf(carvingMasks.getBytes(key)).toLongArray(); -+ final long[] newVal = new long[64 * newSize]; -+ -+ System.arraycopy(old, 0, newVal, 64 * offset, old.length); -+ -+ carvingMasks.setLongs(key, newVal); // no CME: key exists already -+ } -+ } -+ -+ private static void addEmptyListPadding(final MapType level, final String path) { -+ ListType list = level.getListUnchecked(path); -+ if (list != null && list.size() == 24) { -+ return; -+ } -+ -+ if (list == null) { -+ // difference from DFU: Don't create the damn thing! -+ return; -+ } -+ -+ -+ // offset the section array to the new format -+ for (int i = 0; i < 4; ++i) { -+ // always create new copies, so that modifying one doesn't modify ALL of them! -+ list.addList(0, Types.NBT.createEmptyList()); // add below -+ list.addList(Types.NBT.createEmptyList()); // add above -+ } -+ } -+ -+ private static void offsetHeightmaps(final MapType level) { -+ final MapType heightmaps = level.getMap("Heightmaps"); -+ if (heightmaps == null) { -+ return; -+ } -+ -+ for (final String key : HEIGHTMAP_TYPES) { -+ offsetHeightmap(heightmaps.getLongs(key)); -+ } -+ } -+ -+ private static void offsetHeightmap(final long[] heightmap) { -+ if (heightmap == null) { -+ return; -+ } -+ -+ // heightmaps are configured to have 9 bits per value, with 256 total values -+ // heightmaps are also relative to the lowest position -+ for (int idx = 0, len = heightmap.length; idx < len; ++idx) { -+ long curr = heightmap[idx]; -+ long next = 0L; -+ -+ for (int objIdx = 0; objIdx + 9 <= 64; objIdx += 9) { -+ final long value = (curr >> objIdx) & 511L; -+ if (value != 0L) { -+ final long offset = Math.min(511L, value + 64L); -+ -+ next |= (offset << objIdx); -+ } -+ } -+ -+ heightmap[idx] = next; -+ } -+ } -+ -+ private static MapType[] createBiomeSections(final MapType level, final boolean wantExtendedHeight, -+ final int minSection, final MutableBoolean isAlreadyExtended) { -+ final MapType[] ret = new MapType[wantExtendedHeight ? 24 : 16]; -+ -+ final int[] biomes = level.getInts("Biomes"); -+ -+ if (biomes != null && biomes.length == 1536) { // magic value for 24 sections of biomes (24 * 4^3) -+ isAlreadyExtended.setValue(true); -+ for (int sectionIndex = 0; sectionIndex < 24; ++sectionIndex) { -+ ret[sectionIndex] = createBiomeSection(biomes, sectionIndex * 64, -1); // -1 is all 1s -+ } -+ } else if (biomes != null && biomes.length == 1024) { // magic value for 24 sections of biomes (16 * 4^3) -+ for (int sectionY = 0; sectionY < 16; ++sectionY) { -+ ret[sectionY - minSection] = createBiomeSection(biomes, sectionY * 64, -1); // -1 is all 1s -+ } -+ -+ if (wantExtendedHeight) { -+ // must set the new sections at top and bottom -+ final MapType bottomCopy = createBiomeSection(biomes, 0, 15); // just want the biomes at y = 0 -+ final MapType topCopy = createBiomeSection(biomes, 1008, 15); // just want the biomes at y = 252 -+ -+ for (int sectionIndex = 0; sectionIndex < 4; ++sectionIndex) { -+ ret[sectionIndex] = bottomCopy.copy(); // copy palette so that later possible modifications don't trash all sections -+ } -+ -+ for (int sectionIndex = 20; sectionIndex < 24; ++sectionIndex) { -+ ret[sectionIndex] = topCopy.copy(); // copy palette so that later possible modifications don't trash all sections -+ } -+ } -+ } else { -+ final ListType palette = Types.NBT.createEmptyList(); -+ palette.addString("minecraft:plains"); -+ -+ for (int i = 0; i < ret.length; ++i) { -+ ret[i] = wrapPalette(palette.copy()); // copy palette so that later possible modifications don't trash all sections -+ } -+ } -+ -+ return ret; -+ } -+ -+ private static MapType createBiomeSection(final int[] biomes, final int offset, final int mask) { -+ final Int2IntLinkedOpenHashMap paletteId = new Int2IntLinkedOpenHashMap(); -+ -+ for (int idx = 0; idx < 64; ++idx) { -+ final int biome = biomes[offset + (idx & mask)]; -+ paletteId.putIfAbsent(biome, paletteId.size()); -+ } -+ -+ final ListType paletteString = Types.NBT.createEmptyList(); -+ for (final IntIterator iterator = paletteId.keySet().iterator(); iterator.hasNext();) { -+ final int biomeId = iterator.nextInt(); -+ final String biome = biomeId >= 0 && biomeId < BIOMES_BY_ID.length ? BIOMES_BY_ID[biomeId] : null; -+ paletteString.addString(biome == null ? "minecraft:plains" : biome); -+ } -+ -+ final int bitsPerObject = ceilLog2(paletteString.size()); -+ if (bitsPerObject == 0) { -+ return wrapPalette(paletteString); -+ } -+ -+ // manually create packed integer data -+ final int objectsPerValue = 64 / bitsPerObject; -+ final long[] packed = new long[(64 + objectsPerValue - 1) / objectsPerValue]; -+ -+ int shift = 0; -+ int idx = 0; -+ long curr = 0; -+ -+ for (int biome_idx = 0; biome_idx < 64; ++biome_idx) { -+ final int biome = biomes[offset + (biome_idx & mask)]; -+ -+ curr |= ((long)paletteId.get(biome)) << shift; -+ -+ shift += bitsPerObject; -+ -+ if (shift + bitsPerObject > 64) { // will next write overflow? -+ // must move to next idx -+ packed[idx++] = curr; -+ shift = 0; -+ curr = 0L; -+ } -+ } -+ -+ // don't forget to write the last one -+ if (shift != 0) { -+ packed[idx] = curr; -+ } -+ -+ return wrapPalette(paletteString, packed); -+ } -+ -+ private static MapType wrapPalette(final ListType palette) { -+ return wrapPalette(palette, null); -+ } -+ -+ private static MapType wrapPalette(final ListType palette, final long[] blockStates) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setList("palette", palette); -+ if (blockStates != null) { -+ ret.setLongs("data", blockStates); -+ } -+ -+ return ret; -+ } -+ -+ private static MapType wrapPaletteOptimised(final ListType palette, final long[] blockStates) { -+ if (palette.size() == 1) { -+ return wrapPalette(palette); -+ } -+ -+ return wrapPalette(palette, blockStates); -+ } -+ -+ public static int ceilLog2(final int value) { -+ return value == 0 ? 0 : Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ private static void updateLayers(final ListType layers) { -+ if (layers == null) { -+ return; -+ } -+ -+ layers.addMap(0, createEmptyLayer()); // add at the bottom -+ } -+ -+ private static MapType createEmptyLayer() { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ ret.setInt("height", 64); -+ ret.setString("block", "minecraft:air"); -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4bdac86810c51e9f87ea82ba9f6c6d8ae8ce2bdf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2833.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2833 { -+ -+ protected static final int VERSION = MCVersions.V1_17_1 + 103; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ for (final String dimensionKey : dimensions.keys()) { -+ final MapType dimension = dimensions.getMap(dimensionKey); -+ if (!dimension.hasKey("type")) { -+ throw new IllegalStateException("Unable load old custom worlds."); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java -new file mode 100644 -index 0000000000000000000000000000000000000000..586e711163e2bdea110442dd181289fc06f6f7f1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2838.java -@@ -0,0 +1,56 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2838 { -+ -+ protected static final int VERSION = MCVersions.V21W40A; -+ -+ public static final ImmutableMap BIOME_UPDATE = ImmutableMap.builder() -+ .put("minecraft:badlands_plateau", "minecraft:badlands") -+ .put("minecraft:bamboo_jungle_hills", "minecraft:bamboo_jungle") -+ .put("minecraft:birch_forest_hills", "minecraft:birch_forest") -+ .put("minecraft:dark_forest_hills", "minecraft:dark_forest") -+ .put("minecraft:desert_hills", "minecraft:desert") -+ .put("minecraft:desert_lakes", "minecraft:desert") -+ .put("minecraft:giant_spruce_taiga_hills", "minecraft:old_growth_spruce_taiga") -+ .put("minecraft:giant_spruce_taiga", "minecraft:old_growth_spruce_taiga") -+ .put("minecraft:giant_tree_taiga_hills", "minecraft:old_growth_pine_taiga") -+ .put("minecraft:giant_tree_taiga", "minecraft:old_growth_pine_taiga") -+ .put("minecraft:gravelly_mountains", "minecraft:windswept_gravelly_hills") -+ .put("minecraft:jungle_edge", "minecraft:sparse_jungle") -+ .put("minecraft:jungle_hills", "minecraft:jungle") -+ .put("minecraft:modified_badlands_plateau", "minecraft:badlands") -+ .put("minecraft:modified_gravelly_mountains", "minecraft:windswept_gravelly_hills") -+ .put("minecraft:modified_jungle_edge", "minecraft:sparse_jungle") -+ .put("minecraft:modified_jungle", "minecraft:jungle") -+ .put("minecraft:modified_wooded_badlands_plateau", "minecraft:wooded_badlands") -+ .put("minecraft:mountain_edge", "minecraft:windswept_hills") -+ .put("minecraft:mountains", "minecraft:windswept_hills") -+ .put("minecraft:mushroom_field_shore", "minecraft:mushroom_fields") -+ .put("minecraft:shattered_savanna", "minecraft:windswept_savanna") -+ .put("minecraft:shattered_savanna_plateau", "minecraft:windswept_savanna") -+ .put("minecraft:snowy_mountains", "minecraft:snowy_plains") -+ .put("minecraft:snowy_taiga_hills", "minecraft:snowy_taiga") -+ .put("minecraft:snowy_taiga_mountains", "minecraft:snowy_taiga") -+ .put("minecraft:snowy_tundra", "minecraft:snowy_plains") -+ .put("minecraft:stone_shore", "minecraft:stony_shore") -+ .put("minecraft:swamp_hills", "minecraft:swamp") -+ .put("minecraft:taiga_hills", "minecraft:taiga") -+ .put("minecraft:taiga_mountains", "minecraft:taiga") -+ .put("minecraft:tall_birch_forest", "minecraft:old_growth_birch_forest") -+ .put("minecraft:tall_birch_hills", "minecraft:old_growth_birch_forest") -+ .put("minecraft:wooded_badlands_plateau", "minecraft:wooded_badlands") -+ .put("minecraft:wooded_hills", "minecraft:forest") -+ .put("minecraft:wooded_mountains", "minecraft:windswept_forest") -+ .put("minecraft:lofty_peaks", "minecraft:jagged_peaks") -+ .put("minecraft:snowcapped_peaks", "minecraft:frozen_peaks") -+ .build(); -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, BIOME_UPDATE::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java -new file mode 100644 -index 0000000000000000000000000000000000000000..41b41ff084662bbc2e323713473e4e13b8e50cd7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2841.java -@@ -0,0 +1,205 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import ca.spottedleaf.dataconverter.util.IntegerUtil; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V2841 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 1; -+ -+ protected static final Set ALWAYS_WATERLOGGED = new HashSet<>(Arrays.asList( -+ "minecraft:bubble_column", -+ "minecraft:kelp", -+ "minecraft:kelp_plant", -+ "minecraft:seagrass", -+ "minecraft:tall_seagrass" -+ )); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType level = root.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ { -+ // Why it's renamed here and not the next data version is beyond me. -+ final MapType liquidTicks = level.getMap("LiquidTicks"); -+ if (liquidTicks != null) { -+ level.remove("LiquidTicks"); -+ level.setMap("fluid_ticks", liquidTicks); -+ } -+ } -+ -+ final Int2ObjectOpenHashMap sectionBlocks = new Int2ObjectOpenHashMap<>(); -+ final ListType sections = level.getList("Sections", ObjectType.MAP); -+ int minSection = 0; // TODO wtf is this -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final int sectionY = section.getInt("Y"); -+ if (sectionY < minSection && section.hasKey("biomes")) { -+ minSection = sectionY; -+ } -+ -+ final MapType blockStates = section.getMap("block_states"); -+ if (blockStates == null) { -+ continue; -+ } -+ -+ sectionBlocks.put(sectionY, new SimplePaletteReader(section.getList("palette", ObjectType.MAP), section.getLongs("data"))); -+ } -+ } -+ -+ level.setByte("yPos", (byte)minSection); // TODO ??????????????????????????????????????? -+ -+ if (level.hasKey("fluid_ticks") || level.hasKey("TileTicks")) { -+ return null; -+ } -+ -+ final int sectionX = level.getInt("xPos"); -+ final int sectionZ = level.getInt("zPos"); -+ -+ final ListType fluidTicks = level.getList("LiquidsToBeTicked", ObjectType.LIST); -+ final ListType blockTicks = level.getList("ToBeTicked", ObjectType.LIST); -+ level.remove("LiquidsToBeTicked"); -+ level.remove("ToBeTicked"); -+ -+ level.setList("fluid_ticks", migrateTickList(fluidTicks, false, sectionBlocks, sectionX, minSection, sectionZ)); -+ level.setList("TileTicks", migrateTickList(blockTicks, true, sectionBlocks, sectionX, minSection, sectionZ)); -+ -+ return null; -+ } -+ }); -+ } -+ -+ public static ListType migrateTickList(final ListType ticks, final boolean blockTicks, final Int2ObjectOpenHashMap sectionBlocks, -+ final int sectionX, final int minSection, final int sectionZ) { -+ final ListType ret = Types.NBT.createEmptyList(); -+ -+ if (ticks == null) { -+ return ret; -+ } -+ -+ for (int sectionIndex = 0, totalSections = ticks.size(); sectionIndex < totalSections; ++sectionIndex) { -+ final int sectionY = sectionIndex + minSection; -+ final ListType sectionTicks = ticks.getList(sectionIndex); -+ final SimplePaletteReader palette = sectionBlocks.get(sectionY); -+ -+ for (int i = 0, len = sectionTicks.size(); i < len; ++i) { -+ final int localIndex = sectionTicks.getShort(i) & 0xFFFF; -+ final MapType blockState = palette == null ? null : palette.getState(localIndex); -+ final String subjectId = blockTicks ? getBlockId(blockState) : getLiquidId(blockState); -+ -+ ret.addMap(createNewTick(subjectId, localIndex, sectionX, sectionY, sectionZ)); -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static MapType createNewTick(final String subjectId, final int localIndex, final int sectionX, final int sectionY, final int sectionZ) { -+ final int newX = (localIndex & 15) + (sectionX << 4); -+ final int newZ = ((localIndex >> 4) & 15) + (sectionZ << 4); -+ final int newY = ((localIndex >> 8) & 15) + (sectionY << 4); -+ -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setString("i", subjectId); -+ ret.setInt("x", newX); -+ ret.setInt("y", newY); -+ ret.setInt("z", newZ); -+ ret.setInt("t", 0); -+ ret.setInt("p", 0); -+ -+ return ret; -+ } -+ -+ public static String getBlockId(final MapType blockState) { -+ return blockState == null ? "minecraft:air" : blockState.getString("Name", "minecraft:air"); -+ } -+ -+ private static String getLiquidId(final MapType blockState) { -+ if (blockState == null) { -+ return "minecraft:empty"; -+ } -+ -+ final String name = blockState.getString("Name"); -+ if (ALWAYS_WATERLOGGED.contains(name)) { -+ return "minecraft:water"; -+ } -+ -+ final MapType properties = blockState.getMap("Properties"); -+ if ("minecraft:water".equals(name)) { -+ return properties != null && properties.getInt("level") == 0 ? "minecraft:water" : "minecraft:flowing_water"; -+ } else if ("minecraft:lava".equals(name)) { -+ return properties != null && properties.getInt("level") == 0 ? "minecraft:lava" : "minecraft:flowing_lava"; -+ } -+ -+ return (properties != null && properties.getBoolean("waterlogged")) ? "minecraft:water" : "minecraft:empty"; -+ } -+ -+ public static final class SimplePaletteReader { -+ -+ public final ListType palette; -+ public final long[] data; -+ private final int bitsPerValue; -+ private final long mask; -+ private final int valuesPerLong; -+ -+ public SimplePaletteReader(final ListType palette, final long[] data) { -+ this.palette = palette == null ? null : (palette.size() == 0 ? null : palette); -+ this.data = data; -+ this.bitsPerValue = Math.max(4, IntegerUtil.ceilLog2(this.palette == null ? 0 : this.palette.size())); -+ this.mask = (1L << this.bitsPerValue) - 1L; -+ this.valuesPerLong = (int)(64L / this.bitsPerValue); -+ } -+ -+ public MapType getState(final int x, final int y, final int z) { -+ final int index = x | (z << 4) | (y << 8); -+ return this.getState(index); -+ } -+ -+ public MapType getState(final int index) { -+ final ListType palette = this.palette; -+ if (palette == null) { -+ return null; -+ } -+ -+ final int paletteSize = palette.size(); -+ if (paletteSize == 1) { -+ return palette.getMap(0); -+ } -+ -+ // x86 div computes mod. no loss here using mod -+ // if needed, can compute magic mul and shift for div values using IntegerUtil -+ final int dataIndex = index / this.valuesPerLong; -+ final int localIndex = (index % this.valuesPerLong) * this.bitsPerValue; -+ final long[] data = this.data; -+ if (dataIndex < 0 || dataIndex >= data.length) { -+ return null; -+ } -+ -+ final long value = data[dataIndex]; -+ final int paletteIndex = (int)((value >>> localIndex) & this.mask); -+ if (paletteIndex < 0 || paletteIndex >= paletteSize) { -+ return null; -+ } -+ -+ return palette.getMap(paletteIndex); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f06e24bb87baf01b1386fb7a6af1ea04f4d6f2ef ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2842.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V2842 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType root, final long sourceVersion, final long toVersion) { -+ final MapType level = root.getMap("Level"); -+ root.remove("Level"); -+ -+ if (!root.isEmpty()) { -+ for (final String key : root.keys()) { -+ if (level.hasKey(key)) { -+ // Don't clobber level's data -+ continue; -+ } -+ level.setGeneric(key, root.getGeneric(key)); -+ } -+ } -+ -+ // Rename top level first -+ RenameHelper.renameSingle(level, "TileEntities", "block_entities"); -+ RenameHelper.renameSingle(level, "TileTicks", "block_ticks"); -+ RenameHelper.renameSingle(level, "Entities", "entities"); -+ RenameHelper.renameSingle(level, "Sections", "sections"); -+ RenameHelper.renameSingle(level, "Structures", "structures"); -+ -+ // 2nd level -+ final MapType structures = level.getMap("structures"); -+ if (structures != null) { -+ RenameHelper.renameSingle(structures, "Starts", "starts"); -+ } -+ -+ return level; // Level is now root tag. -+ } -+ }); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); -+ -+ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); -+ if (blockTicks != null) { -+ for (int i = 0, len = blockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6333eabb123f13495f7a828fe79c204aff9dcedd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2843.java -@@ -0,0 +1,105 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import java.util.Map; -+ -+public final class V2843 { -+ -+ protected static final int VERSION = MCVersions.V21W42A + 3; -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.BIOME, Map.of("minecraft:deep_warm_ocean", "minecraft:warm_ocean")::get); -+ -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ private static void moveOutOfBoundTicks(final ListType ticks, final MapType chunkRoot, final int chunkX, final int chunkZ, final String intoKey) { -+ if (ticks == null) { -+ return; -+ } -+ -+ ListType outOfBounds = null; -+ for (int i = 0, len = ticks.size(); i < len; ++i) { -+ final MapType tick = ticks.getMap(i); -+ final int x = tick.getInt("x"); -+ final int z = tick.getInt("z"); -+ // anything > 1 is lost, anything less than 1 stays. -+ if (Math.max(Math.abs(chunkX - (x >> 4)), Math.abs(chunkZ - (z >> 4))) == 1) { -+ // DFU doesn't remove, so neither do we. -+ if (outOfBounds == null) { -+ outOfBounds = Types.NBT.createEmptyList(); -+ } -+ outOfBounds.addMap(tick); -+ } -+ } -+ -+ if (outOfBounds != null) { -+ MapType upgradeData = chunkRoot.getMap("UpgradeData"); -+ if (upgradeData == null) { -+ chunkRoot.setMap("UpgradeData", upgradeData = Types.NBT.createEmptyMap()); -+ } -+ upgradeData.setList(intoKey, outOfBounds); -+ } -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ // After renames, so use new names -+ final int x = data.getInt("xPos"); -+ final int z = data.getInt("zPos"); -+ -+ moveOutOfBoundTicks(data.getList("block_ticks", ObjectType.MAP), data, x, z, "neighbor_block_ticks"); -+ moveOutOfBoundTicks(data.getList("fluid_ticks", ObjectType.MAP), data, x, z, "neighbor_fluid_ticks"); -+ -+ return null; -+ } -+ }); -+ -+ // DFU is missing schema for UpgradeData block names -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, data, "block_entities", fromVersion, toVersion); -+ -+ final ListType blockTicks = data.getList("block_ticks", ObjectType.MAP); -+ if (blockTicks != null) { -+ for (int i = 0, len = blockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, blockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ -+ final MapType upgradeData = data.getMap("UpgradeData"); -+ if (upgradeData != null) { -+ // Even though UpgradeData will retrieve the block from the World when the type no longer exists, -+ // the type from the world may not match what was actually queued. So, even though it may look like we -+ // can skip the walker here, we actually don't if we want to be thorough. -+ final ListType neighbourBlockTicks = upgradeData.getList("neighbor_block_ticks", ObjectType.MAP); -+ if (neighbourBlockTicks != null) { -+ for (int i = 0, len = neighbourBlockTicks.size(); i < len; ++i) { -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, neighbourBlockTicks.getMap(i), "i", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections != null) { -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ WalkerUtils.convertList(MCTypeRegistry.BIOME, section.getMap("biomes"), "palette", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, section.getMap("block_states"), "palette", fromVersion, toVersion); -+ } -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data.getMap("structures"), "starts", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java -new file mode 100644 -index 0000000000000000000000000000000000000000..236327249d2b95b799b90172d457601167492249 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2846.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterAbstractAdvancementsRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V2846 { -+ -+ protected static final int VERSION = MCVersions.V21W44A + 1; -+ -+ public static void register() { -+ ConverterAbstractAdvancementsRename.register(VERSION, ImmutableMap.of( -+ "minecraft:husbandry/play_jukebox_in_meadows", "minecraft:adventure/play_jukebox_in_meadows", -+ "minecraft:adventure/caves_and_cliff", "minecraft:adventure/fall_from_world_height", -+ "minecraft:adventure/ride_strider_in_overworld_lava", "minecraft:nether/ride_strider_in_overworld_lava" -+ )::get); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94ab7be8c34d2ebb557df5a0864130f7f12c2185 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2852.java -@@ -0,0 +1,29 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2852 { -+ -+ protected static final int VERSION = MCVersions.V1_18_PRE5 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ for (final String dimensionKey : dimensions.keys()) { -+ final MapType dimension = dimensions.getMap(dimensionKey); -+ if (!dimension.hasKey("type")) { -+ throw new IllegalStateException("Unable load old custom worlds."); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7af7bf450080f65b8b7d7a8d2f941846c029e504 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2967.java -@@ -0,0 +1,56 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V2967 { -+ -+ protected static final int VERSION = MCVersions.V22W05A; -+ -+ public static void register() { -+ MCTypeRegistry.WORLD_GEN_SETTINGS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType dimensions = data.getMap("dimensions"); -+ -+ if (dimensions == null) { -+ return null; -+ } -+ -+ for (final String dimension : dimensions.keys()) { -+ final MapType dimensionData = dimensions.getMap(dimension); -+ if (dimensionData == null) { -+ continue; -+ } -+ -+ final MapType generator = dimensionData.getMap("generator"); -+ if (generator == null) { -+ continue; -+ } -+ -+ final MapType settings = generator.getMap("settings"); -+ if (settings == null) { -+ continue; -+ } -+ -+ final MapType structures = settings.getMap("structures"); -+ if (structures == null) { -+ continue; -+ } -+ -+ for (final String structureKey : structures.keys()) { -+ structures.getMap(structureKey).setString("type", "minecraft:random_spread"); -+ } -+ -+ final MapType stronghold = structures.getMap("stronghold"); -+ stronghold.setString("type", "minecraft:concentric_rings"); -+ structures.setMap("minecraft:stronghold", stronghold.copy()); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fa824cdf629caec745eff7c09eb4570c62263752 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V2970.java -@@ -0,0 +1,192 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.common.collect.ImmutableMap; -+import it.unimi.dsi.fastutil.objects.Object2IntMap; -+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -+import java.util.HashMap; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+ -+public final class V2970 { -+ -+ protected static final int VERSION = MCVersions.V22W07A + 1; -+ private static final Map CONVERSION_MAP = new HashMap<>( -+ ImmutableMap.builder() -+ .put("mineshaft", BiomeRemap.create(Map.of(List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands"), "minecraft:mineshaft_mesa"), "minecraft:mineshaft")) -+ .put("shipwreck", BiomeRemap.create(Map.of(List.of("minecraft:beach", "minecraft:snowy_beach"), "minecraft:shipwreck_beached"), "minecraft:shipwreck")) -+ .put("ocean_ruin", BiomeRemap.create(Map.of(List.of("minecraft:warm_ocean", "minecraft:lukewarm_ocean", "minecraft:deep_lukewarm_ocean"), "minecraft:ocean_ruin_warm"), "minecraft:ocean_ruin_cold")) -+ .put("village", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:village_desert", List.of("minecraft:savanna"), "minecraft:village_savanna", List.of("minecraft:snowy_plains"), "minecraft:village_snowy", List.of("minecraft:taiga"), "minecraft:village_taiga"), "minecraft:village_plains")) -+ .put("ruined_portal", BiomeRemap.create(Map.of(List.of("minecraft:desert"), "minecraft:ruined_portal_desert", List.of("minecraft:badlands", "minecraft:eroded_badlands", "minecraft:wooded_badlands", "minecraft:windswept_hills", "minecraft:windswept_forest", "minecraft:windswept_gravelly_hills", "minecraft:savanna_plateau", "minecraft:windswept_savanna", "minecraft:stony_shore", "minecraft:meadow", "minecraft:frozen_peaks", "minecraft:jagged_peaks", "minecraft:stony_peaks", "minecraft:snowy_slopes"), "minecraft:ruined_portal_mountain", List.of("minecraft:bamboo_jungle", "minecraft:jungle", "minecraft:sparse_jungle"), "minecraft:ruined_portal_jungle", List.of("minecraft:deep_frozen_ocean", "minecraft:deep_cold_ocean", "minecraft:deep_ocean", "minecraft:deep_lukewarm_ocean", "minecraft:frozen_ocean", "minecraft:ocean", "minecraft:cold_ocean", "minecraft:lukewarm_ocean", "minecraft:warm_ocean"), "minecraft:ruined_portal_ocean"), "minecraft:ruined_portal")) // Fix MC-248814, ruined_portal_standard->ruined_portal -+ .put("pillager_outpost", BiomeRemap.create("minecraft:pillager_outpost")) -+ .put("mansion", BiomeRemap.create("minecraft:mansion")) -+ .put("jungle_pyramid", BiomeRemap.create("minecraft:jungle_pyramid")) -+ .put("desert_pyramid", BiomeRemap.create("minecraft:desert_pyramid")) -+ .put("igloo", BiomeRemap.create("minecraft:igloo")) -+ .put("swamp_hut", BiomeRemap.create("minecraft:swamp_hut")) -+ .put("stronghold", BiomeRemap.create("minecraft:stronghold")) -+ .put("monument", BiomeRemap.create("minecraft:monument")) -+ .put("fortress", BiomeRemap.create("minecraft:fortress")) -+ .put("endcity", BiomeRemap.create("minecraft:end_city")) -+ .put("buried_treasure", BiomeRemap.create("minecraft:buried_treasure")) -+ .put("nether_fossil", BiomeRemap.create("minecraft:nether_fossil")) -+ .put("bastion_remnant", BiomeRemap.create("minecraft:bastion_remnant")) -+ .build() -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ private static Object2IntOpenHashMap countBiomes(final MapType chunk) { -+ final ListType sections = chunk.getList("sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ final Object2IntOpenHashMap ret = new Object2IntOpenHashMap<>(); -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ -+ final MapType biomes = section.getMap("biomes"); -+ -+ if (biomes == null) { -+ continue; -+ } -+ -+ final ListType palette = biomes.getList("palette", ObjectType.STRING); -+ -+ if (palette == null) { -+ continue; -+ } -+ -+ for (int k = 0, len2 = palette.size(); k < len2; ++k) { -+ ret.addTo(palette.getString(k), 1); -+ } -+ } -+ -+ return ret; -+ } -+ -+ private static String getStructureConverted(String id, final Object2IntOpenHashMap biomeCount) { -+ id = id.toLowerCase(Locale.ROOT); -+ final BiomeRemap remap = CONVERSION_MAP.get(id); -+ if (remap == null) { -+ throw new IllegalArgumentException("Unknown structure " + id); -+ } -+ if (remap.biomeToNewStructure == null || biomeCount == null) { -+ return remap.dfl; -+ } -+ -+ final Object2IntOpenHashMap remapCount = new Object2IntOpenHashMap<>(); -+ -+ for (final Iterator> iterator = biomeCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ final Object2IntMap.Entry entry = iterator.next(); -+ final String remappedStructure = remap.biomeToNewStructure.get(entry.getKey()); -+ if (remappedStructure != null) { -+ remapCount.addTo(remappedStructure, entry.getIntValue()); -+ } -+ } -+ -+ String converted = remap.dfl; -+ int maxCount = 0; -+ -+ for (final Iterator> iterator = remapCount.object2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ final Object2IntMap.Entry entry = iterator.next(); -+ final int count = entry.getIntValue(); -+ if (count > maxCount) { -+ maxCount = count; -+ converted = entry.getKey(); -+ } -+ } -+ -+ return converted; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType structures = data.getMap("structures"); -+ if (structures == null || structures.isEmpty()) { -+ return null; -+ } -+ -+ final Object2IntOpenHashMap biomeCounts = countBiomes(data); -+ -+ final MapType starts = structures.getMap("starts"); -+ final MapType references = structures.getMap("References"); -+ -+ if (starts != null) { -+ final MapType newStarts = Types.NBT.createEmptyMap(); -+ structures.setMap("starts", newStarts); -+ -+ for (final String key : starts.keys()) { -+ final MapType value = starts.getMap(key); -+ if ("INVALID".equals(value.getString("id", "INVALID"))) { -+ continue; -+ } -+ -+ final String remapped = getStructureConverted(key, biomeCounts); -+ value.setString("id", remapped); -+ newStarts.setMap(remapped, value); -+ } -+ } -+ -+ // This TRULY is a guess, no idea what biomes the referent has. -+ if (references != null) { -+ final MapType newReferences = Types.NBT.createEmptyMap(); -+ structures.setMap("References", newReferences); -+ for (final String key : references.keys()) { -+ final long[] value = references.getLongs(key); -+ if (value.length == 0) { -+ continue; -+ } -+ -+ newReferences.setLongs(getStructureConverted(key, biomeCounts), value); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private static final class BiomeRemap { -+ -+ public final Map biomeToNewStructure; -+ public final String dfl; -+ -+ private BiomeRemap(final Map biomeToNewStructure, final String dfl) { -+ this.biomeToNewStructure = biomeToNewStructure; -+ this.dfl = dfl; -+ } -+ -+ public static BiomeRemap create(final String newId) { -+ return new BiomeRemap(null, newId); -+ } -+ -+ public static BiomeRemap create(final Map, String> biomeMap, final String newId) { -+ final Map biomeToNewStructure = new HashMap<>(); -+ -+ for (final Map.Entry, String> entry : biomeMap.entrySet()) { -+ final List biomes = entry.getKey(); -+ final String newBiomeStructure = entry.getValue(); -+ -+ for (int i = 0, len = biomes.size(); i < len; ++i) { -+ final String biome = biomes.get(i); -+ if (biomeToNewStructure.putIfAbsent(biome, newBiomeStructure) != null) { -+ throw new IllegalStateException("Duplicate biome remap: " + biome + " -> " + newBiomeStructure + ", but already mapped to " + biomeToNewStructure.get(biome)); -+ } -+ } -+ } -+ -+ return new BiomeRemap(biomeToNewStructure, newId); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java -new file mode 100644 -index 0000000000000000000000000000000000000000..97da66165f3e3788af0dfe667509ca7edb15b0a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3077.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+ -+public final class V3077 { -+ -+ protected static final int VERSION = MCVersions.V1_18_2 + 102; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final boolean isLightOn = data.getBoolean("isLightOn"); -+ if (isLightOn) { -+ return null; -+ } -+ -+ final ListType sections = data.getList("sections", ObjectType.MAP); -+ if (sections == null) { -+ return null; -+ } -+ -+ for (int i = 0, len = sections.size(); i < len; ++i) { -+ final MapType section = sections.getMap(i); -+ section.remove("BlockLight"); -+ section.remove("SkyLight"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4271eae27756eb5cbad77679dd562e676d644748 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3078.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3078 { -+ -+ protected static final int VERSION = MCVersions.V1_18_2 + 103; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:frog"); -+ registerMob("minecraft:tadpole"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:sculk_shrieker", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0cd7cd8c2656713b97f83b7e02b65008b62c297 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3081.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3081 { -+ -+ protected static final int VERSION = MCVersions.V22W11A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:warden"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:warden", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ab6ebf4d10842d20c20bcbcc76483d9cfe081862 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3082.java -@@ -0,0 +1,14 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3082 { -+ -+ protected static final int VERSION = MCVersions.V22W11A + 2; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_boat", new DataWalkerItemLists("Items")); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0b133d6a806d571b976d8e96b9ca1e3b6933af20 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3083.java -@@ -0,0 +1,20 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.game_event.GameEventListenerWalker; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V3083 { -+ -+ protected static final int VERSION = MCVersions.V22W12A + 1; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("minecraft:allay"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:allay", new GameEventListenerWalker()); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java -new file mode 100644 -index 0000000000000000000000000000000000000000..52d8510e00d2373226f35e77db6fc7a893ec0764 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3084.java -@@ -0,0 +1,39 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.ConverterAbstractStringValueTypeRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+import com.google.common.collect.ImmutableMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3084 { -+ -+ protected static final int VERSION = MCVersions.V22W12A + 2; -+ -+ protected static final Map GAME_EVENT_RENAMES = new HashMap<>(ImmutableMap.builder() -+ .put("minecraft:block_press", "minecraft:block_activate") -+ .put("minecraft:block_switch", "minecraft:block_activate") -+ .put("minecraft:block_unpress", "minecraft:block_deactivate") -+ .put("minecraft:block_unswitch", "minecraft:block_deactivate") -+ .put("minecraft:drinking_finish", "minecraft:drink") -+ .put("minecraft:elytra_free_fall", "minecraft:elytra_glide") -+ .put("minecraft:entity_damaged", "minecraft:entity_damage") -+ .put("minecraft:entity_dying", "minecraft:entity_die") -+ .put("minecraft:entity_killed", "minecraft:entity_die") -+ .put("minecraft:mob_interact", "minecraft:entity_interact") -+ .put("minecraft:ravager_roar", "minecraft:entity_roar") -+ .put("minecraft:ring_bell", "minecraft:block_change") -+ .put("minecraft:shulker_close", "minecraft:container_close") -+ .put("minecraft:shulker_open", "minecraft:container_open") -+ .put("minecraft:wolf_shaking", "minecraft:entity_shake") -+ .build() -+ ); -+ -+ public static void register() { -+ ConverterAbstractStringValueTypeRename.register(VERSION, MCTypeRegistry.GAME_EVENT_NAME, (final String name) -> { -+ return GAME_EVENT_RENAMES.get(NamespaceUtil.correctNamespace(name)); -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java -new file mode 100644 -index 0000000000000000000000000000000000000000..554df81bb4f1a66bce539b42493f3ea7d4dff153 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3086.java -@@ -0,0 +1,51 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import com.google.common.collect.ImmutableMap; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V3086 { -+ -+ protected static final int VERSION = MCVersions.V22W13A + 1; -+ -+ protected static final Int2ObjectOpenHashMap CAT_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); -+ static { -+ CAT_ID_CONVERSION.defaultReturnValue("minecraft:tabby"); -+ CAT_ID_CONVERSION.put(0, "minecraft:tabby"); -+ CAT_ID_CONVERSION.put(1, "minecraft:black"); -+ CAT_ID_CONVERSION.put(2, "minecraft:red"); -+ CAT_ID_CONVERSION.put(3, "minecraft:siamese"); -+ CAT_ID_CONVERSION.put(4, "minecraft:british"); -+ CAT_ID_CONVERSION.put(5, "minecraft:calico"); -+ CAT_ID_CONVERSION.put(6, "minecraft:persian"); -+ CAT_ID_CONVERSION.put(7, "minecraft:ragdoll"); -+ CAT_ID_CONVERSION.put(8, "minecraft:white"); -+ CAT_ID_CONVERSION.put(9, "minecraft:jellie"); -+ CAT_ID_CONVERSION.put(10, "minecraft:all_black"); -+ } -+ -+ protected static final Map CAT_ADVANCEMENTS_CONVERSION = new HashMap<>(ImmutableMap.builder() -+ .put("textures/entity/cat/tabby.png", "minecraft:tabby") -+ .put("textures/entity/cat/black.png", "minecraft:black") -+ .put("textures/entity/cat/red.png", "minecraft:red") -+ .put("textures/entity/cat/siamese.png", "minecraft:siamese") -+ .put("textures/entity/cat/british_shorthair.png", "minecraft:british") -+ .put("textures/entity/cat/calico.png", "minecraft:calico") -+ .put("textures/entity/cat/persian.png", "minecraft:persian") -+ .put("textures/entity/cat/ragdoll.png", "minecraft:ragdoll") -+ .put("textures/entity/cat/white.png", "minecraft:white") -+ .put("textures/entity/cat/jellie.png", "minecraft:jellie") -+ .put("textures/entity/cat/all_black.png", "minecraft:all_black") -+ .build() -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityToVariant(VERSION, "CatType", CAT_ID_CONVERSION::get)); -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", CAT_ADVANCEMENTS_CONVERSION::get)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8cc7cadb921d52ebb5b8ed25078145536db5e7b5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3087.java -@@ -0,0 +1,22 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityToVariant; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -+ -+public final class V3087 { -+ -+ protected static final int VERSION = MCVersions.V22W13A + 2; -+ -+ protected static Int2ObjectOpenHashMap FROG_ID_CONVERSION = new Int2ObjectOpenHashMap<>(); -+ static { -+ FROG_ID_CONVERSION.put(0, "minecraft:temperate"); -+ FROG_ID_CONVERSION.put(1, "minecraft:warm"); -+ FROG_ID_CONVERSION.put(2, "minecraft:cold"); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:frog", new ConverterEntityToVariant(VERSION, "Variant", FROG_ID_CONVERSION::get)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d21124f6e70c887b9ca67305c8b0c1f4668af8e2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3088.java -@@ -0,0 +1,67 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+import ca.spottedleaf.dataconverter.util.NamespaceUtil; -+import java.util.Arrays; -+import java.util.HashSet; -+import java.util.Set; -+ -+public final class V3088 { -+ -+ // this class originally targeted 3079 but was changed to target a later version without changing the converter, zero clue why -+ protected static final int VERSION = MCVersions.V22W14A; -+ -+ private static final Set STATUSES_TO_SKIP_BLENDING = new HashSet<>( -+ Arrays.asList( -+ "minecraft:empty", -+ "minecraft:structure_starts", -+ "minecraft:structure_references", -+ "minecraft:biomes" -+ ) -+ ); -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ -+ private static MapType createBlendingData(final int height, final int minY) { -+ final MapType ret = Types.NBT.createEmptyMap(); -+ -+ ret.setInt("min_section", minY >> 4); -+ ret.setInt("max_section", (minY + height) >> 4); -+ -+ return ret; -+ } -+ -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("blending_data"); -+ final MapType context = data.getMap("__context"); -+ if (!"minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { -+ return null; -+ } -+ -+ final String status = NamespaceUtil.correctNamespace(data.getString("Status")); -+ if (status == null) { -+ return null; -+ } -+ -+ final MapType belowZeroRetrogen = data.getMap("below_zero_retrogen"); -+ -+ if (!STATUSES_TO_SKIP_BLENDING.contains(status)) { -+ data.setMap("blending_data", createBlendingData(384, -64)); -+ } else if (belowZeroRetrogen != null) { -+ final String realStatus = NamespaceUtil.correctNamespace(belowZeroRetrogen.getString("target_status", "empty")); -+ if (!STATUSES_TO_SKIP_BLENDING.contains(realStatus)) { -+ data.setMap("blending_data", createBlendingData(256, 0)); -+ } -+ } -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b3250a0b5ae2ab0aa5fffaace882052388861fd8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3090.java -@@ -0,0 +1,23 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3090 { -+ -+ protected static final int VERSION = MCVersions.V22W15A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:painting", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ RenameHelper.renameSingle(data, "Motive", "variant"); -+ RenameHelper.renameSingle(data, "Facing", "facing"); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8354c85fc4d92f36555c7de9dc0dffd1da05529a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3093.java -@@ -0,0 +1,22 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3093 { -+ -+ protected static final int VERSION = MCVersions.V22W17A; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:goat", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setBoolean("HasLeftHorn", true); -+ data.setBoolean("HasRightHorn", true); -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java -new file mode 100644 -index 0000000000000000000000000000000000000000..39540b5f76af1c7d51a51db9d711f32a3c7f624c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3094.java -@@ -0,0 +1,42 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3094 { -+ -+ public static final int VERSION = MCVersions.V22W17A + 1; -+ -+ private static final String[] SOUND_VARIANT_TO_INSTRUMENT = new String[] { -+ "minecraft:ponder_goat_horn", -+ "minecraft:sing_goat_horn", -+ "minecraft:seek_goat_horn", -+ "minecraft:feel_goat_horn", -+ "minecraft:admire_goat_horn", -+ "minecraft:call_goat_horn", -+ "minecraft:yearn_goat_horn", -+ "minecraft:dream_goat_horn" -+ }; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:goat_horn", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ -+ if (tag == null) { -+ return null; -+ } -+ -+ final int soundVariant = tag.getInt("SoundVariant"); -+ tag.remove("SoundVariant"); -+ -+ tag.setString("instrument", SOUND_VARIANT_TO_INSTRUMENT[soundVariant < 0 || soundVariant >= SOUND_VARIANT_TO_INSTRUMENT.length ? 0 : soundVariant]); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d5ac17b59c0dcc9baaeff022ecbf827c237cf9d6 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3097.java -@@ -0,0 +1,61 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.advancements.ConverterCriteriaRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterEntityVariantRename; -+import ca.spottedleaf.dataconverter.minecraft.converters.poi.ConverterPoiDelete; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class V3097 { -+ -+ private static final int VERSION = MCVersions.V22W19A + 1; -+ -+ public static void register() { -+ final DataConverter, MapType> removeFilteredBookText = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ tag.remove("filtered_title"); -+ tag.remove("filtered_pages"); -+ -+ return null; -+ } -+ }; -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:writable_book", removeFilteredBookText); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:written_book", removeFilteredBookText); -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:sign", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("FilteredText1"); -+ data.remove("FilteredText2"); -+ data.remove("FilteredText3"); -+ data.remove("FilteredText4"); -+ -+ return null; -+ } -+ }); -+ -+ final Map britishRenamer = new HashMap<>(Map.of( -+ "minecraft:british", "minecraft:british_shorthair" -+ )); -+ final Set poiRemove = new HashSet<>(Set.of( -+ "minecraft:unemployed", -+ "minecraft:nitwit" -+ )); -+ -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:cat", new ConverterEntityVariantRename(VERSION, britishRenamer::get)); -+ MCTypeRegistry.ADVANCEMENTS.addStructureConverter(new ConverterCriteriaRename(VERSION, "minecraft:husbandry/complete_catalogue", britishRenamer::get)); -+ MCTypeRegistry.POI_CHUNK.addStructureConverter(new ConverterPoiDelete(VERSION, poiRemove::contains)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java -new file mode 100644 -index 0000000000000000000000000000000000000000..381b49f2c50d46e52f7f9c8f6baede4e72eb343d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V3108.java -@@ -0,0 +1,27 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V3108 { -+ -+ private static final int VERSION = MCVersions.V1_19_1_PRE1 + 1; -+ -+ public static void register() { -+ MCTypeRegistry.CHUNK.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType context = data.getMap("__context"); -+ if ("minecraft:overworld".equals(context == null ? null : context.getString("dimension"))) { -+ return null; -+ } -+ -+ data.remove("blending_data"); -+ -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6ab2bf99d72983fc2742a1f6f2f7fa671611526d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V501.java -@@ -0,0 +1,21 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+ -+public final class V501 { -+ -+ protected static final int VERSION = MCVersions.V16W20A; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ registerMob("PolarBear"); -+ } -+ -+ private V501() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java -new file mode 100644 -index 0000000000000000000000000000000000000000..febeab68a5eec229ecca4f9e7b82c9ca99b3dbe1 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V502.java -@@ -0,0 +1,46 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.concurrent.ThreadLocalRandom; -+ -+public final class V502 { -+ -+ protected static final int VERSION = MCVersions.V16W20A + 1; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, (final String name) -> { -+ return "minecraft:cooked_fished".equals(name) ? "minecraft:cooked_fish" : null; -+ }); -+ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.getBoolean("IsVillager")) { -+ return null; -+ } -+ -+ data.remove("IsVillager"); -+ -+ if (data.hasKey("ZombieType")) { -+ return null; -+ } -+ -+ int type = data.getInt("VillagerProfession", -1); -+ // Vanilla doesn't remove the profession tag, so we don't! -+ if (type < 0 || type >= 6) { -+ type = ThreadLocalRandom.current().nextInt(6); -+ } -+ -+ data.setInt("ZombieType", type); -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V502() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java -new file mode 100644 -index 0000000000000000000000000000000000000000..30769f902c7d694bce41ab319d0b9a87c6103f11 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V505.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V505 { -+ -+ protected static final int VERSION = MCVersions.V16W21B + 1; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.setString("useVbo", "true"); -+ return null; -+ } -+ }); -+ } -+ -+ private V505() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f46b1a0c4bc96d638853cc61e5703798dbf6b886 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V700.java -@@ -0,0 +1,33 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V700 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 188; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Guardian", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (data.getBoolean("Elder")) { -+ data.setString("id", "ElderGuardian"); -+ } -+ data.remove("Elder"); -+ return null; -+ } -+ }); -+ -+ registerMob("ElderGuardian"); -+ } -+ -+ private V700() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java -new file mode 100644 -index 0000000000000000000000000000000000000000..42f173f426fb7d26e5ddb5a1c92c63b2e6a4930c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V701.java -@@ -0,0 +1,43 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V701 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 189; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Skeleton", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int type = data.getInt("SkeletonType"); -+ data.remove("SkeletonType"); -+ -+ switch (type) { -+ case 1: -+ data.setString("id", "WitherSkeleton"); -+ break; -+ case 2: -+ data.setString("id", "Stray"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("WitherSkeleton"); -+ registerMob("Stray"); -+ } -+ -+ private V701() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5cc91edde9c8160f75165bcef554023246e0a224 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V702.java -@@ -0,0 +1,53 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V702 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 190; -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("Zombie", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int zombieType = data.getInt("ZombieType"); -+ data.remove("ZombieType"); -+ -+ switch (zombieType) { -+ case 0: -+ default: -+ break; -+ -+ case 1: -+ case 2: -+ case 3: -+ case 4: -+ case 5: -+ data.setString("id", "ZombieVillager"); -+ data.setInt("Profession", zombieType - 1); -+ break; -+ -+ case 6: -+ data.setString("id", "Husk"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ registerMob("ZombieVillager"); -+ registerMob( "Husk"); -+ } -+ -+ private V702() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java -new file mode 100644 -index 0000000000000000000000000000000000000000..88d9c0fcd88ccfd6d6b46ae050914079c816fa3f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V703.java -@@ -0,0 +1,66 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V703 { -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 191; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("EntityHorse", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final int type = data.getInt("Type"); -+ data.remove("Type"); -+ -+ switch (type) { -+ case 0: -+ default: -+ data.setString("id", "Horse"); -+ break; -+ -+ case 1: -+ data.setString("id", "Donkey"); -+ break; -+ -+ case 2: -+ data.setString("id", "Mule"); -+ break; -+ -+ case 3: -+ data.setString("id", "ZombieHorse"); -+ break; -+ -+ case 4: -+ data.setString("id", "SkeletonHorse"); -+ break; -+ } -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ZombieHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SkeletonHorse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private V703() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0185fd60943dfb53c9d0707f1ff879658230d488 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V704.java -@@ -0,0 +1,341 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import net.minecraft.core.Registry; -+import net.minecraft.world.item.BlockItem; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.EntityBlock; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V704 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 192; -+ -+ public static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap() { -+ @Override -+ public String put(final String key, final String value) { -+ if (this.containsKey(key)) { -+ LOGGER.error("Duplicate item id to tile key: " + key); -+ throw new RuntimeException(); // only devs should see the consequence of this... at least start up the damn thing... -+ } -+ return super.put(key, value); -+ } -+ }; -+ static { -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "minecraft:furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "minecraft:furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "minecraft:chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "minecraft:chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "minecraft:ender_chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "minecraft:jukebox"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "minecraft:dispenser"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "minecraft:dropper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "minecraft:mob_spawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spawner", "minecraft:mob_spawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "minecraft:noteblock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "minecraft:brewing_stand"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "minecraft:enchanting_table"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "minecraft:beacon"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "minecraft:daylight_detector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "minecraft:hopper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "minecraft:flower_pot"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "minecraft:command_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_shulker_box", "minecraft:shulker_box"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "minecraft:piston"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "minecraft:structure_block"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "minecraft:end_portal"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "minecraft:end_gateway"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:white_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:orange_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:magenta_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_blue_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:yellow_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lime_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:pink_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:gray_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:silver_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:cyan_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:purple_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blue_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brown_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:green_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:red_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:black_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:oak_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:spruce_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:birch_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jungle_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:acacia_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dark_oak_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:crimson_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:warped_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skeleton_skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wither_skeleton_skull", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:zombie_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:player_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:creeper_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dragon_head", "minecraft:skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:barrel", "minecraft:barrel"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:conduit", "minecraft:conduit"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:smoker", "minecraft:smoker"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:blast_furnace", "minecraft:blast_furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lectern", "minecraft:lectern"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bell", "minecraft:bell"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jigsaw", "minecraft:jigsaw"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:campfire", "minecraft:campfire"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:bee_nest", "minecraft:beehive"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beehive", "minecraft:beehive"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_sensor", "minecraft:sculk_sensor"); -+ -+ // These are missing from Vanilla (TODO check on update) -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enchanting_table", "minecraft:enchanting_table"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:comparator", "minecraft:comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_bed", "minecraft:bed"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:light_gray_banner", "minecraft:banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:soul_campfire", "minecraft:campfire"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_catalyst", "minecraft:sculk_catalyst"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mangrove_sign", "minecraft:sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sculk_shrieker", "minecraft:sculk_shrieker"); -+ } -+ -+ // This class is responsible for also integrity checking the item id to tile id map here, we just use the item registry to figure it out -+ -+ static { -+ for (final Item item : Registry.ITEM) { -+ if (!(item instanceof BlockItem)) { -+ continue; -+ } -+ -+ if (!(((BlockItem)item).getBlock() instanceof EntityBlock)) { -+ continue; -+ } -+ -+ final String itemName = Registry.ITEM.getKey(item).toString(); -+ if (!ITEM_ID_TO_TILE_ENTITY_ID.containsKey(itemName)) { -+ LOGGER.error("Item id " + itemName + " does not contain tile mapping! (V704)"); -+ } -+ } -+ } -+ -+ protected static final Map TILE_ID_UPDATE = new HashMap<>(); -+ static { -+ TILE_ID_UPDATE.put("Airportal", "minecraft:end_portal"); -+ TILE_ID_UPDATE.put("Banner", "minecraft:banner"); -+ TILE_ID_UPDATE.put("Beacon", "minecraft:beacon"); -+ TILE_ID_UPDATE.put("Cauldron", "minecraft:brewing_stand"); -+ TILE_ID_UPDATE.put("Chest", "minecraft:chest"); -+ TILE_ID_UPDATE.put("Comparator", "minecraft:comparator"); -+ TILE_ID_UPDATE.put("Control", "minecraft:command_block"); -+ TILE_ID_UPDATE.put("DLDetector", "minecraft:daylight_detector"); -+ TILE_ID_UPDATE.put("Dropper", "minecraft:dropper"); -+ TILE_ID_UPDATE.put("EnchantTable", "minecraft:enchanting_table"); -+ TILE_ID_UPDATE.put("EndGateway", "minecraft:end_gateway"); -+ TILE_ID_UPDATE.put("EnderChest", "minecraft:ender_chest"); -+ TILE_ID_UPDATE.put("FlowerPot", "minecraft:flower_pot"); -+ TILE_ID_UPDATE.put("Furnace", "minecraft:furnace"); -+ TILE_ID_UPDATE.put("Hopper", "minecraft:hopper"); -+ TILE_ID_UPDATE.put("MobSpawner", "minecraft:mob_spawner"); -+ TILE_ID_UPDATE.put("Music", "minecraft:noteblock"); -+ TILE_ID_UPDATE.put("Piston", "minecraft:piston"); -+ TILE_ID_UPDATE.put("RecordPlayer", "minecraft:jukebox"); -+ TILE_ID_UPDATE.put("Sign", "minecraft:sign"); -+ TILE_ID_UPDATE.put("Skull", "minecraft:skull"); -+ TILE_ID_UPDATE.put("Structure", "minecraft:structure_block"); -+ TILE_ID_UPDATE.put("Trap", "minecraft:dispenser"); -+ } -+ -+ protected static void registerInventory(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); -+ } -+ -+ public static void register() { -+ MCTypeRegistry.TILE_ENTITY.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String id = data.getString("id"); -+ if (id == null) { -+ return null; -+ } -+ -+ data.setString("id", TILE_ID_UPDATE.getOrDefault(id, id)); -+ return null; -+ } -+ }); -+ -+ -+ -+ registerInventory( "minecraft:furnace"); -+ registerInventory( "minecraft:chest"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:jukebox", new DataWalkerItems("RecordItem")); -+ registerInventory("minecraft:dispenser"); -+ registerInventory("minecraft:dropper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:mob_spawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ registerInventory("minecraft:brewing_stand"); -+ registerInventory("minecraft:hopper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:flower_pot", new DataWalkerItemNames("Item")); -+ -+ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); -+ -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ // only things here are in tag, if changed update if above -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag != null) { -+ final String itemId = data.getString("id"); -+ final String entityId; -+ if ("minecraft:armor_stand".equals(itemId)) { -+ // The check for version id is changed here. For whatever reason, the legacy -+ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, -+ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. -+ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant -+ // with the V705 schema. -+ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ArmorStand" : "minecraft:armor_stand"; -+ } else if (itemId != null && itemId.contains("_spawn_egg")) { -+ // V1451 changes spawn eggs to have the sub entity id be a part of the item id, but of course Mojang never -+ // bothered to write in logic to set the sub entity id, so we have to. -+ // format is ALWAYS :_spawn_egg post flattening -+ entityId = itemId.substring(0, itemId.indexOf("_spawn_egg")); -+ } else if ("minecraft:item_frame".equals(itemId)) { -+ // add missing item_frame entity id -+ // version check is same for armorstand, as both were namespaced at the same time -+ entityId = DataConverter.getVersion(fromVersion) < 705 ? "ItemFrame" : "minecraft:item_frame"; -+ } else if ("minecraft:glow_item_frame".equals(itemId)) { -+ // add missing glow_item_frame entity id -+ entityId = "minecraft:glow_item_frame"; -+ } else { -+ entityId = entityTag.getString("id"); -+ } -+ -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve Entity for ItemStack (V704): " + itemId); -+ } -+ removeId = false; -+ } else { -+ removeId = !entityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ entityTag.setString("id", entityId); -+ } -+ } -+ -+ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); -+ -+ if (replace != null) { -+ entityTag = replace; -+ tag.setMap("EntityTag", entityTag); -+ } -+ if (removeId) { -+ entityTag.remove("id"); -+ } -+ } -+ -+ MapType blockEntityTag = tag.getMap("BlockEntityTag"); -+ if (blockEntityTag != null) { -+ final String itemId = data.getString("id"); -+ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V704): " + itemId); -+ } -+ removeId = false; -+ } else { -+ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ blockEntityTag.setString("id", entityId); -+ } -+ } -+ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); -+ if (replace != null) { -+ blockEntityTag = replace; -+ tag.setMap("BlockEntityTag", entityTag); -+ } -+ if (removeId) { -+ blockEntityTag.remove("id"); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ // Enforce namespace for ids -+ MCTypeRegistry.TILE_ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ } -+ -+ private V704() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e1d7013e49904dacc5e33d9c0b3f3ddb10e3d07a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V705.java -@@ -0,0 +1,231 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.entity.ConverterAbstractEntityRename; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V705 { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V1_10_2 + 193; -+ -+ protected static final Map ENTITY_ID_UPDATE = new HashMap<>(); -+ static { -+ ENTITY_ID_UPDATE.put("AreaEffectCloud", "minecraft:area_effect_cloud"); -+ ENTITY_ID_UPDATE.put("ArmorStand", "minecraft:armor_stand"); -+ ENTITY_ID_UPDATE.put("Arrow", "minecraft:arrow"); -+ ENTITY_ID_UPDATE.put("Bat", "minecraft:bat"); -+ ENTITY_ID_UPDATE.put("Blaze", "minecraft:blaze"); -+ ENTITY_ID_UPDATE.put("Boat", "minecraft:boat"); -+ ENTITY_ID_UPDATE.put("CaveSpider", "minecraft:cave_spider"); -+ ENTITY_ID_UPDATE.put("Chicken", "minecraft:chicken"); -+ ENTITY_ID_UPDATE.put("Cow", "minecraft:cow"); -+ ENTITY_ID_UPDATE.put("Creeper", "minecraft:creeper"); -+ ENTITY_ID_UPDATE.put("Donkey", "minecraft:donkey"); -+ ENTITY_ID_UPDATE.put("DragonFireball", "minecraft:dragon_fireball"); -+ ENTITY_ID_UPDATE.put("ElderGuardian", "minecraft:elder_guardian"); -+ ENTITY_ID_UPDATE.put("EnderCrystal", "minecraft:ender_crystal"); -+ ENTITY_ID_UPDATE.put("EnderDragon", "minecraft:ender_dragon"); -+ ENTITY_ID_UPDATE.put("Enderman", "minecraft:enderman"); -+ ENTITY_ID_UPDATE.put("Endermite", "minecraft:endermite"); -+ ENTITY_ID_UPDATE.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); -+ ENTITY_ID_UPDATE.put("FallingSand", "minecraft:falling_block"); -+ ENTITY_ID_UPDATE.put("Fireball", "minecraft:fireball"); -+ ENTITY_ID_UPDATE.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); -+ ENTITY_ID_UPDATE.put("Ghast", "minecraft:ghast"); -+ ENTITY_ID_UPDATE.put("Giant", "minecraft:giant"); -+ ENTITY_ID_UPDATE.put("Guardian", "minecraft:guardian"); -+ ENTITY_ID_UPDATE.put("Horse", "minecraft:horse"); -+ ENTITY_ID_UPDATE.put("Husk", "minecraft:husk"); -+ ENTITY_ID_UPDATE.put("Item", "minecraft:item"); -+ ENTITY_ID_UPDATE.put("ItemFrame", "minecraft:item_frame"); -+ ENTITY_ID_UPDATE.put("LavaSlime", "minecraft:magma_cube"); -+ ENTITY_ID_UPDATE.put("LeashKnot", "minecraft:leash_knot"); -+ ENTITY_ID_UPDATE.put("MinecartChest", "minecraft:chest_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartFurnace", "minecraft:furnace_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartHopper", "minecraft:hopper_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartRideable", "minecraft:minecart"); -+ ENTITY_ID_UPDATE.put("MinecartSpawner", "minecraft:spawner_minecart"); -+ ENTITY_ID_UPDATE.put("MinecartTNT", "minecraft:tnt_minecart"); -+ ENTITY_ID_UPDATE.put("Mule", "minecraft:mule"); -+ ENTITY_ID_UPDATE.put("MushroomCow", "minecraft:mooshroom"); -+ ENTITY_ID_UPDATE.put("Ozelot", "minecraft:ocelot"); -+ ENTITY_ID_UPDATE.put("Painting", "minecraft:painting"); -+ ENTITY_ID_UPDATE.put("Pig", "minecraft:pig"); -+ ENTITY_ID_UPDATE.put("PigZombie", "minecraft:zombie_pigman"); -+ ENTITY_ID_UPDATE.put("PolarBear", "minecraft:polar_bear"); -+ ENTITY_ID_UPDATE.put("PrimedTnt", "minecraft:tnt"); -+ ENTITY_ID_UPDATE.put("Rabbit", "minecraft:rabbit"); -+ ENTITY_ID_UPDATE.put("Sheep", "minecraft:sheep"); -+ ENTITY_ID_UPDATE.put("Shulker", "minecraft:shulker"); -+ ENTITY_ID_UPDATE.put("ShulkerBullet", "minecraft:shulker_bullet"); -+ ENTITY_ID_UPDATE.put("Silverfish", "minecraft:silverfish"); -+ ENTITY_ID_UPDATE.put("Skeleton", "minecraft:skeleton"); -+ ENTITY_ID_UPDATE.put("SkeletonHorse", "minecraft:skeleton_horse"); -+ ENTITY_ID_UPDATE.put("Slime", "minecraft:slime"); -+ ENTITY_ID_UPDATE.put("SmallFireball", "minecraft:small_fireball"); -+ ENTITY_ID_UPDATE.put("SnowMan", "minecraft:snowman"); -+ ENTITY_ID_UPDATE.put("Snowball", "minecraft:snowball"); -+ ENTITY_ID_UPDATE.put("SpectralArrow", "minecraft:spectral_arrow"); -+ ENTITY_ID_UPDATE.put("Spider", "minecraft:spider"); -+ ENTITY_ID_UPDATE.put("Squid", "minecraft:squid"); -+ ENTITY_ID_UPDATE.put("Stray", "minecraft:stray"); -+ ENTITY_ID_UPDATE.put("ThrownEgg", "minecraft:egg"); -+ ENTITY_ID_UPDATE.put("ThrownEnderpearl", "minecraft:ender_pearl"); -+ ENTITY_ID_UPDATE.put("ThrownExpBottle", "minecraft:xp_bottle"); -+ ENTITY_ID_UPDATE.put("ThrownPotion", "minecraft:potion"); -+ ENTITY_ID_UPDATE.put("Villager", "minecraft:villager"); -+ ENTITY_ID_UPDATE.put("VillagerGolem", "minecraft:villager_golem"); -+ ENTITY_ID_UPDATE.put("Witch", "minecraft:witch"); -+ ENTITY_ID_UPDATE.put("WitherBoss", "minecraft:wither"); -+ ENTITY_ID_UPDATE.put("WitherSkeleton", "minecraft:wither_skeleton"); -+ ENTITY_ID_UPDATE.put("WitherSkull", "minecraft:wither_skull"); -+ ENTITY_ID_UPDATE.put("Wolf", "minecraft:wolf"); -+ ENTITY_ID_UPDATE.put("XPOrb", "minecraft:xp_orb"); -+ ENTITY_ID_UPDATE.put("Zombie", "minecraft:zombie"); -+ ENTITY_ID_UPDATE.put("ZombieHorse", "minecraft:zombie_horse"); -+ ENTITY_ID_UPDATE.put("ZombieVillager", "minecraft:zombie_villager"); -+ } -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("ArmorItems", "HandItems")); -+ } -+ -+ private static void registerThrowableProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ public static void register() { -+ ConverterAbstractEntityRename.register(VERSION, ENTITY_ID_UPDATE::get); -+ -+ registerMob("minecraft:armor_stand"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:arrow", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:bat"); -+ registerMob("minecraft:blaze"); -+ registerMob("minecraft:cave_spider"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:chest_minecart", new DataWalkerItemLists("Items")); -+ registerMob("minecraft:chicken"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:commandblock_minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:cow"); -+ registerMob("minecraft:creeper"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:donkey", new DataWalkerItems("SaddleItem")); -+ registerThrowableProjectile("minecraft:egg"); -+ registerMob("minecraft:elder_guardian"); -+ registerMob("minecraft:ender_dragon"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:enderman", new DataWalkerBlockNames("carried")); -+ registerMob("minecraft:endermite"); -+ registerThrowableProjectile("minecraft:ender_pearl"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerBlockNames("Block")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:falling_block", new DataWalkerTileEntities("TileEntityData")); -+ registerThrowableProjectile("minecraft:fireball"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:fireworks_rocket", new DataWalkerItems("FireworksItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:furnace_minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:ghast"); -+ registerMob("minecraft:giant"); -+ registerMob("minecraft:guardian"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:hopper_minecart", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ registerMob("minecraft:husk"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item", new DataWalkerItems("Item")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:item_frame", new DataWalkerItems("Item")); -+ registerMob("minecraft:magma_cube"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:minecart", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("minecraft:mooshroom"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:mule", new DataWalkerItems("SaddleItem")); -+ registerMob("minecraft:ocelot"); -+ registerMob("minecraft:pig"); -+ registerMob("minecraft:polar_bear"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerItems("Potion")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:potion", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:rabbit"); -+ registerMob("minecraft:sheep"); -+ registerMob("minecraft:shulker"); -+ registerMob("minecraft:silverfish"); -+ registerMob("minecraft:skeleton"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:skeleton_horse", new DataWalkerItems("SaddleItem")); -+ registerMob("minecraft:slime"); -+ registerThrowableProjectile("minecraft:small_fireball"); -+ registerThrowableProjectile("minecraft:snowball"); -+ registerMob("minecraft:snowman"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spawner_minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:spectral_arrow", new DataWalkerBlockNames("inTile")); -+ registerMob("minecraft:spider"); -+ registerMob("minecraft:squid"); -+ registerMob("minecraft:stray"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:tnt_minecart", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "Inventory", fromVersion, toVersion); -+ -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0, len = recipes.size(); i < len; ++i) { -+ final MapType recipe = recipes.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "ArmorItems", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, data, "HandItems", fromVersion, toVersion); -+ -+ return null; -+ }); -+ registerMob("minecraft:villager_golem"); -+ registerMob("minecraft:witch"); -+ registerMob("minecraft:wither"); -+ registerMob("minecraft:wither_skeleton"); -+ registerThrowableProjectile("minecraft:wither_skull"); -+ registerMob("minecraft:wolf"); -+ registerThrowableProjectile("minecraft:xp_bottle"); -+ registerMob("minecraft:zombie"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItems("SaddleItem")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:zombie_horse", new DataWalkerItemLists("ArmorItems", "HandItems")); -+ registerMob("minecraft:zombie_pigman"); -+ registerMob("minecraft:zombie_villager"); -+ registerMob("minecraft:evocation_illager"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItemLists("Items", "ArmorItems", "HandItems")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "minecraft:llama", new DataWalkerItems("SaddleItem", "DecorItem")); -+ registerMob("minecraft:vex"); -+ registerMob("minecraft:vindication_illager"); -+ // Don't need to re-register itemstack walker, the V704 will correctly choose the right id for armorstand based on -+ // the source version -+ -+ // Enforce namespace for ids -+ MCTypeRegistry.ENTITY.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ MCTypeRegistry.ENTITY_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ } -+ -+ private V705() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0070a3d02a87b0f08cd5e74d4f106f3e97f6b4f8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V804.java -@@ -0,0 +1,60 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V804 { -+ -+ protected static final int VERSION = MCVersions.V16W35A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:banner", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType blockEntity = tag.getMap("BlockEntityTag"); -+ if (blockEntity == null) { -+ return null; -+ } -+ -+ if (!blockEntity.hasKey("Base", ObjectType.NUMBER)) { -+ return null; -+ } -+ -+ data.setShort("Damage", (short)(blockEntity.getShort("Base") & 15)); -+ -+ final MapType display = tag.getMap("display"); -+ if (display != null) { -+ final ListType lore = display.getList("Lore", ObjectType.STRING); -+ if (lore != null) { -+ if (lore.size() == 1 && "(+NBT)".equals(lore.getString(0))) { -+ return null; -+ } -+ } -+ } -+ -+ blockEntity.remove("Base"); -+ if (blockEntity.isEmpty()) { -+ tag.remove("BlockEntityTag"); -+ } -+ -+ if (tag.isEmpty()) { -+ data.remove("tag"); -+ } -+ -+ return null; -+ } -+ }); -+ } -+ -+ private V804() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a2717b9d936872ec07141b0f3ae2a6eec81f2dbf ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V806.java -@@ -0,0 +1,40 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.Types; -+ -+public final class V806 { -+ -+ protected static final int VERSION = MCVersions.V16W36A + 1; -+ -+ public static void register() { -+ final DataConverter, MapType> potionWaterUpdater = new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ tag = Types.NBT.createEmptyMap(); -+ data.setMap("tag", tag); -+ } -+ -+ if (!tag.hasKey("Potion", ObjectType.STRING)) { -+ tag.setString("Potion", "minecraft:water"); -+ } -+ -+ return null; -+ } -+ }; -+ -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:splash_potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:lingering_potion", potionWaterUpdater); -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:tipped_arrow", potionWaterUpdater); -+ } -+ -+ private V806() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b058e5e9b34a9dd134ef93e7a397b5f1e4e11fbd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V808.java -@@ -0,0 +1,30 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V808 { -+ -+ protected static final int VERSION = MCVersions.V16W38A + 1; -+ -+ public static void register() { -+ MCTypeRegistry.ENTITY.addConverterForId("minecraft:shulker", new DataConverter<>(VERSION, 1) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ if (!data.hasKey("Color", ObjectType.NUMBER)) { -+ data.setByte("Color", (byte)10); -+ } -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "minecraft:shulker_box", new DataWalkerItemLists("Items")); -+ } -+ -+ private V808() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b6de7c32acd0adf78812edbbd184117661599c80 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V813.java -@@ -0,0 +1,64 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class V813 { -+ -+ protected static final int VERSION = MCVersions.V16W40A; -+ -+ public static final String[] SHULKER_ID_BY_COLOUR = new String[] { -+ "minecraft:white_shulker_box", -+ "minecraft:orange_shulker_box", -+ "minecraft:magenta_shulker_box", -+ "minecraft:light_blue_shulker_box", -+ "minecraft:yellow_shulker_box", -+ "minecraft:lime_shulker_box", -+ "minecraft:pink_shulker_box", -+ "minecraft:gray_shulker_box", -+ "minecraft:silver_shulker_box", -+ "minecraft:cyan_shulker_box", -+ "minecraft:purple_shulker_box", -+ "minecraft:blue_shulker_box", -+ "minecraft:brown_shulker_box", -+ "minecraft:green_shulker_box", -+ "minecraft:red_shulker_box", -+ "minecraft:black_shulker_box" -+ }; -+ -+ public static void register() { -+ MCTypeRegistry.ITEM_STACK.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ final MapType blockEntity = tag.getMap("BlockEntityTag"); -+ if (blockEntity == null) { -+ return null; -+ } -+ -+ final int color = blockEntity.getInt("Color"); -+ blockEntity.remove("Color"); -+ -+ data.setString("id", SHULKER_ID_BY_COLOUR[color % SHULKER_ID_BY_COLOUR.length]); -+ -+ return null; -+ } -+ }); -+ -+ MCTypeRegistry.TILE_ENTITY.addConverterForId("minecraft:shulker_box", new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ data.remove("Color"); -+ return null; -+ } -+ }); -+ } -+ -+ private V813() {} -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4b427c128bd75d2dc8b36f0c377454385c029467 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V816.java -@@ -0,0 +1,28 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.converters.DataConverter; -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.Locale; -+ -+public final class V816 { -+ -+ protected static final int VERSION = MCVersions.V16W43A; -+ -+ public static void register() { -+ MCTypeRegistry.OPTIONS.addStructureConverter(new DataConverter<>(VERSION) { -+ @Override -+ public MapType convert(final MapType data, final long sourceVersion, final long toVersion) { -+ final String lang = data.getString("lang"); -+ if (lang != null) { -+ data.setString("lang", lang.toLowerCase(Locale.ROOT)); -+ } -+ return null; -+ } -+ }); -+ } -+ -+ private V816() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e2ef3cea4fd382a75a4d787fe2e2ff509eb49fc ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V820.java -@@ -0,0 +1,19 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.itemname.ConverterAbstractItemRename; -+import com.google.common.collect.ImmutableMap; -+ -+public final class V820 { -+ -+ protected static final int VERSION = MCVersions.V1_11 + 1; -+ -+ public static void register() { -+ ConverterAbstractItemRename.register(VERSION, ImmutableMap.of( -+ "minecraft:totem", "minecraft:totem_of_undying" -+ )::get); -+ } -+ -+ private V820() {} -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d95f8af7bd9bcedbb30be1b59e3dc749551e8cbe ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/versions/V99.java -@@ -0,0 +1,348 @@ -+package ca.spottedleaf.dataconverter.minecraft.versions; -+ -+import ca.spottedleaf.dataconverter.minecraft.MCVersions; -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.HelperItemNameV102; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookEnforceNamespacedID; -+import ca.spottedleaf.dataconverter.minecraft.hooks.DataHookValueTypeEnforceNamespaced; -+import ca.spottedleaf.dataconverter.minecraft.walkers.block_name.DataWalkerBlockNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItemLists; -+import ca.spottedleaf.dataconverter.minecraft.walkers.item_name.DataWalkerItemNames; -+import ca.spottedleaf.dataconverter.minecraft.walkers.itemstack.DataWalkerItems; -+import ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity.DataWalkerTileEntities; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import com.mojang.logging.LogUtils; -+import org.slf4j.Logger; -+import java.util.HashMap; -+import java.util.Map; -+ -+public final class V99 { -+ -+ // Structure for all data before data upgrading was added to minecraft (pre 15w32a) -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ protected static final int VERSION = MCVersions.V15W32A - 1; -+ -+ protected static final Map ITEM_ID_TO_TILE_ENTITY_ID = new HashMap<>(); -+ -+ static { -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:furnace", "Furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:lit_furnace", "Furnace"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chest", "Chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:trapped_chest", "Chest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:ender_chest", "EnderChest"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:jukebox", "RecordPlayer"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dispenser", "Trap"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:dropper", "Dropper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:mob_spawner", "MobSpawner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:noteblock", "Music"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:brewing_stand", "Cauldron"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:enhanting_table", "EnchantTable"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:beacon", "Beacon"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:skull", "Skull"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector", "DLDetector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:hopper", "Hopper"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:flower_pot", "FlowerPot"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:repeating_command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:chain_command_block", "CommandBlock"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_sign", "Sign"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:piston_head", "Piston"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:daylight_detector_inverted", "DLDetector"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:unpowered_comparator", "Comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:powered_comparator", "Comparator"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:wall_banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:standing_banner", "Banner"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:structure_block", "Structure"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_portal", "Airportal"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:end_gateway", "EndGateway"); -+ ITEM_ID_TO_TILE_ENTITY_ID.put("minecraft:shield", "Banner"); -+ } -+ -+ private static void registerMob(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Equipment")); -+ } -+ -+ private static void registerProjectile(final String id) { -+ MCTypeRegistry.ENTITY.addWalker(VERSION, id, new DataWalkerBlockNames("inTile")); -+ } -+ -+ private static void registerInventory(final String id) { -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, id, new DataWalkerItemLists("Items")); -+ } -+ -+ public static void register() { -+ // entities -+ MCTypeRegistry.ENTITY.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ENTITY, data, "Riding", fromVersion, toVersion); -+ -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Item", new DataWalkerItems("Item")); -+ registerProjectile("ThrownEgg"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Arrow", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "TippedArrow", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "SpectralArrow", new DataWalkerBlockNames("inTile")); -+ registerProjectile("Snowball"); -+ registerProjectile("Fireball"); -+ registerProjectile("SmallFireball"); -+ registerProjectile("ThrownEnderpearl"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerBlockNames("inTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ThrownPotion", new DataWalkerItems("Potion")); -+ registerProjectile("ThrownExpBottle"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "ItemFrame", new DataWalkerItems("Item")); -+ registerProjectile("WitherSkull"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerBlockNames("Block")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FallingSand", new DataWalkerTileEntities("TileEntityData")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "FireworksRocketEntity", new DataWalkerItems("FireworksItem")); -+ // Note: Minecart is the generic entity. It can be subtyped via an int to become one of the specific minecarts -+ // (i.e rideable, chest, furnace, tnt, etc) -+ // Because of this, we add all walkers to the generic type, even though they might not be needed. -+ // Vanilla does not make the generic minecart convert spawners, but we do. -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerBlockNames("DisplayTile")); // for all minecart types -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", new DataWalkerItemLists("Items")); // for chest types -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Minecart", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); // for spawner type -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartRideable", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartChest", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartFurnace", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartTNT", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerBlockNames("DisplayTile")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartHopper", new DataWalkerItemLists("Items")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "MinecartCommandBlock", new DataWalkerBlockNames("DisplayTile")); -+ registerMob("ArmorStand"); -+ registerMob("Creeper"); -+ registerMob("Skeleton"); -+ registerMob("Spider"); -+ registerMob("Giant"); -+ registerMob("Zombie"); -+ registerMob("Slime"); -+ registerMob("Ghast"); -+ registerMob("PigZombie"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerBlockNames("carried")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Enderman", new DataWalkerItemLists("Equipment")); -+ registerMob("CaveSpider"); -+ registerMob("Silverfish"); -+ registerMob("Blaze"); -+ registerMob("LavaSlime"); -+ registerMob("EnderDragon"); -+ registerMob("WitherBoss"); -+ registerMob("Bat"); -+ registerMob("Witch"); -+ registerMob("Endermite"); -+ registerMob("Guardian"); -+ registerMob("Pig"); -+ registerMob("Sheep"); -+ registerMob("Cow"); -+ registerMob("Chicken"); -+ registerMob("Squid"); -+ registerMob("Wolf"); -+ registerMob("MushroomCow"); -+ registerMob("SnowMan"); -+ registerMob("Ozelot"); -+ registerMob("VillagerGolem"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItemLists("Items", "Equipment")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "EntityHorse", new DataWalkerItems("ArmorItem", "SaddleItem")); -+ registerMob("Rabbit"); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", new DataWalkerItemLists("Inventory", "Equipment")); -+ MCTypeRegistry.ENTITY.addWalker(VERSION, "Villager", (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType offers = data.getMap("Offers"); -+ if (offers != null) { -+ final ListType recipes = offers.getList("Recipes", ObjectType.MAP); -+ if (recipes != null) { -+ for (int i = 0; i < recipes.size(); ++i) { -+ final MapType recipe = recipes.getMap(i); -+ -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buy", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "buyB", fromVersion, toVersion); -+ WalkerUtils.convert(MCTypeRegistry.ITEM_STACK, recipe, "sell", fromVersion, toVersion); -+ } -+ } -+ } -+ -+ return null; -+ }); -+ registerMob("Shulker"); -+ -+ // tile entities -+ -+ // Inventory -> new DataWalkerItemLists("Items") -+ registerInventory("Furnace"); -+ registerInventory("Chest"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "RecordPlayer", new DataWalkerItems("RecordItem")); -+ registerInventory("Trap"); -+ registerInventory("Dropper"); -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "MobSpawner", (final MapType data, final long fromVersion, final long toVersion) -> { -+ MCTypeRegistry.UNTAGGED_SPAWNER.convert(data, fromVersion, toVersion); -+ return null; -+ }); -+ registerInventory("Cauldron"); -+ registerInventory("Hopper"); -+ // Note: Vanilla does not properly handle this case, it will not convert int ids! -+ MCTypeRegistry.TILE_ENTITY.addWalker(VERSION, "FlowerPot", new DataWalkerItemNames("Item")); -+ -+ // rest -+ -+ MCTypeRegistry.ITEM_STACK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convert(MCTypeRegistry.ITEM_NAME, data, "id", fromVersion, toVersion); -+ -+ final MapType tag = data.getMap("tag"); -+ if (tag == null) { -+ return null; -+ } -+ -+ // only things here are in tag, if changed update if above -+ -+ WalkerUtils.convertList(MCTypeRegistry.ITEM_STACK, tag, "Items", fromVersion, toVersion); -+ -+ MapType entityTag = tag.getMap("EntityTag"); -+ if (entityTag != null) { -+ String itemId = getStringId(data.getString("id")); -+ final String entityId; -+ if ("minecraft:armor_stand".equals(itemId)) { -+ // The check for version id is removed here. For whatever reason, the legacy -+ // data converters used entity id "minecraft:armor_stand" when version was greater-than 514, -+ // but entity ids were not namespaced until V705! So somebody fucked up the legacy converters. -+ // DFU agrees with my analysis here, it will only set the entityId here to the namespaced variant -+ // with the V705 schema. -+ entityId = "ArmorStand"; -+ } else if ("minecraft:item_frame".equals(itemId)) { -+ // add missing item_frame entity id -+ entityId = "ItemFrame"; -+ } else { -+ entityId = entityTag.getString("id"); -+ } -+ -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve Entity for ItemStack (V99): " + data.getGeneric("id")); -+ } -+ removeId = false; -+ } else { -+ removeId = !entityTag.hasKey("id", ObjectType.STRING); -+ if (removeId) { -+ entityTag.setString("id", entityId); -+ } -+ } -+ -+ final MapType replace = MCTypeRegistry.ENTITY.convert(entityTag, fromVersion, toVersion); -+ -+ if (replace != null) { -+ entityTag = replace; -+ tag.setMap("EntityTag", entityTag); -+ } -+ if (removeId) { -+ entityTag.remove("id"); -+ } -+ } -+ -+ MapType blockEntityTag = tag.getMap("BlockEntityTag"); -+ if (blockEntityTag != null) { -+ final String itemId = getStringId(data.getString("id")); -+ final String entityId = ITEM_ID_TO_TILE_ENTITY_ID.get(itemId); -+ final boolean removeId; -+ if (entityId == null) { -+ if (!"minecraft:air".equals(itemId)) { -+ LOGGER.warn("Unable to resolve BlockEntity for ItemStack (V99): " + data.getGeneric("id")); -+ } -+ removeId = false; -+ } else { -+ removeId = !blockEntityTag.hasKey("id", ObjectType.STRING); -+ blockEntityTag.setString("id", entityId); -+ } -+ final MapType replace = MCTypeRegistry.TILE_ENTITY.convert(blockEntityTag, fromVersion, toVersion); -+ if (replace != null) { -+ blockEntityTag = replace; -+ tag.setMap("BlockEntityTag", blockEntityTag); -+ } -+ if (removeId) { -+ blockEntityTag.remove("id"); -+ } -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanDestroy", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_NAME, tag, "CanPlaceOn", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.PLAYER.addStructureWalker(VERSION, new DataWalkerItemLists("Inventory", "EnderItems")); -+ -+ MCTypeRegistry.CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ final MapType level = data.getMap("Level"); -+ if (level == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, level, "Entities", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TILE_ENTITY, level, "TileEntities", fromVersion, toVersion); -+ -+ final ListType tileTicks = level.getList("TileTicks", ObjectType.MAP); -+ if (tileTicks != null) { -+ for (int i = 0, len = tileTicks.size(); i < len; ++i) { -+ final MapType tileTick = tileTicks.getMap(i); -+ WalkerUtils.convert(MCTypeRegistry.BLOCK_NAME, tileTick, "i", fromVersion, toVersion); -+ } -+ } -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.ENTITY_CHUNK.addStructureWalker(VERSION, (final MapType data, final long fromVersion, final long toVersion) -> { -+ WalkerUtils.convertList(MCTypeRegistry.ENTITY, data, "Entities", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ MCTypeRegistry.SAVED_DATA.addStructureWalker(VERSION, (final MapType root, final long fromVersion, final long toVersion) -> { -+ final MapType data = root.getMap("data"); -+ if (data == null) { -+ return null; -+ } -+ -+ WalkerUtils.convertValues(MCTypeRegistry.STRUCTURE_FEATURE, data, "Features", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.OBJECTIVE, data, "Objectives", fromVersion, toVersion); -+ WalkerUtils.convertList(MCTypeRegistry.TEAM, data, "Teams", fromVersion, toVersion); -+ -+ return null; -+ }); -+ -+ -+ // Enforce namespacing for ids -+ MCTypeRegistry.BLOCK_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ MCTypeRegistry.ITEM_NAME.addStructureHook(VERSION, new DataHookValueTypeEnforceNamespaced()); -+ MCTypeRegistry.ITEM_STACK.addStructureHook(VERSION, new DataHookEnforceNamespacedID()); -+ -+ // Entity is absent; the String form is not yet namespaced, unlike the above. -+ } -+ -+ protected static String getStringId(final Object id) { -+ if (id instanceof String) { -+ return (String)id; -+ } else if (id instanceof Number) { -+ return HelperItemNameV102.getNameFromId(((Number)id).intValue()); -+ } else { -+ return null; -+ } -+ } -+ -+ private V99() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..930e014858ef635ebe25f7f92dc81ba0eaac50a8 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/block_name/DataWalkerBlockNames.java -@@ -0,0 +1,11 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.block_name; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+ -+public final class DataWalkerBlockNames extends DataWalkerTypePaths { -+ -+ public DataWalkerBlockNames(final String... paths) { -+ super(MCTypeRegistry.BLOCK_NAME, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e7655645f5d32026a609a8c7517827653c5c5e8b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/game_event/GameEventListenerWalker.java -@@ -0,0 +1,26 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.game_event; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class GameEventListenerWalker implements DataWalker { -+ -+ @Override -+ public MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ final MapType listener = data.getMap("listener"); -+ if (listener == null) { -+ return null; -+ } -+ -+ final MapType event = listener.getMap("event"); -+ if (event == null) { -+ return null; -+ } -+ -+ WalkerUtils.convert(MCTypeRegistry.GAME_EVENT_NAME, event, "game_event", fromVersion, toVersion); -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7c8f6a5034b48e1ec2c5925211f491115ca735aa ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerListPaths.java -@@ -0,0 +1,38 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerListPaths implements DataWalker { -+ -+ protected final DataType type; -+ protected final String[] paths; -+ -+ public DataWalkerListPaths(final DataType type, final String... paths) { -+ this.type = type; -+ this.paths = paths; -+ } -+ -+ @Override -+ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ final DataType type = this.type; -+ for (final String path : this.paths) { -+ final ListType list = data.getListUnchecked(path); -+ if (list == null) { -+ continue; -+ } -+ -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final Object current = list.getGeneric(i); -+ final Object converted = type.convert((T)current, fromVersion, toVersion); -+ if (converted != null) { -+ list.setGeneric(i, converted); -+ } -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e66b4e0f7cdb032b545ace7ba852ad7979f3c96a ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/DataWalkerTypePaths.java -@@ -0,0 +1,34 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.converters.datatypes.DataType; -+import ca.spottedleaf.dataconverter.converters.datatypes.DataWalker; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerTypePaths implements DataWalker { -+ -+ protected final DataType type; -+ protected final String[] paths; -+ -+ public DataWalkerTypePaths(final DataType type, final String... paths) { -+ this.type = type; -+ this.paths = paths; -+ } -+ -+ @Override -+ public final MapType walk(final MapType data, final long fromVersion, final long toVersion) { -+ for (final String path : this.paths) { -+ final Object current = data.getGeneric(path); -+ if (current == null) { -+ continue; -+ } -+ -+ final Object converted = this.type.convert((T)current, fromVersion, toVersion); -+ -+ if (converted != null) { -+ data.setGeneric(path, converted); -+ } -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1e81a1e46a9c0ffceb564a7b1fc4d1b51009f3f7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/generic/WalkerUtils.java -@@ -0,0 +1,127 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.generic; -+ -+import ca.spottedleaf.dataconverter.minecraft.converters.helpers.RenameHelper; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCDataType; -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCValueType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import java.util.ArrayList; -+ -+public final class WalkerUtils { -+ -+ public static void convert(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final MapType map = data.getMap(path); -+ if (map != null) { -+ final MapType replace = type.convert(map, fromVersion, toVersion); -+ if (replace != null) { -+ data.setMap(path, replace); -+ } -+ } -+ } -+ -+ public static void convertList(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final ListType list = data.getList(path, ObjectType.MAP); -+ if (list != null) { -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ final MapType replace = type.convert(list.getMap(i), fromVersion, toVersion); -+ if (replace != null) { -+ list.setMap(i, replace); -+ } -+ } -+ } -+ } -+ -+ public static void convert(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final Object value = data.getGeneric(path); -+ if (value != null) { -+ final Object converted = type.convert(value, fromVersion, toVersion); -+ if (converted != null) { -+ data.setGeneric(path, converted); -+ } -+ } -+ } -+ -+ public static void convert(final MCValueType type, final ListType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ for (int i = 0, len = data.size(); i < len; ++i) { -+ final Object value = data.getGeneric(i); -+ final Object converted = type.convert(value, fromVersion, toVersion); -+ if (converted != null) { -+ data.setGeneric(i, converted); -+ } -+ } -+ } -+ -+ public static void convertList(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final ListType list = data.getListUnchecked(path); -+ if (list != null) { -+ convert(type, list, fromVersion, toVersion); -+ } -+ } -+ -+ public static void convertKeys(final MCValueType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ final MapType map = data.getMap(path); -+ if (map != null) { -+ convertKeys(type, map, fromVersion, toVersion); -+ } -+ } -+ -+ public static void convertKeys(final MCValueType type, final MapType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ RenameHelper.renameKeys(data, (final String input) -> { -+ return (String)type.convert(input, fromVersion, toVersion); -+ }); -+ } -+ -+ public static void convertValues(final MCDataType type, final MapType data, final String path, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ convertValues(type, data.getMap(path), fromVersion, toVersion); -+ } -+ -+ public static void convertValues(final MCDataType type, final MapType data, final long fromVersion, final long toVersion) { -+ if (data == null) { -+ return; -+ } -+ -+ for (final String key : data.keys()) { -+ final MapType value = data.getMap(key); -+ if (value != null) { -+ final MapType replace = type.convert(value, fromVersion, toVersion); -+ if (replace != null) { -+ // no CME, key is in map already -+ data.setMap(key, replace); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..14e291efd864d97dcf83db01c09b9daaae1949bd ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/item_name/DataWalkerItemNames.java -@@ -0,0 +1,11 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.item_name; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+ -+public final class DataWalkerItemNames extends DataWalkerTypePaths { -+ -+ public DataWalkerItemNames(final String... paths) { -+ super(MCTypeRegistry.ITEM_NAME, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5b4402c3cc4e68e9c591e8bbb4a2542d8e2214d4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItemLists.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerListPaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerItemLists extends DataWalkerListPaths, MapType> { -+ -+ public DataWalkerItemLists(final String... paths) { -+ super(MCTypeRegistry.ITEM_STACK, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java -new file mode 100644 -index 0000000000000000000000000000000000000000..04770e8378ac8784895cdfe400a47b0b601c2187 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/itemstack/DataWalkerItems.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.itemstack; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public class DataWalkerItems extends DataWalkerTypePaths, MapType> { -+ -+ public DataWalkerItems(final String... paths) { -+ super(MCTypeRegistry.ITEM_STACK, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d9cc21bf41cb4b377752b684f8e59818cd620103 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/minecraft/walkers/tile_entity/DataWalkerTileEntities.java -@@ -0,0 +1,12 @@ -+package ca.spottedleaf.dataconverter.minecraft.walkers.tile_entity; -+ -+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry; -+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.DataWalkerTypePaths; -+import ca.spottedleaf.dataconverter.types.MapType; -+ -+public final class DataWalkerTileEntities extends DataWalkerTypePaths, MapType> { -+ -+ public DataWalkerTileEntities(final String... paths) { -+ super(MCTypeRegistry.TILE_ENTITY, paths); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..19f7e95f754e8385bbe60fd2fb7fc95b6a4ebd7c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ListType.java -@@ -0,0 +1,272 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public interface ListType { -+ -+ public TypeUtil getTypeUtil(); -+ -+ @Override -+ public int hashCode(); -+ -+ @Override -+ public boolean equals(final Object other); -+ -+ // Provides a deep copy of this list -+ public ListType copy(); -+ -+ // returns NONE if no type has been assigned. if NONE, then this list is also empty. It is not true on the other hand that an empty list has no type. -+ public ObjectType getType(); -+ -+ public int size(); -+ -+ public void remove(final int index); -+ -+ public default Object getGeneric(final int index) { -+ switch (this.getType()) { -+ case NONE: -+ throw new IllegalStateException("List is empty and has no type"); -+ case BYTE: -+ return Byte.valueOf(this.getByte(index)); -+ case SHORT: -+ return Short.valueOf(this.getShort(index)); -+ case INT: -+ return Integer.valueOf(this.getInt(index)); -+ case LONG: -+ return Long.valueOf(this.getLong(index)); -+ case FLOAT: -+ return Float.valueOf(this.getFloat(index)); -+ case DOUBLE: -+ return Double.valueOf(this.getDouble(index)); -+ case NUMBER: -+ return this.getNumber(index); -+ case BYTE_ARRAY: -+ return this.getBytes(index); -+ case SHORT_ARRAY: -+ return this.getShorts(index); -+ case INT_ARRAY: -+ return this.getInts(index); -+ case LONG_ARRAY: -+ return this.getLongs(index); -+ case LIST: -+ return this.getList(index); -+ case MAP: -+ return this.getMap(index); -+ case STRING: -+ return this.getString(index); -+ default: -+ throw new UnsupportedOperationException(this.getType().name()); -+ } -+ } -+ -+ public default void setGeneric(final int index, final Object to) { -+ if (to instanceof Number) { -+ if (to instanceof Byte) { -+ this.setByte(index, ((Byte)to).byteValue()); -+ return; -+ } else if (to instanceof Short) { -+ this.setShort(index, ((Short)to).shortValue()); -+ return; -+ } else if (to instanceof Integer) { -+ this.setInt(index, ((Integer)to).intValue()); -+ return; -+ } else if (to instanceof Long) { -+ this.setLong(index, ((Long)to).longValue()); -+ return; -+ } else if (to instanceof Float) { -+ this.setFloat(index, ((Float)to).floatValue()); -+ return; -+ } else if (to instanceof Double) { -+ this.setDouble(index, ((Double)to).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (to instanceof MapType) { -+ this.setMap(index, (MapType)to); -+ return; -+ } else if (to instanceof ListType) { -+ this.setList(index, (ListType)to); -+ return; -+ } else if (to instanceof String) { -+ this.setString(index, (String)to); -+ return; -+ } else if (to.getClass().isArray()) { -+ if (to instanceof byte[]) { -+ this.setBytes(index, (byte[])to); -+ return; -+ } else if (to instanceof short[]) { -+ this.setShorts(index, (short[])to); -+ return; -+ } else if (to instanceof int[]) { -+ this.setInts(index, (int[])to); -+ return; -+ } else if (to instanceof long[]) { -+ this.setLongs(index, (long[])to); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); -+ } -+ -+ // types here are strict. if the type on get does not match the underlying type, will throw. -+ -+ public Number getNumber(final int index); -+ -+ // if the value at index is a Number but not a byte, then returns the number casted to byte. If the value at the index is not a number, then throws -+ public byte getByte(final int index); -+ -+ public void setByte(final int index, final byte to); -+ -+ // if the value at index is a Number but not a short, then returns the number casted to short. If the value at the index is not a number, then throws -+ public short getShort(final int index); -+ -+ public void setShort(final int index, final short to); -+ -+ // if the value at index is a Number but not a int, then returns the number casted to int. If the value at the index is not a number, then throws -+ public int getInt(final int index); -+ -+ public void setInt(final int index, final int to); -+ -+ // if the value at index is a Number but not a long, then returns the number casted to long. If the value at the index is not a number, then throws -+ public long getLong(final int index); -+ -+ public void setLong(final int index, final long to); -+ -+ // if the value at index is a Number but not a float, then returns the number casted to float. If the value at the index is not a number, then throws -+ public float getFloat(final int index); -+ -+ public void setFloat(final int index, final float to); -+ -+ // if the value at index is a Number but not a double, then returns the number casted to double. If the value at the index is not a number, then throws -+ public double getDouble(final int index); -+ -+ public void setDouble(final int index, final double to); -+ -+ public byte[] getBytes(final int index); -+ -+ public void setBytes(final int index, final byte[] to); -+ -+ public short[] getShorts(final int index); -+ -+ public void setShorts(final int index, final short[] to); -+ -+ public int[] getInts(final int index); -+ -+ public void setInts(final int index, final int[] to); -+ -+ public long[] getLongs(final int index); -+ -+ public void setLongs(final int index, final long[] to); -+ -+ public ListType getList(final int index); -+ -+ public void setList(final int index, final ListType list); -+ -+ public MapType getMap(final int index); -+ -+ public void setMap(final int index, final MapType to); -+ -+ public String getString(final int index); -+ -+ public void setString(final int index, final String to); -+ -+ public default void addGeneric(final Object to) { -+ if (to instanceof Number) { -+ if (to instanceof Byte) { -+ this.addByte(((Byte)to).byteValue()); -+ return; -+ } else if (to instanceof Short) { -+ this.addShort(((Short)to).shortValue()); -+ return; -+ } else if (to instanceof Integer) { -+ this.addInt(((Integer)to).intValue()); -+ return; -+ } else if (to instanceof Long) { -+ this.addLong(((Long)to).longValue()); -+ return; -+ } else if (to instanceof Float) { -+ this.addFloat(((Float)to).floatValue()); -+ return; -+ } else if (to instanceof Double) { -+ this.addDouble(((Double)to).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (to instanceof MapType) { -+ this.addMap((MapType)to); -+ return; -+ } else if (to instanceof ListType) { -+ this.addList((ListType)to); -+ return; -+ } else if (to instanceof String) { -+ this.addString((String)to); -+ return; -+ } else if (to.getClass().isArray()) { -+ if (to instanceof byte[]) { -+ this.addByteArray((byte[])to); -+ return; -+ } else if (to instanceof short[]) { -+ this.addShortArray((short[])to); -+ return; -+ } else if (to instanceof int[]) { -+ this.addIntArray((int[])to); -+ return; -+ } else if (to instanceof long[]) { -+ this.addLongArray((long[])to); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + to + " is not a valid type!"); -+ } -+ -+ public void addByte(final byte b); -+ -+ public void addByte(final int index, final byte b); -+ -+ public void addShort(final short s); -+ -+ public void addShort(final int index, final short s); -+ -+ public void addInt(final int i); -+ -+ public void addInt(final int index, final int i); -+ -+ public void addLong(final long l); -+ -+ public void addLong(final int index, final long l); -+ -+ public void addFloat(final float f); -+ -+ public void addFloat(final int index, final float f); -+ -+ public void addDouble(final double d); -+ -+ public void addDouble(final int index, final double d); -+ -+ public void addByteArray(final byte[] arr); -+ -+ public void addByteArray(final int index, final byte[] arr); -+ -+ public void addShortArray(final short[] arr); -+ -+ public void addShortArray(final int index, final short[] arr); -+ -+ public void addIntArray(final int[] arr); -+ -+ public void addIntArray(final int index, final int[] arr); -+ -+ public void addLongArray(final long[] arr); -+ -+ public void addLongArray(final int index, final long[] arr); -+ -+ public void addList(final ListType list); -+ -+ public void addList(final int index, final ListType list); -+ -+ public void addMap(final MapType map); -+ -+ public void addMap(final int index, final MapType map); -+ -+ public void addString(final String string); -+ -+ public void addString(final int index, final String string); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1bd7809b7ee198d1ceeb2756b44105e1b0de956e ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/MapType.java -@@ -0,0 +1,219 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+import java.util.Set; -+ -+public interface MapType { -+ -+ public TypeUtil getTypeUtil(); -+ -+ @Override -+ public int hashCode(); -+ -+ @Override -+ public boolean equals(final Object other); -+ -+ public int size(); -+ -+ public boolean isEmpty(); -+ -+ public void clear(); -+ -+ public Set keys(); -+ -+ // Provides a deep copy of this map -+ public MapType copy(); -+ -+ public boolean hasKey(final K key); -+ -+ public boolean hasKey(final K key, final ObjectType type); -+ -+ public void remove(final K key); -+ -+ public Object getGeneric(final K key); -+ -+ // types here are not strict. if the key maps to a different type, default is always returned -+ // if default is not a parameter, then default is always null -+ -+ public Number getNumber(final K key); -+ -+ public Number getNumber(final K key, final Number dfl); -+ -+ public boolean getBoolean(final K key); -+ -+ public boolean getBoolean(final K key, final boolean dfl); -+ -+ public void setBoolean(final K key, final boolean val); -+ -+ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns 0 -+ public byte getByte(final K key); -+ -+ // if the mapped value is a Number but not a byte, then the number is casted to byte. If the mapped value does not exist or is not a number, returns dfl -+ public byte getByte(final K key, final byte dfl); -+ -+ public void setByte(final K key, final byte val); -+ -+ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns 0 -+ public short getShort(final K key); -+ -+ // if the mapped value is a Number but not a short, then the number is casted to short. If the mapped value does not exist or is not a number, returns dfl -+ public short getShort(final K key, final short dfl); -+ -+ public void setShort(final K key, final short val); -+ -+ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns 0 -+ public int getInt(final K key); -+ -+ // if the mapped value is a Number but not a int, then the number is casted to int. If the mapped value does not exist or is not a number, returns dfl -+ public int getInt(final K key, final int dfl); -+ -+ public void setInt(final K key, final int val); -+ -+ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns 0 -+ public long getLong(final K key); -+ -+ // if the mapped value is a Number but not a long, then the number is casted to long. If the mapped value does not exist or is not a number, returns dfl -+ public long getLong(final K key, final long dfl); -+ -+ public void setLong(final K key, final long val); -+ -+ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns 0 -+ public float getFloat(final K key); -+ -+ // if the mapped value is a Number but not a float, then the number is casted to float. If the mapped value does not exist or is not a number, returns dfl -+ public float getFloat(final K key, final float dfl); -+ -+ public void setFloat(final K key, final float val); -+ -+ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns 0 -+ public double getDouble(final K key); -+ -+ // if the mapped value is a Number but not a double, then the number is casted to double. If the mapped value does not exist or is not a number, returns dfl -+ public double getDouble(final K key, final double dfl); -+ -+ public void setDouble(final K key, final double val); -+ -+ public byte[] getBytes(final K key); -+ -+ public byte[] getBytes(final K key, final byte[] dfl); -+ -+ public void setBytes(final K key, final byte[] val); -+ -+ public short[] getShorts(final K key); -+ -+ public short[] getShorts(final K key, final short[] dfl); -+ -+ public void setShorts(final K key, final short[] val); -+ -+ public int[] getInts(final K key); -+ -+ public int[] getInts(final K key, final int[] dfl); -+ -+ public void setInts(final K key, final int[] val); -+ -+ public long[] getLongs(final K key); -+ -+ public long[] getLongs(final K key, final long[] dfl); -+ -+ public void setLongs(final K key, final long[] val); -+ -+ public ListType getListUnchecked(final K key); -+ -+ public ListType getListUnchecked(final K key, final ListType dfl); -+ -+ public default ListType getList(final K key, final ObjectType type) { -+ return this.getList(key, type, null); -+ } -+ -+ public default ListType getOrCreateList(final K key, final ObjectType type) { -+ ListType ret = this.getList(key, type); -+ if (ret == null) { -+ this.setList(key, ret = this.getTypeUtil().createEmptyList()); -+ } -+ -+ return ret; -+ } -+ -+ public default ListType getList(final K key, final ObjectType type, final ListType dfl) { -+ final ListType ret = this.getListUnchecked(key, null); -+ final ObjectType retType; -+ if (ret != null && ((retType = ret.getType()) == type || retType == ObjectType.UNDEFINED)) { -+ return ret; -+ } else { -+ return dfl; -+ } -+ } -+ -+ public void setList(final K key, final ListType val); -+ -+ public MapType getMap(final K key); -+ -+ public default MapType getOrCreateMap(final K key) { -+ MapType ret = this.getMap(key); -+ if (ret == null) { -+ this.setMap(key, ret = this.getTypeUtil().createEmptyMap()); -+ } -+ -+ return ret; -+ } -+ -+ public MapType getMap(final K key, final MapType dfl); -+ -+ public void setMap(final K key, final MapType val); -+ -+ public String getString(final K key); -+ -+ public String getString(final K key, final String dfl); -+ -+ public void setString(final K key, final String val); -+ -+ public default void setGeneric(final K key, final Object value) { -+ if (value instanceof Boolean) { -+ this.setBoolean(key, ((Boolean)value).booleanValue()); -+ } else if (value instanceof Number) { -+ if (value instanceof Byte) { -+ this.setByte(key, ((Byte)value).byteValue()); -+ return; -+ } else if (value instanceof Short) { -+ this.setShort(key, ((Short)value).shortValue()); -+ return; -+ } else if (value instanceof Integer) { -+ this.setInt(key, ((Integer)value).intValue()); -+ return; -+ } else if (value instanceof Long) { -+ this.setLong(key, ((Long)value).longValue()); -+ return; -+ } else if (value instanceof Float) { -+ this.setFloat(key, ((Float)value).floatValue()); -+ return; -+ } else if (value instanceof Double) { -+ this.setDouble(key, ((Double)value).doubleValue()); -+ return; -+ } // else fall through to throw -+ } else if (value instanceof MapType) { -+ this.setMap(key, (MapType)value); -+ return; -+ } else if (value instanceof ListType) { -+ this.setList(key, (ListType)value); -+ return; -+ } else if (value instanceof String) { -+ this.setString(key, (String)value); -+ return; -+ } else if (value.getClass().isArray()) { -+ if (value instanceof byte[]) { -+ this.setBytes(key, (byte[])value); -+ return; -+ } else if (value instanceof short[]) { -+ this.setShorts(key, (short[])value); -+ return; -+ } else if (value instanceof int[]) { -+ this.setInts(key, (int[])value); -+ return; -+ } else if (value instanceof long[]) { -+ this.setLongs(key, (long[])value); -+ return; -+ } // else fall through to throw -+ } -+ -+ throw new IllegalArgumentException("Object " + value + " is not a valid type!"); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1aab91233ddb98c3af5d424bac120891f1ee16c7 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/ObjectType.java -@@ -0,0 +1,72 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public enum ObjectType { -+ NONE(null), -+ BYTE(Byte.class), -+ SHORT(Short.class), -+ INT(Integer.class), -+ LONG(Long.class), -+ FLOAT(Float.class), -+ DOUBLE(Double.class), -+ NUMBER(Number.class), -+ BYTE_ARRAY(byte[].class), -+ SHORT_ARRAY(short[].class), -+ INT_ARRAY(int[].class), -+ LONG_ARRAY(long[].class), -+ LIST(ListType.class), -+ MAP(MapType.class), -+ STRING(String.class), -+ UNDEFINED(null); -+ -+ private final Class clazz; -+ private final boolean isNumber; -+ -+ private ObjectType(final Class clazz) { -+ this.clazz = clazz; -+ this.isNumber = clazz != null && Number.class.isAssignableFrom(clazz); -+ } -+ -+ public boolean isNumber() { -+ return this.isNumber; -+ } -+ -+ public Class getObjectClass() { -+ return this.clazz; -+ } -+ -+ public static ObjectType getType(final Object object) { -+ if (object instanceof Number) { -+ if (object instanceof Byte) { -+ return BYTE; -+ } else if (object instanceof Short) { -+ return SHORT; -+ } else if (object instanceof Integer) { -+ return INT; -+ } else if (object instanceof Long) { -+ return LONG; -+ } else if (object instanceof Float) { -+ return FLOAT; -+ } else if (object instanceof Double) { -+ return DOUBLE; -+ } // else return null -+ } else if (object instanceof MapType) { -+ return MAP; -+ } else if (object instanceof ListType) { -+ return LIST; -+ } else if (object instanceof String) { -+ return STRING; -+ } else if (object.getClass().isArray()) { -+ if (object instanceof byte[]) { -+ return BYTE_ARRAY; -+ } else if (object instanceof short[]) { -+ return SHORT_ARRAY; -+ } else if (object instanceof int[]) { -+ return INT_ARRAY; -+ } else if (object instanceof long[]) { -+ return LONG_ARRAY; -+ } // else return null -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..156a2ea46f8f88a02e88b50d7bb7be82ecd41919 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/TypeUtil.java -@@ -0,0 +1,9 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+public interface TypeUtil { -+ -+ public ListType createEmptyList(); -+ -+ public MapType createEmptyMap(); -+ -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/Types.java b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2ab9e3b579f20c9a189518496c522155630a36c4 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/Types.java -@@ -0,0 +1,15 @@ -+package ca.spottedleaf.dataconverter.types; -+ -+import ca.spottedleaf.dataconverter.types.json.JsonTypeCompressedUtil; -+import ca.spottedleaf.dataconverter.types.json.JsonTypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTTypeUtil; -+ -+public interface Types { -+ -+ public static final TypeUtil NBT = new NBTTypeUtil(); -+ -+ public static final TypeUtil JSON = new JsonTypeUtil(); -+ -+ // why does this exist -+ public static final TypeUtil JSON_COMPRESSED = new JsonTypeCompressedUtil(); -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f6f57cb3a215876976b5eecae810b8b20925f2e2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonListType.java -@@ -0,0 +1,415 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+ -+public final class JsonListType implements ListType { -+ -+ protected final JsonArray array; -+ protected final boolean compressed; -+ -+ public JsonListType(final boolean compressed) { -+ this.array = new JsonArray(); -+ this.compressed = compressed; -+ } -+ -+ public JsonListType(final JsonArray array, final boolean compressed) { -+ this.array = array; -+ this.compressed = compressed; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (obj == null || obj.getClass() != JsonListType.class) { -+ return false; -+ } -+ -+ return this.array.equals(((JsonListType)obj).array); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.array.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "JsonListType{" + -+ "array=" + this.array + -+ ", compressed=" + this.compressed + -+ '}'; -+ } -+ -+ public JsonArray getJson() { -+ return this.array; -+ } -+ -+ @Override -+ public ListType copy() { -+ return new JsonListType(JsonTypeUtil.copyJson(this.array), this.compressed); -+ } -+ -+ @Override -+ public ObjectType getType() { -+ return ObjectType.UNDEFINED; -+ } -+ -+ @Override -+ public int size() { -+ return this.array.size(); -+ } -+ -+ @Override -+ public void remove(final int index) { -+ this.array.remove(index); -+ } -+ -+ @Override -+ public Number getNumber(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (this.compressed && primitive.isString()) { -+ try { -+ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public byte getByte(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.byteValue(); -+ } -+ -+ @Override -+ public void setByte(final int index, final byte to) { -+ this.array.set(index, new JsonPrimitive(Byte.valueOf(to))); -+ } -+ -+ @Override -+ public short getShort(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.shortValue(); -+ } -+ -+ @Override -+ public void setShort(final int index, final short to) { -+ this.array.set(index, new JsonPrimitive(Short.valueOf(to))); -+ } -+ -+ @Override -+ public int getInt(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.intValue(); -+ } -+ -+ @Override -+ public void setInt(final int index, final int to) { -+ this.array.set(index, new JsonPrimitive(Integer.valueOf(to))); -+ } -+ -+ @Override -+ public long getLong(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.longValue(); -+ } -+ -+ @Override -+ public void setLong(final int index, final long to) { -+ this.array.set(index, new JsonPrimitive(Long.valueOf(to))); -+ } -+ -+ @Override -+ public float getFloat(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.floatValue(); -+ } -+ -+ @Override -+ public void setFloat(final int index, final float to) { -+ this.array.set(index, new JsonPrimitive(Float.valueOf(to))); -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ final Number number = this.getNumber(index); -+ -+ return number == null ? 0 : number.doubleValue(); -+ } -+ -+ @Override -+ public void setDouble(final int index, final double to) { -+ this.array.set(index, new JsonPrimitive(Double.valueOf(to))); -+ } -+ -+ @Override -+ public byte[] getBytes(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setBytes(final int index, final byte[] to) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public short[] getShorts(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setShorts(final int index, final short[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setInts(final int index, final int[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public long[] getLongs(final int index) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setLongs(final int index, final long[] to) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public ListType getList(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonArray) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } -+ return null; -+ } -+ -+ @Override -+ public void setList(final int index, final ListType list) { -+ this.array.set(index, ((JsonListType)list).array); -+ } -+ -+ @Override -+ public MapType getMap(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonObject) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } -+ return null; -+ } -+ -+ @Override -+ public void setMap(final int index, final MapType to) { -+ this.array.set(index, ((JsonMapType)to).map); -+ } -+ -+ @Override -+ public String getString(final int index) { -+ final JsonElement element = this.array.get(index); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString() || (this.compressed && primitive.isNumber())) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public void setString(final int index, final String to) { -+ this.array.set(index, new JsonPrimitive(to)); -+ } -+ -+ @Override -+ public void addByte(final byte b) { -+ this.array.add(Byte.valueOf(b)); -+ } -+ -+ @Override -+ public void addByte(final int index, final byte b) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShort(final short s) { -+ this.array.add(Short.valueOf(s)); -+ } -+ -+ @Override -+ public void addShort(final int index, final short s) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addInt(final int i) { -+ this.array.add(Integer.valueOf(i)); -+ } -+ -+ @Override -+ public void addInt(final int index, final int i) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLong(final long l) { -+ this.array.add(Long.valueOf(l)); -+ } -+ -+ @Override -+ public void addLong(final int index, final long l) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addFloat(final float f) { -+ this.array.add(Float.valueOf(f)); -+ } -+ -+ @Override -+ public void addFloat(final int index, final float f) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addDouble(final double d) { -+ this.array.add(Double.valueOf(d)); -+ } -+ -+ @Override -+ public void addDouble(final int index, final double d) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addByteArray(final byte[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addByteArray(final int index, final byte[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final short[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final int index, final short[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int index, final int[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLongArray(final long[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addLongArray(final int index, final long[] arr) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addList(final ListType list) { -+ this.array.add(((JsonListType)list).array); -+ } -+ -+ @Override -+ public void addList(final int index, final ListType list) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addMap(final MapType map) { -+ this.array.add(((JsonMapType)map).map); -+ } -+ -+ @Override -+ public void addMap(final int index, final MapType map) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addString(final String string) { -+ this.array.add(string); -+ } -+ -+ @Override -+ public void addString(final int index, final String string) { -+ // doesn't implement any methods for adding at index... yee haw... -+ throw new UnsupportedOperationException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c89bcc4b9974dd65bad9b096cccf8a4369d47f4f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonMapType.java -@@ -0,0 +1,450 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import java.util.LinkedHashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class JsonMapType implements MapType { -+ -+ protected final JsonObject map; -+ protected final boolean compressed; -+ -+ public JsonMapType(final boolean compressed) { -+ this.map = new JsonObject(); -+ this.compressed = compressed; -+ } -+ -+ public JsonMapType(final JsonObject map, final boolean compressed) { -+ this.map = map; -+ this.compressed = compressed; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return this.compressed ? Types.JSON_COMPRESSED : Types.JSON; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (obj == null || obj.getClass() != JsonMapType.class) { -+ return false; -+ } -+ -+ return this.map.equals(((JsonMapType)obj).map); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.map.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "JsonMapType{" + -+ "map=" + this.map + -+ ", compressed=" + this.compressed + -+ '}'; -+ } -+ -+ public JsonObject getJson() { -+ return this.map; -+ } -+ -+ @Override -+ public int size() { -+ return this.map.entrySet().size(); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return this.map.entrySet().isEmpty(); -+ } -+ -+ @Override -+ public void clear() { -+ this.map.entrySet().clear(); -+ } -+ -+ @Override -+ public Set keys() { -+ // ah shit. no keyset method -+ final Set keys = new LinkedHashSet<>(); -+ -+ for (final Map.Entry entry : this.map.entrySet()) { -+ keys.add(entry.getKey()); -+ } -+ -+ return keys; -+ } -+ -+ @Override -+ public MapType copy() { -+ return new JsonMapType(JsonTypeUtil.copyJson(this.map), this.compressed); -+ } -+ -+ @Override -+ public boolean hasKey(final String key) { -+ return this.map.has(key); -+ } -+ -+ @Override -+ public boolean hasKey(final String key, final ObjectType type) { -+ final JsonElement element = this.map.get(key); -+ if (element == null) { -+ return false; -+ } -+ -+ if (type == ObjectType.UNDEFINED) { -+ return true; -+ } -+ -+ if (element.isJsonArray()) { -+ return type == ObjectType.LIST; -+ } else if (element.isJsonObject()) { -+ return type == ObjectType.MAP; -+ } else if (element.isJsonNull()) { -+ return false; -+ } -+ -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString()) { -+ return type == ObjectType.STRING || (this.compressed && type == ObjectType.NUMBER); -+ } else if (primitive.isBoolean()) { -+ return type.isNumber(); -+ } else { -+ // is number -+ final Number number = primitive.getAsNumber(); -+ if (number instanceof Byte) { -+ return type == ObjectType.BYTE || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Short) { -+ return type == ObjectType.SHORT || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Integer) { -+ return type == ObjectType.INT || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Long) { -+ return type == ObjectType.LONG || (this.compressed && type == ObjectType.STRING); -+ } else if (number instanceof Float) { -+ return type == ObjectType.FLOAT || (this.compressed && type == ObjectType.STRING); -+ } else { -+ return type == ObjectType.DOUBLE || (this.compressed && type == ObjectType.STRING); -+ } -+ } -+ } -+ -+ @Override -+ public void remove(final String key) { -+ this.map.remove(key); -+ } -+ -+ @Override -+ public Object getGeneric(final String key) { -+ final JsonElement element = this.map.get(key); -+ if (element == null || element.isJsonNull()) { -+ return null; -+ } else if (element.isJsonObject()) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } else if (element.isJsonArray()) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } else { -+ // primitive -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isString()) { -+ return primitive.getAsString(); -+ } else if (primitive.isBoolean()) { -+ return Boolean.valueOf(primitive.getAsBoolean()); -+ } else { -+ throw new IllegalStateException("Unknown json object " + element); -+ } -+ } -+ } -+ -+ @Override -+ public Number getNumber(final String key) { -+ return this.getNumber(key, null); -+ } -+ -+ @Override -+ public Number getNumber(final String key, final Number dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (this.compressed && primitive.isString()) { -+ try { -+ return Integer.valueOf(Integer.parseInt(primitive.getAsString())); -+ } catch (final NumberFormatException ex) { -+ return null; -+ } -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key) { -+ return this.getBoolean(key, false); -+ } -+ -+ @Override -+ public boolean getBoolean(final String key, final boolean dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isNumber()) { -+ return primitive.getAsNumber().byteValue() != 0; -+ } else if (primitive.isBoolean()) { -+ return primitive.getAsBoolean(); -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setBoolean(final String key, final boolean val) { -+ this.map.addProperty(key, Boolean.valueOf(val)); -+ } -+ -+ @Override -+ public byte getByte(final String key) { -+ return this.getByte(key, (byte)0); -+ } -+ -+ @Override -+ public byte getByte(final String key, final byte dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.byteValue(); -+ } -+ -+ @Override -+ public void setByte(final String key, final byte val) { -+ this.map.addProperty(key, Byte.valueOf(val)); -+ } -+ -+ @Override -+ public short getShort(final String key) { -+ return this.getShort(key, (short)0); -+ } -+ -+ @Override -+ public short getShort(final String key, final short dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.shortValue(); -+ } -+ -+ @Override -+ public void setShort(final String key, final short val) { -+ this.map.addProperty(key, Short.valueOf(val)); -+ } -+ -+ @Override -+ public int getInt(final String key) { -+ return this.getInt(key, 0); -+ } -+ -+ @Override -+ public int getInt(final String key, final int dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.intValue(); -+ } -+ -+ @Override -+ public void setInt(final String key, final int val) { -+ this.map.addProperty(key, Integer.valueOf(val)); -+ } -+ -+ @Override -+ public long getLong(final String key) { -+ return this.getLong(key, 0L); -+ } -+ -+ @Override -+ public long getLong(final String key, final long dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.longValue(); -+ } -+ -+ @Override -+ public void setLong(final String key, final long val) { -+ this.map.addProperty(key, Long.valueOf(val)); -+ } -+ -+ @Override -+ public float getFloat(final String key) { -+ return this.getFloat(key, 0.0F); -+ } -+ -+ @Override -+ public float getFloat(final String key, final float dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.floatValue(); -+ } -+ -+ @Override -+ public void setFloat(final String key, final float val) { -+ this.map.addProperty(key, Float.valueOf(val)); -+ } -+ -+ @Override -+ public double getDouble(final String key) { -+ return this.getDouble(key, 0.0D); -+ } -+ -+ @Override -+ public double getDouble(final String key, final double dfl) { -+ final Number ret = this.getNumber(key, null); -+ return ret == null ? dfl : ret.doubleValue(); -+ } -+ -+ @Override -+ public void setDouble(final String key, final double val) { -+ this.map.addProperty(key, Double.valueOf(val)); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key) { -+ return this.getBytes(key, null); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key, final byte[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setBytes(final String key, final byte[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public short[] getShorts(final String key) { -+ return this.getShorts(key, null); -+ } -+ -+ @Override -+ public short[] getShorts(final String key, final short[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setShorts(final String key, final short[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final String key) { -+ return this.getInts(key, null); -+ } -+ -+ @Override -+ public int[] getInts(final String key, final int[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setInts(final String key, final int[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public long[] getLongs(final String key) { -+ return this.getLongs(key, null); -+ } -+ -+ @Override -+ public long[] getLongs(final String key, final long[] dfl) { -+ return dfl; -+ } -+ -+ @Override -+ public void setLongs(final String key, final long[] val) { -+ // JSON does not support raw primitive arrays -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key) { -+ return this.getListUnchecked(key, null); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key, final ListType dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonArray) { -+ return new JsonListType((JsonArray)element, this.compressed); -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setList(final String key, final ListType val) { -+ this.map.add(key, ((JsonListType)val).getJson()); -+ } -+ -+ @Override -+ public MapType getMap(final String key) { -+ return this.getMap(key, null); -+ } -+ -+ @Override -+ public MapType getMap(final String key, final MapType dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonObject) { -+ return new JsonMapType((JsonObject)element, this.compressed); -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setMap(final String key, final MapType val) { -+ this.map.add(key, ((JsonMapType)val).map); -+ } -+ -+ @Override -+ public String getString(final String key) { -+ return this.getString(key, null); -+ } -+ -+ @Override -+ public String getString(final String key, final String dfl) { -+ final JsonElement element = this.map.get(key); -+ if (element instanceof JsonPrimitive) { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isString()) { -+ return primitive.getAsString(); -+ } else if (this.compressed && primitive.isNumber()) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ return dfl; -+ } -+ -+ @Override -+ public void setString(final String key, final String val) { -+ this.map.addProperty(key, val); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9c3093b66b847b5248bde923243fce78842bf67f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeCompressedUtil.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+ -+public final class JsonTypeCompressedUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new JsonListType(true); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new JsonMapType(true); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9410ae68395a09c7710bdbb2ccc6acf6633cad23 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/json/JsonTypeUtil.java -@@ -0,0 +1,81 @@ -+package ca.spottedleaf.dataconverter.types.json; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.nbt.NBTListType; -+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonNull; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import com.google.gson.internal.Streams; -+import com.google.gson.stream.JsonReader; -+import java.io.StringReader; -+import java.util.Map; -+ -+public final class JsonTypeUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new JsonListType(false); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new JsonMapType(false); -+ } -+ -+ public static T copyJson(final T from) { -+ // This is stupidly inefficient. However, deepCopy() is not exposed in this gson version. -+ final String out = from.toString(); -+ -+ return (T)Streams.parse(new JsonReader(new StringReader(out))); -+ } -+ -+ private static Object convertToGenericNBT(final JsonElement element, final boolean compressed) { -+ if (element instanceof JsonObject) { -+ return convertJsonToNBT(new JsonMapType((JsonObject)element, compressed)); -+ } else if (element instanceof JsonArray) { -+ return convertJsonToNBT(new JsonListType((JsonArray)element, compressed)); -+ } else if (element instanceof JsonNull) { -+ return null; -+ } else { -+ final JsonPrimitive primitive = (JsonPrimitive)element; -+ if (primitive.isBoolean()) { -+ return primitive.getAsBoolean() ? Byte.valueOf((byte)1) : Byte.valueOf((byte)0); -+ } else if (primitive.isNumber()) { -+ return primitive.getAsNumber(); -+ } else if (primitive.isString()) { -+ return primitive.getAsString(); -+ } -+ } -+ -+ throw new IllegalStateException("Unrecognized type " + element); -+ } -+ -+ public static NBTMapType convertJsonToNBT(final JsonMapType json) { -+ final NBTMapType ret = new NBTMapType(); -+ for (final Map.Entry entry : json.map.entrySet()) { -+ final Object obj = convertToGenericNBT(entry.getValue(), json.compressed); -+ if (obj == null) { -+ continue; -+ } -+ -+ ret.setGeneric(entry.getKey(), obj); -+ } -+ -+ return ret; -+ } -+ -+ public static NBTListType convertJsonToNBT(final JsonListType json) { -+ final NBTListType ret = new NBTListType(); -+ -+ for (int i = 0, len = json.size(); i < len; ++i) { -+ ret.addGeneric(convertToGenericNBT(json.array.get(i), json.compressed)); -+ } -+ -+ return ret; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bf4e9ea17222cfa8f7cee9e46775302c9c2e6328 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTListType.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.nbt.ByteArrayTag; -+import net.minecraft.nbt.ByteTag; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.DoubleTag; -+import net.minecraft.nbt.FloatTag; -+import net.minecraft.nbt.IntArrayTag; -+import net.minecraft.nbt.IntTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.LongArrayTag; -+import net.minecraft.nbt.LongTag; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.ShortTag; -+import net.minecraft.nbt.StringTag; -+import net.minecraft.nbt.Tag; -+ -+public final class NBTListType implements ListType { -+ -+ private final ListTag list; -+ -+ public NBTListType() { -+ this.list = new ListTag(); -+ } -+ -+ public NBTListType(final ListTag tag) { -+ this.list = tag; -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return Types.NBT; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ if (obj == null || obj.getClass() != NBTListType.class) { -+ return false; -+ } -+ -+ return this.list.equals(((NBTListType)obj).list); -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.list.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "NBTListType{" + -+ "list=" + this.list + -+ '}'; -+ } -+ -+ public ListTag getTag() { -+ return this.list; -+ } -+ -+ @Override -+ public ListType copy() { -+ return new NBTListType(this.list.copy()); -+ } -+ -+ protected static ObjectType getType(final byte id) { -+ switch (id) { -+ case 0: // END -+ return ObjectType.NONE; -+ case 1: // BYTE -+ return ObjectType.BYTE; -+ case 2: // SHORT -+ return ObjectType.SHORT; -+ case 3: // INT -+ return ObjectType.INT; -+ case 4: // LONG -+ return ObjectType.LONG; -+ case 5: // FLOAT -+ return ObjectType.FLOAT; -+ case 6: // DOUBLE -+ return ObjectType.DOUBLE; -+ case 7: // BYTE_ARRAY -+ return ObjectType.BYTE_ARRAY; -+ case 8: // STRING -+ return ObjectType.STRING; -+ case 9: // LIST -+ return ObjectType.LIST; -+ case 10: // COMPOUND -+ return ObjectType.MAP; -+ case 11: // INT_ARRAY -+ return ObjectType.INT_ARRAY; -+ case 12: // LONG_ARRAY -+ return ObjectType.LONG_ARRAY; -+ default: -+ throw new IllegalStateException("Unknown type: " + id); -+ } -+ } -+ -+ @Override -+ public ObjectType getType() { -+ return getType(this.list.getElementType()); -+ } -+ -+ @Override -+ public int size() { -+ return this.list.size(); -+ } -+ -+ @Override -+ public void remove(final int index) { -+ this.list.remove(index); -+ } -+ -+ @Override -+ public Number getNumber(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsNumber(); -+ } -+ -+ @Override -+ public byte getByte(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsByte(); -+ } -+ -+ @Override -+ public void setByte(final int index, final byte to) { -+ this.list.set(index, ByteTag.valueOf(to)); -+ } -+ -+ @Override -+ public short getShort(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsShort(); -+ } -+ -+ @Override -+ public void setShort(final int index, final short to) { -+ this.list.set(index, ShortTag.valueOf(to)); -+ } -+ -+ @Override -+ public int getInt(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsInt(); -+ } -+ -+ @Override -+ public void setInt(final int index, final int to) { -+ this.list.set(index, IntTag.valueOf(to)); -+ } -+ -+ @Override -+ public long getLong(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsLong(); -+ } -+ -+ @Override -+ public void setLong(final int index, final long to) { -+ this.list.set(index, LongTag.valueOf(to)); -+ } -+ -+ @Override -+ public float getFloat(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ -+ @Override -+ public void setFloat(final int index, final float to) { -+ this.list.set(index, FloatTag.valueOf(to)); -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof NumericTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ -+ @Override -+ public void setDouble(final int index, final double to) { -+ this.list.set(index, DoubleTag.valueOf(to)); -+ } -+ -+ @Override -+ public byte[] getBytes(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof ByteArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ } -+ -+ @Override -+ public void setBytes(final int index, final byte[] to) { -+ this.list.set(index, new ByteArrayTag(to)); -+ } -+ -+ @Override -+ public short[] getShorts(final int index) { -+ // NBT does not support shorts -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setShorts(final int index, final short[] to) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof IntArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((IntArrayTag)tag).getAsIntArray(); -+ } -+ -+ @Override -+ public void setInts(final int index, final int[] to) { -+ this.list.set(index, new IntArrayTag(to)); -+ } -+ -+ @Override -+ public long[] getLongs(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof LongArrayTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ -+ @Override -+ public void setLongs(final int index, final long[] to) { -+ this.list.set(index, new LongArrayTag(to)); -+ } -+ -+ @Override -+ public ListType getList(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof ListTag)) { -+ throw new IllegalStateException(); -+ } -+ return new NBTListType((ListTag)tag); -+ } -+ -+ @Override -+ public void setList(final int index, final ListType list) { -+ this.list.set(index, ((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public MapType getMap(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof CompoundTag)) { -+ throw new IllegalStateException(); -+ } -+ return new NBTMapType((CompoundTag)tag); -+ } -+ -+ @Override -+ public void setMap(final int index, final MapType to) { -+ this.list.set(index, ((NBTMapType)to).getTag()); -+ } -+ -+ @Override -+ public String getString(final int index) { -+ final Tag tag = this.list.get(index); // does bound checking for us -+ if (!(tag instanceof StringTag)) { -+ throw new IllegalStateException(); -+ } -+ return ((StringTag)tag).getAsString(); -+ } -+ -+ @Override -+ public void setString(final int index, final String to) { -+ this.list.set(index, StringTag.valueOf(to)); -+ } -+ -+ @Override -+ public void addByte(final byte b) { -+ this.list.add(ByteTag.valueOf(b)); -+ } -+ -+ @Override -+ public void addByte(final int index, final byte b) { -+ this.list.add(index, ByteTag.valueOf(b)); -+ } -+ -+ @Override -+ public void addShort(final short s) { -+ this.list.add(ShortTag.valueOf(s)); -+ } -+ -+ @Override -+ public void addShort(final int index, final short s) { -+ this.list.add(index, ShortTag.valueOf(s)); -+ } -+ -+ @Override -+ public void addInt(final int i) { -+ this.list.add(IntTag.valueOf(i)); -+ } -+ -+ @Override -+ public void addInt(final int index, final int i) { -+ this.list.add(index, IntTag.valueOf(i)); -+ } -+ -+ @Override -+ public void addLong(final long l) { -+ this.list.add(LongTag.valueOf(l)); -+ } -+ -+ @Override -+ public void addLong(final int index, final long l) { -+ this.list.add(index, LongTag.valueOf(l)); -+ } -+ -+ @Override -+ public void addFloat(final float f) { -+ this.list.add(FloatTag.valueOf(f)); -+ } -+ -+ @Override -+ public void addFloat(final int index, final float f) { -+ this.list.add(index, FloatTag.valueOf(f)); -+ } -+ -+ @Override -+ public void addDouble(final double d) { -+ this.list.add(DoubleTag.valueOf(d)); -+ } -+ -+ @Override -+ public void addDouble(final int index, final double d) { -+ this.list.add(index, DoubleTag.valueOf(d)); -+ } -+ -+ @Override -+ public void addByteArray(final byte[] arr) { -+ this.list.add(new ByteArrayTag(arr)); -+ } -+ -+ @Override -+ public void addByteArray(final int index, final byte[] arr) { -+ this.list.add(index, new ByteArrayTag(arr)); -+ } -+ -+ @Override -+ public void addShortArray(final short[] arr) { -+ // NBT does not support short[] -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addShortArray(final int index, final short[] arr) { -+ // NBT does not support short[] -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void addIntArray(final int[] arr) { -+ this.list.add(new IntArrayTag(arr)); -+ } -+ -+ @Override -+ public void addIntArray(final int index, final int[] arr) { -+ this.list.add(index, new IntArrayTag(arr)); -+ } -+ -+ @Override -+ public void addLongArray(final long[] arr) { -+ this.list.add(new LongArrayTag(arr)); -+ } -+ -+ @Override -+ public void addLongArray(final int index, final long[] arr) { -+ this.list.add(index, new LongArrayTag(arr)); -+ } -+ -+ @Override -+ public void addList(final ListType list) { -+ this.list.add(((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public void addList(final int index, final ListType list) { -+ this.list.add(index, ((NBTListType)list).getTag()); -+ } -+ -+ @Override -+ public void addMap(final MapType map) { -+ this.list.add(((NBTMapType)map).getTag()); -+ } -+ -+ @Override -+ public void addMap(final int index, final MapType map) { -+ this.list.add(index, ((NBTMapType)map).getTag()); -+ } -+ -+ @Override -+ public void addString(final String string) { -+ this.list.add(StringTag.valueOf(string)); -+ } -+ -+ @Override -+ public void addString(final int index, final String string) { -+ this.list.add(index, StringTag.valueOf(string)); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..12880f93b53db1e60cbf13805e2eb08fee5fd203 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTMapType.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.ObjectType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+import ca.spottedleaf.dataconverter.types.Types; -+import net.minecraft.nbt.ByteArrayTag; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.IntArrayTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.LongArrayTag; -+import net.minecraft.nbt.NumericTag; -+import net.minecraft.nbt.StringTag; -+import net.minecraft.nbt.Tag; -+ -+import java.util.Set; -+ -+public final class NBTMapType implements MapType { -+ -+ private final CompoundTag map; -+ -+ public NBTMapType() { -+ this.map = new CompoundTag(); -+ } -+ -+ public NBTMapType(final CompoundTag tag) { -+ this.map = tag; -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ if (obj == null || obj.getClass() != NBTMapType.class) { -+ return false; -+ } -+ -+ return this.map.equals(((NBTMapType)obj).map); -+ } -+ -+ @Override -+ public TypeUtil getTypeUtil() { -+ return Types.NBT; -+ } -+ -+ @Override -+ public int hashCode() { -+ return this.map.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return "NBTMapType{" + -+ "map=" + this.map + -+ '}'; -+ } -+ -+ @Override -+ public int size() { -+ return this.map.size(); -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return this.map.isEmpty(); -+ } -+ -+ @Override -+ public void clear() { -+ this.map.getAllKeys().clear(); -+ } -+ -+ @Override -+ public Set keys() { -+ return this.map.getAllKeys(); -+ } -+ -+ public CompoundTag getTag() { -+ return this.map; -+ } -+ -+ @Override -+ public MapType copy() { -+ return new NBTMapType(this.map.copy()); -+ } -+ -+ @Override -+ public boolean hasKey(final String key) { -+ return this.map.get(key) != null; -+ } -+ -+ @Override -+ public boolean hasKey(final String key, final ObjectType type) { -+ final Tag tag = this.map.get(key); -+ if (tag == null) { -+ return false; -+ } -+ -+ final ObjectType valueType = NBTListType.getType(tag.getId()); -+ -+ return valueType == type || (type == ObjectType.NUMBER && valueType.isNumber()); -+ } -+ -+ @Override -+ public void remove(final String key) { -+ this.map.remove(key); -+ } -+ -+ @Override -+ public Object getGeneric(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag == null) { -+ return null; -+ } -+ -+ switch (NBTListType.getType(tag.getId())) { -+ case BYTE: -+ case SHORT: -+ case INT: -+ case LONG: -+ case FLOAT: -+ case DOUBLE: -+ return ((NumericTag)tag).getAsNumber(); -+ case MAP: -+ return new NBTMapType((CompoundTag)tag); -+ case LIST: -+ return new NBTListType((ListTag)tag); -+ case STRING: -+ return ((StringTag)tag).getAsString(); -+ case BYTE_ARRAY: -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ // Note: No short array tag! -+ case INT_ARRAY: -+ return ((IntArrayTag)tag).getAsIntArray(); -+ case LONG_ARRAY: -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ -+ throw new IllegalStateException("Unrecognized type " + tag); -+ } -+ -+ @Override -+ public Number getNumber(final String key) { -+ return this.getNumber(key, null); -+ } -+ -+ @Override -+ public Number getNumber(final String key, final Number dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsNumber(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key) { -+ return this.getByte(key) != 0; -+ } -+ -+ @Override -+ public boolean getBoolean(final String key, final boolean dfl) { -+ return this.getByte(key, dfl ? (byte)1 : (byte)0) != 0; -+ } -+ -+ @Override -+ public void setBoolean(final String key, final boolean val) { -+ this.setByte(key, val ? (byte)1 : (byte)0); -+ } -+ -+ @Override -+ public byte getByte(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsByte(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public byte getByte(final String key, final byte dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsByte(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setByte(final String key, final byte val) { -+ this.map.putByte(key, val); -+ } -+ -+ @Override -+ public short getShort(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsShort(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public short getShort(final String key, final short dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsShort(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setShort(final String key, final short val) { -+ this.map.putShort(key, val); -+ } -+ -+ @Override -+ public int getInt(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsInt(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public int getInt(final String key, final int dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsInt(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setInt(final String key, final int val) { -+ this.map.putInt(key, val); -+ } -+ -+ @Override -+ public long getLong(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsLong(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public long getLong(final String key, final long dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsLong(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setLong(final String key, final long val) { -+ this.map.putLong(key, val); -+ } -+ -+ @Override -+ public float getFloat(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public float getFloat(final String key, final float dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsFloat(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setFloat(final String key, final float val) { -+ this.map.putFloat(key, val); -+ } -+ -+ @Override -+ public double getDouble(final String key) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ return 0; -+ } -+ -+ @Override -+ public double getDouble(final String key, final double dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof NumericTag) { -+ return ((NumericTag)tag).getAsDouble(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setDouble(final String key, final double val) { -+ this.map.putDouble(key, val); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key) { -+ return this.getBytes(key, null); -+ } -+ -+ @Override -+ public byte[] getBytes(final String key, final byte[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof ByteArrayTag) { -+ return ((ByteArrayTag)tag).getAsByteArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setBytes(final String key, final byte[] val) { -+ this.map.putByteArray(key, val); -+ } -+ -+ @Override -+ public short[] getShorts(final String key) { -+ return this.getShorts(key, null); -+ } -+ -+ @Override -+ public short[] getShorts(final String key, final short[] dfl) { -+ // NBT does not support short array -+ return dfl; -+ } -+ -+ @Override -+ public void setShorts(final String key, final short[] val) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int[] getInts(final String key) { -+ return this.getInts(key, null); -+ } -+ -+ @Override -+ public int[] getInts(final String key, final int[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof IntArrayTag) { -+ return ((IntArrayTag)tag).getAsIntArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setInts(final String key, final int[] val) { -+ this.map.putIntArray(key, val); -+ } -+ -+ @Override -+ public long[] getLongs(final String key) { -+ return this.getLongs(key, null); -+ } -+ -+ @Override -+ public long[] getLongs(final String key, final long[] dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof LongArrayTag) { -+ return ((LongArrayTag)tag).getAsLongArray(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setLongs(final String key, final long[] val) { -+ this.map.putLongArray(key, val); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key) { -+ return this.getListUnchecked(key, null); -+ } -+ -+ @Override -+ public ListType getListUnchecked(final String key, final ListType dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof ListTag) { -+ return new NBTListType((ListTag)tag); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setList(final String key, final ListType val) { -+ this.map.put(key, ((NBTListType)val).getTag()); -+ } -+ -+ @Override -+ public MapType getMap(final String key) { -+ return this.getMap(key, null); -+ } -+ -+ @Override -+ public MapType getMap(final String key, final MapType dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof CompoundTag) { -+ return new NBTMapType((CompoundTag)tag); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setMap(final String key, final MapType val) { -+ this.map.put(key, ((NBTMapType)val).getTag()); -+ } -+ -+ @Override -+ public String getString(final String key) { -+ return this.getString(key, null); -+ } -+ -+ @Override -+ public String getString(final String key, final String dfl) { -+ final Tag tag = this.map.get(key); -+ if (tag instanceof StringTag) { -+ return ((StringTag)tag).getAsString(); -+ } -+ return dfl; -+ } -+ -+ @Override -+ public void setString(final String key, final String val) { -+ this.map.putString(key, val); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..62c0f4073aff301bf5b3187e0d4446fd8d0ac475 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/types/nbt/NBTTypeUtil.java -@@ -0,0 +1,18 @@ -+package ca.spottedleaf.dataconverter.types.nbt; -+ -+import ca.spottedleaf.dataconverter.types.ListType; -+import ca.spottedleaf.dataconverter.types.MapType; -+import ca.spottedleaf.dataconverter.types.TypeUtil; -+ -+public final class NBTTypeUtil implements TypeUtil { -+ -+ @Override -+ public ListType createEmptyList() { -+ return new NBTListType(); -+ } -+ -+ @Override -+ public MapType createEmptyMap() { -+ return new NBTMapType(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6596de3d9ebae583c252aa061f0cfdf8778ea1a5 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2IntArraySortedMap.java -@@ -0,0 +1,77 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import it.unimi.dsi.fastutil.ints.Int2IntFunction; -+ -+import java.util.Arrays; -+ -+public class Int2IntArraySortedMap { -+ -+ protected int[] key; -+ protected int[] val; -+ protected int size; -+ -+ public Int2IntArraySortedMap() { -+ this.key = new int[8]; -+ this.val = new int[8]; -+ } -+ -+ public int put(final int key, final int value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final int current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return 0; -+ } -+ -+ public int computeIfAbsent(final int key, final Int2IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public int get(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return 0; -+ } -+ return this.val[index]; -+ } -+ -+ public int getFloor(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? 0 : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..de9d632489609136c712a9adaee941fd38fad440 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Int2ObjectArraySortedMap.java -@@ -0,0 +1,74 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import java.util.Arrays; -+import java.util.function.IntFunction; -+ -+public class Int2ObjectArraySortedMap { -+ -+ protected int[] key; -+ protected V[] val; -+ protected int size; -+ -+ public Int2ObjectArraySortedMap() { -+ this.key = new int[8]; -+ this.val = (V[])new Object[8]; -+ } -+ -+ public V put(final int key, final V value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final V current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return null; -+ } -+ -+ public V computeIfAbsent(final int key, final IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public V get(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return null; -+ } -+ return this.val[index]; -+ } -+ -+ public V getFloor(final int key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1); -+ return this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b5ebf6bbe1a3711111cf045ee8b46c934c7e4563 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/IntegerUtil.java -@@ -0,0 +1,223 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+public final class IntegerUtil { -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ public static long getDivisorNumbers(final int d) { -+ final int ad = Math.abs(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 - (int)((t & mask)%ad); -+ int q1 = (int)((two31 & mask)/(anc & mask)); -+ int r1 = two31 - q1*anc; -+ int q2 = (int)((two31 & mask)/(ad & mask)); -+ int r2 = two31 - q2*ad; -+ int delta; -+ -+ do { -+ p = p + 1; -+ q1 = 2*q1; // Update q1 = 2**p/|nc|. -+ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). -+ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) -+ q1 = q1 + 1; -+ r1 = r1 - anc; -+ } -+ q2 = 2*q2; // Update q2 = 2**p/|d|. -+ r2 = 2*r2; // Update r2 = rem(2**p, |d|). -+ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) -+ q2 = q2 + 1; -+ r2 = r2 - ad; -+ } -+ delta = ad - r2; -+ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); -+ -+ int magicNum = q2 + 1; -+ if (d < 0) { -+ magicNum = -magicNum; -+ } -+ int shift = p - 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/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..94705bb141b550589faa9a0408402d8636c61907 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2IntArraySortedMap.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import it.unimi.dsi.fastutil.longs.Long2IntFunction; -+import java.util.Arrays; -+ -+public class Long2IntArraySortedMap { -+ -+ protected long[] key; -+ protected int[] val; -+ protected int size; -+ -+ public Long2IntArraySortedMap() { -+ this.key = new long[8]; -+ this.val = new int[8]; -+ } -+ -+ public int put(final long key, final int value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final int current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return 0; -+ } -+ -+ public int computeIfAbsent(final long key, final Long2IntFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public int get(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return 0; -+ } -+ return this.val[index]; -+ } -+ -+ public int getFloor(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? 0 : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6f634c8825589a23f46ad7b54354475c9a95bd1b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/Long2ObjectArraySortedMap.java -@@ -0,0 +1,76 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import java.util.Arrays; -+import java.util.function.LongFunction; -+ -+public class Long2ObjectArraySortedMap { -+ -+ protected long[] key; -+ protected V[] val; -+ protected int size; -+ -+ public Long2ObjectArraySortedMap() { -+ this.key = new long[8]; -+ this.val = (V[])new Object[8]; -+ } -+ -+ public V put(final long key, final V value) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ final V current = this.val[index]; -+ this.val[index] = value; -+ return current; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ this.val[insert] = value; -+ -+ return null; -+ } -+ -+ public V computeIfAbsent(final long key, final LongFunction producer) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index >= 0) { -+ return this.val[index]; -+ } -+ final int insert = -(index + 1); -+ // shift entries down -+ if (this.size >= this.val.length) { -+ this.key = Arrays.copyOf(this.key, this.key.length * 2); -+ this.val = Arrays.copyOf(this.val, this.val.length * 2); -+ } -+ System.arraycopy(this.key, insert, this.key, insert + 1, this.size - insert); -+ System.arraycopy(this.val, insert, this.val, insert + 1, this.size - insert); -+ ++this.size; -+ -+ this.key[insert] = key; -+ -+ return this.val[insert] = producer.apply(key); -+ } -+ -+ public V get(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ return null; -+ } -+ return this.val[index]; -+ } -+ -+ public V getFloor(final long key) { -+ final int index = Arrays.binarySearch(this.key, 0, this.size, key); -+ if (index < 0) { -+ final int insert = -(index + 1) - 1; -+ return insert < 0 ? null : this.val[insert]; -+ } -+ return this.val[index]; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..967ad1186cbc81a76a4958ea99d4eff37c15b48f ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/dataconverter/util/NamespaceUtil.java -@@ -0,0 +1,24 @@ -+package ca.spottedleaf.dataconverter.util; -+ -+import net.minecraft.resources.ResourceLocation; -+ -+public final class NamespaceUtil { -+ -+ private NamespaceUtil() {} -+ -+ public static String correctNamespace(final String value) { -+ if (value == null) { -+ return null; -+ } -+ final ResourceLocation resourceLocation = ResourceLocation.tryParse(value); -+ return resourceLocation != null ? resourceLocation.toString() : value; -+ } -+ -+ public static String correctNamespaceOrNull(final String value) { -+ if (value == null) { -+ return null; -+ } -+ final String correct = correctNamespace(value); -+ return correct.equals(value) ? null : correct; -+ } -+} -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index fee9a8e74bfcc94942991b56799debf67b551f43..b230a3d475357d2ffd340f9a89934ea7227e69d0 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -87,7 +87,7 @@ public class ChunkStorage implements AutoCloseable { - int i = ChunkStorage.getVersion(nbttagcompound); - - // CraftBukkit start -- if (i < 1466) { -+ if (false && i < 1466) { // Paper - no longer needed, data converter system handles it now - CompoundTag level = nbttagcompound.getCompound("Level"); - if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) { - ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource(); -@@ -99,7 +99,7 @@ public class ChunkStorage implements AutoCloseable { - // CraftBukkit end - - if (i < 1493) { -- nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, i, 1493); -+ ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, i, 1493); // Paper - replace chunk converter - if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { - synchronized (this.persistentDataLock) { // Paper - Async chunk loading - LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); -@@ -119,7 +119,7 @@ public class ChunkStorage implements AutoCloseable { - // Spigot end - - ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional); -- nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, Math.max(1493, i)); -+ nbttagcompound = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, nbttagcompound, Math.max(1493, i), SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - replace chunk converter - if (i < SharedConstants.getCurrentVersion().getWorldVersion()) { - nbttagcompound.putInt("DataVersion", SharedConstants.getCurrentVersion().getWorldVersion()); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index de7afc737b1ab099edc29a4ef94baa76329c2947..2bc0384728f89b7c64a8beec78a1b77dc063d37b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -128,7 +128,7 @@ public class EntityStorage implements EntityPersistentStorage { - - private CompoundTag upgradeChunkTag(CompoundTag chunkNbt) { - int i = getVersion(chunkNbt); -- return NbtUtils.update(this.fixerUpper, DataFixTypes.ENTITY_CHUNK, chunkNbt, i); -+ return ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.ENTITY_CHUNK, chunkNbt, i, SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - route to new converter system - } - - public static int getVersion(CompoundTag chunkNbt) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 9b2cec7528936a5d53a926c91063cb6e9ed7da1b..47cda78efcce597d3d7ba8fc93a2865e10cdc237 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -148,7 +148,14 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - int j = getVersion(dynamic); - int k = SharedConstants.getCurrentVersion().getWorldVersion(); - boolean bl = j != k; -- Dynamic dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k); -+ // Paper start - route to new converter system -+ Dynamic dynamic2; -+ if (this.type.getType() == net.minecraft.util.datafix.fixes.References.POI_CHUNK) { -+ dynamic2 = new Dynamic<>(dynamic.getOps(), (T)ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.POI_CHUNK, (CompoundTag)dynamic.getValue(), j, k)); -+ } else { -+ dynamic2 = this.fixerUpper.update(this.type.getType(), dynamic, j, k); -+ } -+ // Paper end - route to new converter system - OptionalDynamic optionalDynamic = dynamic2.get("Sections"); - - for(int l = this.levelHeightAccessor.getMinSection(); l < this.levelHeightAccessor.getMaxSection(); ++l) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 963ad3ce1ef83888ae1537ff01accdbb5b04ffa1..a7cba5b828a586d7435bda4d512af68684244682 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -115,7 +115,7 @@ public class StructureCheck { - - CompoundTag compoundTag2; - try { -- compoundTag2 = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, compoundTag, i); -+ compoundTag2 = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.CHUNK, compoundTag, i, net.minecraft.SharedConstants.getCurrentVersion().getWorldVersion()); // Paper - replace chunk converter - } catch (Exception var12) { - LOGGER.warn("Failed to partially datafix chunk {}", pos, var12); - return StructureCheckResult.CHUNK_LOAD_NEEDED; -diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -index d785efd61caa2237e05d9ce3dbf84d86076ff047..601f8099f74e81c17600566b3c9b7a6dd39c9bcb 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -93,7 +93,7 @@ public class PlayerDataStorage { - // CraftBukkit end - int i = nbttagcompound.contains("DataVersion", 3) ? nbttagcompound.getInt("DataVersion") : -1; - -- player.load(NbtUtils.update(this.fixerUpper, DataFixTypes.PLAYER, nbttagcompound, i)); -+ player.load(ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, nbttagcompound, i, net.minecraft.SharedConstants.getCurrentVersion().getWorldVersion())); // Paper - replace player converter - } - - return nbttagcompound; diff --git a/patches/server/0761-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/0761-Use-Velocity-compression-and-cipher-natives.patch new file mode 100644 index 0000000000..ed1f57fae4 --- /dev/null +++ b/patches/server/0761-Use-Velocity-compression-and-cipher-natives.patch @@ -0,0 +1,364 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 26 Jul 2021 02:15:17 -0400 +Subject: [PATCH] Use Velocity compression and cipher natives + + +diff --git a/build.gradle.kts b/build.gradle.kts +index a02f53c6ee0111e07d78a718a6ca0ec708f70cfc..fb6bfd4967b4ec113463cfaa77e621183f93e441 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -29,6 +29,11 @@ dependencies { + implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files + implementation("commons-lang:commons-lang:2.6") + implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation ++ // Paper start - Use Velocity cipher ++ implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") { ++ isTransitive = false ++ } ++ // Paper end + runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3") + runtimeOnly("mysql:mysql-connector-java:8.0.29") + runtimeOnly("com.lmax:disruptor:3.4.4") // Paper +diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java +index 778beb445eac5769b9e4e07b4d1294c50ae2602b..c712fb8193115e1ab71b5e40fb0ccb9413062b03 100644 +--- a/src/main/java/net/minecraft/network/CipherDecoder.java ++++ b/src/main/java/net/minecraft/network/CipherDecoder.java +@@ -7,13 +7,29 @@ import java.util.List; + import javax.crypto.Cipher; + + public class CipherDecoder extends MessageToMessageDecoder { +- private final CipherBase cipher; ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper + +- public CipherDecoder(Cipher cipher) { +- this.cipher = new CipherBase(cipher); ++ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper ++ this.cipher = cipher; // Paper + } + + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { +- list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); ++ // Paper start ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); ++ try { ++ cipher.process(compatible); ++ list.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } ++ // Paper end + } ++ ++ // Paper start ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ cipher.close(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java +index 0f3d502a9680006bcdcd7d272240a2e5c3b46790..5dd7be70603e8754d2625bb9d16900cb01b9c730 100644 +--- a/src/main/java/net/minecraft/network/CipherEncoder.java ++++ b/src/main/java/net/minecraft/network/CipherEncoder.java +@@ -4,15 +4,32 @@ import io.netty.buffer.ByteBuf; + import io.netty.channel.ChannelHandlerContext; + import io.netty.handler.codec.MessageToByteEncoder; + import javax.crypto.Cipher; ++import java.util.List; + +-public class CipherEncoder extends MessageToByteEncoder { +- private final CipherBase cipher; ++public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder { // Paper - change superclass ++ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper + +- public CipherEncoder(Cipher cipher) { +- this.cipher = new CipherBase(cipher); ++ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper ++ this.cipher = cipher; // Paper + } + +- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { +- this.cipher.encipher(byteBuf, byteBuf2); ++ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { ++ // Paper start ++ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); ++ try { ++ cipher.process(compatible); ++ list.add(compatible); ++ } catch (Exception e) { ++ compatible.release(); // compatible will never be used if we throw an exception ++ throw e; ++ } ++ // Paper end + } ++ ++ // Paper start ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ cipher.close(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java +index b62be99c57b0a5bba0dc29809557d4d247698b13..f4d4ad983baf24d889441541d5a84dc1f49ea4d4 100644 +--- a/src/main/java/net/minecraft/network/CompressionDecoder.java ++++ b/src/main/java/net/minecraft/network/CompressionDecoder.java +@@ -12,13 +12,20 @@ public class CompressionDecoder extends ByteToMessageDecoder { + public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; + public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; + private final Inflater inflater; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper + private int threshold; + private boolean validateDecompressed; + ++ // Paper start + public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) { ++ this(null, compressionThreshold, rejectsBadPackets); ++ } ++ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { + this.threshold = compressionThreshold; + this.validateDecompressed = rejectsBadPackets; +- this.inflater = new Inflater(); ++ this.inflater = compressor == null ? new Inflater() : null; ++ this.compressor = compressor; ++ // Paper end + } + + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { +@@ -38,6 +45,8 @@ public class CompressionDecoder extends ByteToMessageDecoder { + } + } + ++ // Paper start ++ if (this.inflater != null) { + byte[] bs = new byte[friendlyByteBuf.readableBytes()]; + friendlyByteBuf.readBytes(bs); + this.inflater.setInput(bs); +@@ -45,10 +54,36 @@ public class CompressionDecoder extends ByteToMessageDecoder { + this.inflater.inflate(cs); + list.add(Unpooled.wrappedBuffer(cs)); + this.inflater.reset(); ++ return; ++ } ++ ++ int claimedUncompressedSize = i; // OBFHELPER ++ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); ++ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize); ++ try { ++ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); ++ list.add(uncompressed); ++ byteBuf.clear(); ++ } catch (Exception e) { ++ uncompressed.release(); ++ throw e; ++ } finally { ++ compatibleIn.release(); ++ } ++ // Paper end + } + } + } + ++ // Paper start ++ @Override ++ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { ++ if (this.compressor != null) { ++ this.compressor.close(); ++ } ++ } ++ // Paper end ++ + public void setThreshold(int compressionThreshold, boolean rejectsBadPackets) { + this.threshold = compressionThreshold; + this.validateDecompressed = rejectsBadPackets; +diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java +index 792883afe53d2b7989c25a81c2f9a639d5e21d20..c04379ca8a4db0f4de46ad2b3b3384310eebddf3 100644 +--- a/src/main/java/net/minecraft/network/CompressionEncoder.java ++++ b/src/main/java/net/minecraft/network/CompressionEncoder.java +@@ -6,22 +6,37 @@ import io.netty.handler.codec.MessageToByteEncoder; + import java.util.zip.Deflater; + + public class CompressionEncoder extends MessageToByteEncoder { +- private final byte[] encodeBuf = new byte[8192]; ++ private final byte[] encodeBuf; // Paper + private final Deflater deflater; ++ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper + private int threshold; + ++ // Paper start + public CompressionEncoder(int compressionThreshold) { ++ this(null, compressionThreshold); ++ } ++ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) { + this.threshold = compressionThreshold; +- this.deflater = new Deflater(); ++ if (compressor == null) { ++ this.encodeBuf = new byte[8192]; ++ this.deflater = new Deflater(); ++ } else { ++ this.encodeBuf = null; ++ this.deflater = null; ++ } ++ this.compressor = compressor; ++ // Paper end + } + +- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { ++ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper + int i = byteBuf.readableBytes(); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf2); + if (i < this.threshold) { + friendlyByteBuf.writeVarInt(0); + friendlyByteBuf.writeBytes(byteBuf); + } else { ++ // Paper start ++ if (this.deflater != null) { + byte[] bs = new byte[i]; + byteBuf.readBytes(bs); + friendlyByteBuf.writeVarInt(bs.length); +@@ -34,10 +49,48 @@ public class CompressionEncoder extends MessageToByteEncoder { + } + + this.deflater.reset(); ++ return; ++ } ++ ++ friendlyByteBuf.writeVarInt(i); ++ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); ++ try { ++ this.compressor.deflate(compatibleIn, byteBuf2); ++ } finally { ++ compatibleIn.release(); ++ } ++ // Paper end + } + + } + ++ // Paper start ++ @Override ++ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ ++ if (this.compressor != null) { ++ // We allocate bytes to be compressed plus 1 byte. This covers two cases: ++ // ++ // - Compression ++ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, ++ // if the data compresses well (and we do not have some pathological case) then the maximum ++ // size the compressed size will ever be is the input size minus one. ++ // - Uncompressed ++ // This is fairly obvious - we will then have one more than the uncompressed size. ++ int initialBufferSize = msg.readableBytes() + 1; ++ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); ++ } ++ ++ return super.allocateBuffer(ctx, msg, preferDirect); ++ } ++ ++ @Override ++ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { ++ if (this.compressor != null) { ++ this.compressor.close(); ++ } ++ } ++ // Paper end ++ + public int getThreshold() { + return this.threshold; + } +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index fefda9868fd3c4b3392b2bf4c68c0b4b2f311f31..66afd752fd7d327e141d49b477f07e1ff3645d02 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -652,11 +652,28 @@ public class Connection extends SimpleChannelInboundHandler> { + return networkmanager; + } + +- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { +- this.encrypted = true; +- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); +- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); ++ // Paper start ++// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { ++// this.encrypted = true; ++// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); ++// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); ++// } ++ ++ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException { ++ if (!this.encrypted) { ++ try { ++ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); ++ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); ++ ++ this.encrypted = true; ++ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption)); ++ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption)); ++ } catch (java.security.GeneralSecurityException e) { ++ throw new net.minecraft.util.CryptException(e); ++ } ++ } + } ++ // Paper end + + public boolean isEncrypted() { + return this.encrypted; +@@ -685,16 +702,17 @@ public class Connection extends SimpleChannelInboundHandler> { + + public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { + if (compressionThreshold >= 0) { ++ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); // Paper + if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { + ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, rejectsBadPackets); + } else { +- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); ++ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper + } + + if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { + ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); + } else { +- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); ++ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper + } + this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper + } else { +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index b80aedd2002959b4026c27ce76b3ed17f0acfb5b..2985271132c9ae822dcb0d7a7e6f0c268d1736cc 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -106,6 +106,11 @@ public class ServerConnectionListener { + ServerConnectionListener.LOGGER.info("Using default channel type"); + } + ++ // Paper start - indicate Velocity natives in use ++ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); ++ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); ++ // Paper end ++ + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) { + try { +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index f6efd220d7f78f3f763bf1983d20c636eb4923b6..77cb18da4f89bb89aea7d1ef5ebe3dd7acfe000d 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -315,12 +315,14 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + } + + SecretKey secretkey = packet.getSecretKey(privatekey); +- Cipher cipher = Crypt.getCipher(2, secretkey); +- Cipher cipher1 = Crypt.getCipher(1, secretkey); ++ // Paper start ++// Cipher cipher = Crypt.getCipher(2, secretkey); ++// Cipher cipher1 = Crypt.getCipher(1, secretkey); ++ // Paper end + + s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); + this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; +- this.connection.setEncryptionKey(cipher, cipher1); ++ this.connection.setupEncryption(secretkey); // Paper + } catch (CryptException cryptographyexception) { + throw new IllegalStateException("Protocol error", cryptographyexception); + } diff --git a/patches/server/0762-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch b/patches/server/0762-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch new file mode 100644 index 0000000000..c8dd06ca73 --- /dev/null +++ b/patches/server/0762-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 30 Aug 2021 04:26:40 -0700 +Subject: [PATCH] Reduce worldgen thread worker count for low core count CPUs + + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 384ddb03af26ae360fd22e2e231d9d14d6ad0865..23a999c19809a4fb62b37400e3767dc44692adb3 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -146,7 +146,19 @@ public class Util { + + private static ExecutorService makeExecutor(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)); ++ // Paper start - also try to avoid suffocating the system with the worldgen workers ++ int cpus = Runtime.getRuntime().availableProcessors() / 2; ++ int i; ++ if (cpus <= 4) { ++ i = cpus <= 2 ? 1 : 2; ++ } else if (cpus <= 8) { ++ // [5, 8] ++ i = Math.max(3, cpus - 2); ++ } else { ++ i = cpus * 2 / 3; ++ } ++ i = Math.min(8, i); ++ // Paper end - also try to avoid suffocating the system with the worldgen workers + i = Integer.getInteger("Paper.WorkerThreadCount", i); + ExecutorService executorService; + diff --git a/patches/server/0762-Use-Velocity-compression-and-cipher-natives.patch b/patches/server/0762-Use-Velocity-compression-and-cipher-natives.patch deleted file mode 100644 index ed1f57fae4..0000000000 --- a/patches/server/0762-Use-Velocity-compression-and-cipher-natives.patch +++ /dev/null @@ -1,364 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 26 Jul 2021 02:15:17 -0400 -Subject: [PATCH] Use Velocity compression and cipher natives - - -diff --git a/build.gradle.kts b/build.gradle.kts -index a02f53c6ee0111e07d78a718a6ca0ec708f70cfc..fb6bfd4967b4ec113463cfaa77e621183f93e441 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -29,6 +29,11 @@ dependencies { - implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files - implementation("commons-lang:commons-lang:2.6") - implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation -+ // Paper start - Use Velocity cipher -+ implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") { -+ isTransitive = false -+ } -+ // Paper end - runtimeOnly("org.xerial:sqlite-jdbc:3.36.0.3") - runtimeOnly("mysql:mysql-connector-java:8.0.29") - runtimeOnly("com.lmax:disruptor:3.4.4") // Paper -diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java -index 778beb445eac5769b9e4e07b4d1294c50ae2602b..c712fb8193115e1ab71b5e40fb0ccb9413062b03 100644 ---- a/src/main/java/net/minecraft/network/CipherDecoder.java -+++ b/src/main/java/net/minecraft/network/CipherDecoder.java -@@ -7,13 +7,29 @@ import java.util.List; - import javax.crypto.Cipher; - - public class CipherDecoder extends MessageToMessageDecoder { -- private final CipherBase cipher; -+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - -- public CipherDecoder(Cipher cipher) { -- this.cipher = new CipherBase(cipher); -+ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper -+ this.cipher = cipher; // Paper - } - - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -- list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); -+ // Paper start -+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); -+ try { -+ cipher.process(compatible); -+ list.add(compatible); -+ } catch (Exception e) { -+ compatible.release(); // compatible will never be used if we throw an exception -+ throw e; -+ } -+ // Paper end - } -+ -+ // Paper start -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ cipher.close(); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java -index 0f3d502a9680006bcdcd7d272240a2e5c3b46790..5dd7be70603e8754d2625bb9d16900cb01b9c730 100644 ---- a/src/main/java/net/minecraft/network/CipherEncoder.java -+++ b/src/main/java/net/minecraft/network/CipherEncoder.java -@@ -4,15 +4,32 @@ import io.netty.buffer.ByteBuf; - import io.netty.channel.ChannelHandlerContext; - import io.netty.handler.codec.MessageToByteEncoder; - import javax.crypto.Cipher; -+import java.util.List; - --public class CipherEncoder extends MessageToByteEncoder { -- private final CipherBase cipher; -+public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder { // Paper - change superclass -+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - -- public CipherEncoder(Cipher cipher) { -- this.cipher = new CipherBase(cipher); -+ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper -+ this.cipher = cipher; // Paper - } - -- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { -- this.cipher.encipher(byteBuf, byteBuf2); -+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -+ // Paper start -+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); -+ try { -+ cipher.process(compatible); -+ list.add(compatible); -+ } catch (Exception e) { -+ compatible.release(); // compatible will never be used if we throw an exception -+ throw e; -+ } -+ // Paper end - } -+ -+ // Paper start -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ cipher.close(); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java -index b62be99c57b0a5bba0dc29809557d4d247698b13..f4d4ad983baf24d889441541d5a84dc1f49ea4d4 100644 ---- a/src/main/java/net/minecraft/network/CompressionDecoder.java -+++ b/src/main/java/net/minecraft/network/CompressionDecoder.java -@@ -12,13 +12,20 @@ public class CompressionDecoder extends ByteToMessageDecoder { - public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; - public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; - private final Inflater inflater; -+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - private int threshold; - private boolean validateDecompressed; - -+ // Paper start - public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) { -+ this(null, compressionThreshold, rejectsBadPackets); -+ } -+ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { - this.threshold = compressionThreshold; - this.validateDecompressed = rejectsBadPackets; -- this.inflater = new Inflater(); -+ this.inflater = compressor == null ? new Inflater() : null; -+ this.compressor = compressor; -+ // Paper end - } - - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { -@@ -38,6 +45,8 @@ public class CompressionDecoder extends ByteToMessageDecoder { - } - } - -+ // Paper start -+ if (this.inflater != null) { - byte[] bs = new byte[friendlyByteBuf.readableBytes()]; - friendlyByteBuf.readBytes(bs); - this.inflater.setInput(bs); -@@ -45,10 +54,36 @@ public class CompressionDecoder extends ByteToMessageDecoder { - this.inflater.inflate(cs); - list.add(Unpooled.wrappedBuffer(cs)); - this.inflater.reset(); -+ return; -+ } -+ -+ int claimedUncompressedSize = i; // OBFHELPER -+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); -+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize); -+ try { -+ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); -+ list.add(uncompressed); -+ byteBuf.clear(); -+ } catch (Exception e) { -+ uncompressed.release(); -+ throw e; -+ } finally { -+ compatibleIn.release(); -+ } -+ // Paper end - } - } - } - -+ // Paper start -+ @Override -+ public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { -+ if (this.compressor != null) { -+ this.compressor.close(); -+ } -+ } -+ // Paper end -+ - public void setThreshold(int compressionThreshold, boolean rejectsBadPackets) { - this.threshold = compressionThreshold; - this.validateDecompressed = rejectsBadPackets; -diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java -index 792883afe53d2b7989c25a81c2f9a639d5e21d20..c04379ca8a4db0f4de46ad2b3b3384310eebddf3 100644 ---- a/src/main/java/net/minecraft/network/CompressionEncoder.java -+++ b/src/main/java/net/minecraft/network/CompressionEncoder.java -@@ -6,22 +6,37 @@ import io.netty.handler.codec.MessageToByteEncoder; - import java.util.zip.Deflater; - - public class CompressionEncoder extends MessageToByteEncoder { -- private final byte[] encodeBuf = new byte[8192]; -+ private final byte[] encodeBuf; // Paper - private final Deflater deflater; -+ private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - private int threshold; - -+ // Paper start - public CompressionEncoder(int compressionThreshold) { -+ this(null, compressionThreshold); -+ } -+ public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) { - this.threshold = compressionThreshold; -- this.deflater = new Deflater(); -+ if (compressor == null) { -+ this.encodeBuf = new byte[8192]; -+ this.deflater = new Deflater(); -+ } else { -+ this.encodeBuf = null; -+ this.deflater = null; -+ } -+ this.compressor = compressor; -+ // Paper end - } - -- protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { -+ protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - int i = byteBuf.readableBytes(); - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf2); - if (i < this.threshold) { - friendlyByteBuf.writeVarInt(0); - friendlyByteBuf.writeBytes(byteBuf); - } else { -+ // Paper start -+ if (this.deflater != null) { - byte[] bs = new byte[i]; - byteBuf.readBytes(bs); - friendlyByteBuf.writeVarInt(bs.length); -@@ -34,10 +49,48 @@ public class CompressionEncoder extends MessageToByteEncoder { - } - - this.deflater.reset(); -+ return; -+ } -+ -+ friendlyByteBuf.writeVarInt(i); -+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); -+ try { -+ this.compressor.deflate(compatibleIn, byteBuf2); -+ } finally { -+ compatibleIn.release(); -+ } -+ // Paper end - } - - } - -+ // Paper start -+ @Override -+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ -+ if (this.compressor != null) { -+ // We allocate bytes to be compressed plus 1 byte. This covers two cases: -+ // -+ // - Compression -+ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, -+ // if the data compresses well (and we do not have some pathological case) then the maximum -+ // size the compressed size will ever be is the input size minus one. -+ // - Uncompressed -+ // This is fairly obvious - we will then have one more than the uncompressed size. -+ int initialBufferSize = msg.readableBytes() + 1; -+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); -+ } -+ -+ return super.allocateBuffer(ctx, msg, preferDirect); -+ } -+ -+ @Override -+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { -+ if (this.compressor != null) { -+ this.compressor.close(); -+ } -+ } -+ // Paper end -+ - public int getThreshold() { - return this.threshold; - } -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index fefda9868fd3c4b3392b2bf4c68c0b4b2f311f31..66afd752fd7d327e141d49b477f07e1ff3645d02 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -652,11 +652,28 @@ public class Connection extends SimpleChannelInboundHandler> { - return networkmanager; - } - -- public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { -- this.encrypted = true; -- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); -- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); -+ // Paper start -+// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { -+// this.encrypted = true; -+// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); -+// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); -+// } -+ -+ public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException { -+ if (!this.encrypted) { -+ try { -+ com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); -+ com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); -+ -+ this.encrypted = true; -+ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption)); -+ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption)); -+ } catch (java.security.GeneralSecurityException e) { -+ throw new net.minecraft.util.CryptException(e); -+ } -+ } - } -+ // Paper end - - public boolean isEncrypted() { - return this.encrypted; -@@ -685,16 +702,17 @@ public class Connection extends SimpleChannelInboundHandler> { - - public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { - if (compressionThreshold >= 0) { -+ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); // Paper - if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { - ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, rejectsBadPackets); - } else { -- this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); -+ this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - } - - if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { - ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); - } else { -- this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); -+ this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - } - this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - } else { -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index b80aedd2002959b4026c27ce76b3ed17f0acfb5b..2985271132c9ae822dcb0d7a7e6f0c268d1736cc 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -106,6 +106,11 @@ public class ServerConnectionListener { - ServerConnectionListener.LOGGER.info("Using default channel type"); - } - -+ // Paper start - indicate Velocity natives in use -+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); -+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); -+ // Paper end -+ - this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) { - try { -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index f6efd220d7f78f3f763bf1983d20c636eb4923b6..77cb18da4f89bb89aea7d1ef5ebe3dd7acfe000d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -315,12 +315,14 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - } - - SecretKey secretkey = packet.getSecretKey(privatekey); -- Cipher cipher = Crypt.getCipher(2, secretkey); -- Cipher cipher1 = Crypt.getCipher(1, secretkey); -+ // Paper start -+// Cipher cipher = Crypt.getCipher(2, secretkey); -+// Cipher cipher1 = Crypt.getCipher(1, secretkey); -+ // Paper end - - s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); - this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; -- this.connection.setEncryptionKey(cipher, cipher1); -+ this.connection.setupEncryption(secretkey); // Paper - } catch (CryptException cryptographyexception) { - throw new IllegalStateException("Protocol error", cryptographyexception); - } diff --git a/patches/server/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch b/patches/server/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch new file mode 100644 index 0000000000..33087ceabe --- /dev/null +++ b/patches/server/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Fri, 3 Sep 2021 15:50:25 +0100 +Subject: [PATCH] Do not process entity loads in CraftChunk#getEntities + +This re-introduces the issue behind #5872 but fixes #6543 +The logic here is generally flawed however somewhat of a nuance, +upstream uses managedBlock which is basically needed to process +the posted entity adds, but, has the side-effect of processing any +chunk loads which has the naunce of stacking up and either causing a +massive performance hit, or can potentially lead the server to crash. + +This issue is particularly noticable on paper due to the cumulative efforts +to drastically improve chunk loading speeds which means that there is much more +of a chance that we're about to eat a dirtload of chunk load callbacks, thus +making this issue much more of an issue + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 2224b9cfbc3be59037ef49ce278989ea3a710bb5..0b9312dc5ee43d2d450dc6e9f07a9ac0320955ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -130,46 +130,6 @@ public class CraftChunk implements Chunk { + this.getWorld().getChunkAt(x, z); // Transient load for this tick + } + +- PersistentEntitySectionManager entityManager = this.getCraftWorld().getHandle().entityManager; +- long pair = ChunkPos.asLong(x, z); +- +- if (entityManager.areEntitiesLoaded(pair)) { +- return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this +- } +- +- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading +- +- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded +- ProcessorMailbox mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue; +- BooleanSupplier supplier = () -> { +- // only execute inbox if our entities are not present +- if (entityManager.areEntitiesLoaded(pair)) { +- return true; +- } +- +- if (!entityManager.isPending(pair)) { +- // Our entities got unloaded, this should normally not happen. +- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading +- } +- +- // tick loading inbox, which loads the created entities to the world +- // (if present) +- entityManager.tick(); +- // check if our entities are loaded +- return entityManager.areEntitiesLoaded(pair); +- }; +- +- // now we wait until the entities are loaded, +- // the converting from NBT to entity object is done on the main Thread which is why we wait +- while (!supplier.getAsBoolean()) { +- if (mailbox.size() != 0) { +- mailbox.run(); +- } else { +- Thread.yield(); +- LockSupport.parkNanos("waiting for entity loading", 100000L); +- } +- } +- + return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this + } + diff --git a/patches/server/0763-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch b/patches/server/0763-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch deleted file mode 100644 index c8dd06ca73..0000000000 --- a/patches/server/0763-Reduce-worldgen-thread-worker-count-for-low-core-cou.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 30 Aug 2021 04:26:40 -0700 -Subject: [PATCH] Reduce worldgen thread worker count for low core count CPUs - - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 384ddb03af26ae360fd22e2e231d9d14d6ad0865..23a999c19809a4fb62b37400e3767dc44692adb3 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -146,7 +146,19 @@ public class Util { - - private static ExecutorService makeExecutor(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)); -+ // Paper start - also try to avoid suffocating the system with the worldgen workers -+ int cpus = Runtime.getRuntime().availableProcessors() / 2; -+ int i; -+ if (cpus <= 4) { -+ i = cpus <= 2 ? 1 : 2; -+ } else if (cpus <= 8) { -+ // [5, 8] -+ i = Math.max(3, cpus - 2); -+ } else { -+ i = cpus * 2 / 3; -+ } -+ i = Math.min(8, i); -+ // Paper end - also try to avoid suffocating the system with the worldgen workers - i = Integer.getInteger("Paper.WorkerThreadCount", i); - ExecutorService executorService; - diff --git a/patches/server/0764-Async-catch-modifications-to-critical-entity-state.patch b/patches/server/0764-Async-catch-modifications-to-critical-entity-state.patch new file mode 100644 index 0000000000..3e8995df49 --- /dev/null +++ b/patches/server/0764-Async-catch-modifications-to-critical-entity-state.patch @@ -0,0 +1,133 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 31 Oct 2021 21:34:00 -0700 +Subject: [PATCH] Async catch modifications to critical entity state + +These used to be here from Spigot, but were dropped with 1.17. +Now in 1.17, this state is _even more_ critical than it was before, +so these must exist to catch stupid plugins. + +diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +index 1f0eddb0f3ded42bf312f8933def2f5c9a964651..2d3aacdae95963385ea228e73a2073a6fd96e640 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -138,6 +138,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntityUuid(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper + if (!this.knownUuids.add(entity.getUUID())) { + // Paper start + T conflict = this.visibleEntityStorage.getEntity(entity.getUUID()); +@@ -166,6 +167,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean addEntity(T entity, boolean existing) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper + // Paper start - chunk system hooks + if (existing) { + // I don't want to know why this is a generic type. +@@ -222,19 +224,23 @@ public class PersistentEntitySectionManager implements A + } + + void startTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper + this.callbacks.onTickingStart(entity); + } + + void stopTicking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper + this.callbacks.onTickingEnd(entity); + } + + void startTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper + this.visibleEntityStorage.add(entity); + this.callbacks.onTrackingStart(entity); + } + + void stopTracking(T entity) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper + this.callbacks.onTrackingEnd(entity); + this.visibleEntityStorage.remove(entity); + } +@@ -248,6 +254,7 @@ public class PersistentEntitySectionManager implements A + } + + public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { ++ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper + long i = chunkPos.toLong(); + + if (trackingStatus == Visibility.HIDDEN) { +@@ -292,6 +299,7 @@ public class PersistentEntitySectionManager implements A + } + + public void ensureChunkQueuedForLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper + PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); + + if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { +@@ -336,6 +344,7 @@ public class PersistentEntitySectionManager implements A + } + + private void requestChunkLoad(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper + this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); + ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); + CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); +@@ -349,6 +358,7 @@ public class PersistentEntitySectionManager implements A + } + + private boolean processChunkUnload(long chunkPos) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper + boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { + entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); + }, true); // CraftBukkit - add boolean for event call +@@ -373,6 +383,7 @@ public class PersistentEntitySectionManager implements A + } + + private void processPendingLoads() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper + ChunkEntities chunkentities; // CraftBukkit - decompile error + + while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { +@@ -389,6 +400,7 @@ public class PersistentEntitySectionManager implements A + } + + public void tick() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper + this.processPendingLoads(); + this.processUnloads(); + } +@@ -409,6 +421,7 @@ public class PersistentEntitySectionManager implements A + } + + public void autoSave() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper + this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error + boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; + +@@ -423,6 +436,7 @@ public class PersistentEntitySectionManager implements A + } + + public void saveAll() { ++ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper + LongSet longset = this.getAllChunksToSave(); + + while (!longset.isEmpty()) { +@@ -530,6 +544,7 @@ public class PersistentEntitySectionManager implements A + long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section + + if (i != this.currentSectionKey) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper + PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper + Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility + // Paper start +@@ -604,6 +619,7 @@ public class PersistentEntitySectionManager implements A + + @Override + public void onRemove(Entity.RemovalReason reason) { ++ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper + if (!this.currentSection.remove(this.entity)) { + PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); + } diff --git a/patches/server/0764-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch b/patches/server/0764-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch deleted file mode 100644 index d601eb8884..0000000000 --- a/patches/server/0764-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Fri, 3 Sep 2021 15:50:25 +0100 -Subject: [PATCH] Do not process entity loads in CraftChunk#getEntities - -This re-introduces the issue behind #5872 but fixes #6543 -The logic here is generally flawed however somewhat of a nuance, -upstream uses managedBlock which is basically needed to process -the posted entity adds, but, has the side-effect of processing any -chunk loads which has the naunce of stacking up and either causing a -massive performance hit, or can potentially lead the server to crash. - -This issue is particularly noticable on paper due to the cumulative efforts -to drastically improve chunk loading speeds which means that there is much more -of a chance that we're about to eat a dirtload of chunk load callbacks, thus -making this issue much more of an issue - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index e86b3ee5c8225d9f789cf426cc1418fde0fa12f0..f0b41e4d08691f2f97b0a4396b5d6c8fd75b2c86 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -130,46 +130,6 @@ public class CraftChunk implements Chunk { - this.getWorld().getChunkAt(x, z); // Transient load for this tick - } - -- PersistentEntitySectionManager entityManager = this.getCraftWorld().getHandle().entityManager; -- long pair = ChunkPos.asLong(x, z); -- -- if (entityManager.areEntitiesLoaded(pair)) { -- return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this -- } -- -- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading -- -- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded -- ProcessorMailbox mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue; -- BooleanSupplier supplier = () -> { -- // only execute inbox if our entities are not present -- if (entityManager.areEntitiesLoaded(pair)) { -- return true; -- } -- -- if (!entityManager.isPending(pair)) { -- // Our entities got unloaded, this should normally not happen. -- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading -- } -- -- // tick loading inbox, which loads the created entities to the world -- // (if present) -- entityManager.tick(); -- // check if our entities are loaded -- return entityManager.areEntitiesLoaded(pair); -- }; -- -- // now we wait until the entities are loaded, -- // the converting from NBT to entity object is done on the main Thread which is why we wait -- while (!supplier.getAsBoolean()) { -- if (mailbox.size() != 0) { -- mailbox.run(); -- } else { -- Thread.yield(); -- LockSupport.parkNanos("waiting for entity loading", 100000L); -- } -- } -- - return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this - } - diff --git a/patches/server/0765-Async-catch-modifications-to-critical-entity-state.patch b/patches/server/0765-Async-catch-modifications-to-critical-entity-state.patch deleted file mode 100644 index 3e8995df49..0000000000 --- a/patches/server/0765-Async-catch-modifications-to-critical-entity-state.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 31 Oct 2021 21:34:00 -0700 -Subject: [PATCH] Async catch modifications to critical entity state - -These used to be here from Spigot, but were dropped with 1.17. -Now in 1.17, this state is _even more_ critical than it was before, -so these must exist to catch stupid plugins. - -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 1f0eddb0f3ded42bf312f8933def2f5c9a964651..2d3aacdae95963385ea228e73a2073a6fd96e640 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -138,6 +138,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntityUuid(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper - if (!this.knownUuids.add(entity.getUUID())) { - // Paper start - T conflict = this.visibleEntityStorage.getEntity(entity.getUUID()); -@@ -166,6 +167,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean addEntity(T entity, boolean existing) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper - // Paper start - chunk system hooks - if (existing) { - // I don't want to know why this is a generic type. -@@ -222,19 +224,23 @@ public class PersistentEntitySectionManager implements A - } - - void startTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper - this.callbacks.onTickingStart(entity); - } - - void stopTicking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper - this.callbacks.onTickingEnd(entity); - } - - void startTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper - this.visibleEntityStorage.add(entity); - this.callbacks.onTrackingStart(entity); - } - - void stopTracking(T entity) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper - this.callbacks.onTrackingEnd(entity); - this.visibleEntityStorage.remove(entity); - } -@@ -248,6 +254,7 @@ public class PersistentEntitySectionManager implements A - } - - public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) { -+ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper - long i = chunkPos.toLong(); - - if (trackingStatus == Visibility.HIDDEN) { -@@ -292,6 +299,7 @@ public class PersistentEntitySectionManager implements A - } - - public void ensureChunkQueuedForLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper - PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos); - - if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) { -@@ -336,6 +344,7 @@ public class PersistentEntitySectionManager implements A - } - - private void requestChunkLoad(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper - this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING); - ChunkPos chunkcoordintpair = new ChunkPos(chunkPos); - CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair); -@@ -349,6 +358,7 @@ public class PersistentEntitySectionManager implements A - } - - private boolean processChunkUnload(long chunkPos) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper - boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> { - entityaccess.getPassengersAndSelf().forEach(this::unloadEntity); - }, true); // CraftBukkit - add boolean for event call -@@ -373,6 +383,7 @@ public class PersistentEntitySectionManager implements A - } - - private void processPendingLoads() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper - ChunkEntities chunkentities; // CraftBukkit - decompile error - - while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) { -@@ -389,6 +400,7 @@ public class PersistentEntitySectionManager implements A - } - - public void tick() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper - this.processPendingLoads(); - this.processUnloads(); - } -@@ -409,6 +421,7 @@ public class PersistentEntitySectionManager implements A - } - - public void autoSave() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper - this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error - boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN; - -@@ -423,6 +436,7 @@ public class PersistentEntitySectionManager implements A - } - - public void saveAll() { -+ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper - LongSet longset = this.getAllChunksToSave(); - - while (!longset.isEmpty()) { -@@ -530,6 +544,7 @@ public class PersistentEntitySectionManager implements A - long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section - - if (i != this.currentSectionKey) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper - PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper - Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility - // Paper start -@@ -604,6 +619,7 @@ public class PersistentEntitySectionManager implements A - - @Override - public void onRemove(Entity.RemovalReason reason) { -+ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason}); - } diff --git a/patches/server/0765-Fix-Bukkit-NamespacedKey-shenanigans.patch b/patches/server/0765-Fix-Bukkit-NamespacedKey-shenanigans.patch new file mode 100644 index 0000000000..fabf5a82e9 --- /dev/null +++ b/patches/server/0765-Fix-Bukkit-NamespacedKey-shenanigans.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sun, 24 Oct 2021 15:49:35 +0200 +Subject: [PATCH] Fix Bukkit NamespacedKey shenanigans + + +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java +index 88e32ed64f90bfd277dac84ba4bd84f0d943f5f8..d4a8c1bbb8fef27ac42bdf27dde495b4c649e6cb 100644 +--- a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java +@@ -17,7 +17,7 @@ public class PaperContainerEntityLootableInventory implements PaperLootableEntit + + @Override + public org.bukkit.loot.LootTable getLootTable() { +- return entity.getLootTable() != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; ++ return entity.getLootTable() != null && !entity.getLootTable().getPath().isEmpty() ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; + } + + @Override +diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +index 3377b86c337d0234bbb9b0349e4034a7cd450a97..94dc68182ec5f6dc1294ad15523427836228086a 100644 +--- a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java +@@ -15,7 +15,7 @@ public class PaperTileEntityLootableInventory implements PaperLootableBlockInven + + @Override + public org.bukkit.loot.LootTable getLootTable() { +- return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; ++ return tileEntityLootable.lootTable != null && !tileEntityLootable.lootTable.getPath().isEmpty() ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java +index 5f40d240b879e3989897b6e45725a8e5a6a7f194..5014192edb9616ce725fc1592832034789527b6f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java +@@ -13,7 +13,7 @@ public final class CraftNamespacedKey { + return null; + } + ResourceLocation minecraft = ResourceLocation.tryParse(string); +- return (minecraft == null) ? null : CraftNamespacedKey.fromMinecraft(minecraft); ++ return (minecraft == null || minecraft.getPath().isEmpty()) ? null : CraftNamespacedKey.fromMinecraft(minecraft); // Paper - Bukkit's parser does not match Vanilla for empty paths + } + + public static NamespacedKey fromString(String string) { diff --git a/patches/server/0766-Fix-Bukkit-NamespacedKey-shenanigans.patch b/patches/server/0766-Fix-Bukkit-NamespacedKey-shenanigans.patch deleted file mode 100644 index fabf5a82e9..0000000000 --- a/patches/server/0766-Fix-Bukkit-NamespacedKey-shenanigans.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sun, 24 Oct 2021 15:49:35 +0200 -Subject: [PATCH] Fix Bukkit NamespacedKey shenanigans - - -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -index 88e32ed64f90bfd277dac84ba4bd84f0d943f5f8..d4a8c1bbb8fef27ac42bdf27dde495b4c649e6cb 100644 ---- a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -@@ -17,7 +17,7 @@ public class PaperContainerEntityLootableInventory implements PaperLootableEntit - - @Override - public org.bukkit.loot.LootTable getLootTable() { -- return entity.getLootTable() != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; -+ return entity.getLootTable() != null && !entity.getLootTable().getPath().isEmpty() ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(entity.getLootTable())) : null; - } - - @Override -diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -index 3377b86c337d0234bbb9b0349e4034a7cd450a97..94dc68182ec5f6dc1294ad15523427836228086a 100644 ---- a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -@@ -15,7 +15,7 @@ public class PaperTileEntityLootableInventory implements PaperLootableBlockInven - - @Override - public org.bukkit.loot.LootTable getLootTable() { -- return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; -+ return tileEntityLootable.lootTable != null && !tileEntityLootable.lootTable.getPath().isEmpty() ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable)) : null; - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -index 5f40d240b879e3989897b6e45725a8e5a6a7f194..5014192edb9616ce725fc1592832034789527b6f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftNamespacedKey.java -@@ -13,7 +13,7 @@ public final class CraftNamespacedKey { - return null; - } - ResourceLocation minecraft = ResourceLocation.tryParse(string); -- return (minecraft == null) ? null : CraftNamespacedKey.fromMinecraft(minecraft); -+ return (minecraft == null || minecraft.getPath().isEmpty()) ? null : CraftNamespacedKey.fromMinecraft(minecraft); // Paper - Bukkit's parser does not match Vanilla for empty paths - } - - public static NamespacedKey fromString(String string) { diff --git a/patches/server/0766-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0766-Fix-merchant-inventory-not-closing-on-entity-removal.patch new file mode 100644 index 0000000000..85602ad6d7 --- /dev/null +++ b/patches/server/0766-Fix-merchant-inventory-not-closing-on-entity-removal.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 2 Sep 2021 00:24:06 -0700 +Subject: [PATCH] Fix merchant inventory not closing on entity removal + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index a7a403d34551453a1e0502fe1f7bc139f645d917..3bb6dbdd05ed981f70556c8f905d1eeeeade30b8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2578,6 +2578,11 @@ public class ServerLevel extends Level implements WorldGenLevel { + // Spigot end + // Spigot Start + if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message ++ // Paper start ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { ++ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); ++ } ++ // Paper end + for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { + h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper + } diff --git a/patches/server/0767-Check-requirement-before-suggesting-root-nodes.patch b/patches/server/0767-Check-requirement-before-suggesting-root-nodes.patch new file mode 100644 index 0000000000..d824c5f2af --- /dev/null +++ b/patches/server/0767-Check-requirement-before-suggesting-root-nodes.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 12 Sep 2021 00:14:21 +0200 +Subject: [PATCH] Check requirement before suggesting root nodes + +Child nodes are handled by CommandDispatcher#parse checking +requirements. + +Vanilla clients only send ServerboundCommandSuggestionPacket when +encountering a command node with ASK_SERVER suggestions, however a +modified client can send this packet whenever it wants. + +diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +index e733a5657032d29e5a0d64375c9e36639360a7e0..b64c98c173e25055f4ff9d7124d0a3cb7ff6ab1d 100644 +--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java ++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java +@@ -595,10 +595,14 @@ public class CommandDispatcher { + int i = 0; + for (final CommandNode node : parent.getChildren()) { + CompletableFuture future = Suggestions.empty(); ++ // Paper start - Don't suggest if the requirement isn't met ++ if (parent != this.root || node.canUse(context.getSource())) { + try { +- if (node.canUse(parse.getContext().getSource())) future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit ++ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit + } catch (final CommandSyntaxException ignored) { + } ++ } ++ // Paper end + futures[i++] = future; + } + diff --git a/patches/server/0767-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0767-Fix-merchant-inventory-not-closing-on-entity-removal.patch deleted file mode 100644 index 85602ad6d7..0000000000 --- a/patches/server/0767-Fix-merchant-inventory-not-closing-on-entity-removal.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 2 Sep 2021 00:24:06 -0700 -Subject: [PATCH] Fix merchant inventory not closing on entity removal - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index a7a403d34551453a1e0502fe1f7bc139f645d917..3bb6dbdd05ed981f70556c8f905d1eeeeade30b8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2578,6 +2578,11 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Spigot end - // Spigot Start - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message -+ // Paper start -+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { -+ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); -+ } -+ // Paper end - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { - h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - } diff --git a/patches/server/0768-Check-requirement-before-suggesting-root-nodes.patch b/patches/server/0768-Check-requirement-before-suggesting-root-nodes.patch deleted file mode 100644 index d824c5f2af..0000000000 --- a/patches/server/0768-Check-requirement-before-suggesting-root-nodes.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 12 Sep 2021 00:14:21 +0200 -Subject: [PATCH] Check requirement before suggesting root nodes - -Child nodes are handled by CommandDispatcher#parse checking -requirements. - -Vanilla clients only send ServerboundCommandSuggestionPacket when -encountering a command node with ASK_SERVER suggestions, however a -modified client can send this packet whenever it wants. - -diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -index e733a5657032d29e5a0d64375c9e36639360a7e0..b64c98c173e25055f4ff9d7124d0a3cb7ff6ab1d 100644 ---- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java -+++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java -@@ -595,10 +595,14 @@ public class CommandDispatcher { - int i = 0; - for (final CommandNode node : parent.getChildren()) { - CompletableFuture future = Suggestions.empty(); -+ // Paper start - Don't suggest if the requirement isn't met -+ if (parent != this.root || node.canUse(context.getSource())) { - try { -- if (node.canUse(parse.getContext().getSource())) future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit -+ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit - } catch (final CommandSyntaxException ignored) { - } -+ } -+ // Paper end - futures[i++] = future; - } - diff --git a/patches/server/0768-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0768-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch new file mode 100644 index 0000000000..b218b2916f --- /dev/null +++ b/patches/server/0768-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 12 Sep 2021 00:14:21 +0200 +Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when + tab-complete is disabled + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a4ed338b3317378fdad694d3b0b12379128ffd26..678c0b1c37b16e405205933a16f0d2d29359fd12 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -847,6 +847,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // Paper end + // CraftBukkit end ++ // Paper start - Don't suggest if tab-complete is disabled ++ if (org.spigotmc.SpigotConfig.tabComplete < 0) { ++ return; ++ } ++ // Paper end - Don't suggest if tab-complete is disabled + // Paper start - async tab completion + TAB_COMPLETE_EXECUTOR.execute(() -> { + StringReader stringreader = new StringReader(packet.getCommand()); diff --git a/patches/server/0769-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0769-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch deleted file mode 100644 index 0f32675f8e..0000000000 --- a/patches/server/0769-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 12 Sep 2021 00:14:21 +0200 -Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when - tab-complete is disabled - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index fe2b99d0cde747c86cdc04c3d48f717b94747101..f63ff3240b477e86e45bd7572ab0dda308bab5f3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -847,6 +847,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // Paper end - // CraftBukkit end -+ // Paper start - Don't suggest if tab-complete is disabled -+ if (org.spigotmc.SpigotConfig.tabComplete < 0) { -+ return; -+ } -+ // Paper end - Don't suggest if tab-complete is disabled - // Paper start - async tab completion - TAB_COMPLETE_EXECUTOR.execute(() -> { - StringReader stringreader = new StringReader(packet.getCommand()); diff --git a/patches/server/0769-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch b/patches/server/0769-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch new file mode 100644 index 0000000000..8bc6527cd8 --- /dev/null +++ b/patches/server/0769-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bjarne Koll +Date: Sat, 6 Nov 2021 23:15:20 +0100 +Subject: [PATCH] Fix setPatternColor on tropical fish bucket meta + +Prior to this commit, the tropical fish bucket meta would set the body +color to the previous metas pattern colour when updating the pattern +colour. + +This commit hence simply fixes this by using the proper body colour +value when updating the pattern color. + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +index 581e0f4d68d6eb8eb04449586ffdba35e8b3ad2b..9a045a7793ec20334853a0e1c3529b31899214b3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java +@@ -107,7 +107,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB + if (this.variant == null) { + this.variant = 0; + } +- this.variant = CraftTropicalFish.getData(color, this.getPatternColor(), this.getPattern()); ++ this.variant = CraftTropicalFish.getData(color, this.getBodyColor(), this.getPattern()); // Paper - properly set tropical fish pattern color without mutating body color + } + + @Override +diff --git a/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2e7f8ef88ae74c7cbfdb7f397951cbc8479a995f +--- /dev/null ++++ b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java +@@ -0,0 +1,40 @@ ++package io.papermc.paper.inventory; ++ ++import org.bukkit.DyeColor; ++import org.bukkit.Material; ++import org.bukkit.entity.TropicalFish; ++import org.bukkit.inventory.ItemStack; ++import org.bukkit.inventory.meta.TropicalFishBucketMeta; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.Assert; ++import org.junit.Test; ++ ++public class CraftMetaTropicalFishBucketTest extends AbstractTestingBase { ++ ++ @Test ++ public void testAllCombinations() { ++ final var rawMeta = new ItemStack(Material.TROPICAL_FISH_BUCKET).getItemMeta(); ++ Assert.assertTrue("Meta was not a tropical fish bucket", rawMeta instanceof TropicalFishBucketMeta); ++ ++ final var meta = (TropicalFishBucketMeta) rawMeta; ++ ++ for (final var bodyColor : DyeColor.values()) { ++ for (final var pattern : TropicalFish.Pattern.values()) { ++ for (final var patternColor : DyeColor.values()) { ++ meta.setBodyColor(bodyColor); ++ Assert.assertEquals("Body color did not match post body color!", bodyColor, meta.getBodyColor()); ++ ++ meta.setPattern(pattern); ++ Assert.assertEquals("Pattern did not match post pattern!", pattern, meta.getPattern()); ++ Assert.assertEquals("Body color did not match post pattern!", bodyColor, meta.getBodyColor()); ++ ++ meta.setPatternColor(patternColor); ++ Assert.assertEquals("Pattern did not match post pattern color!", pattern, meta.getPattern()); ++ Assert.assertEquals("Body color did not match post pattern color!", bodyColor, meta.getBodyColor()); ++ Assert.assertEquals("Pattern color did not match post pattern color!", patternColor, meta.getPatternColor()); ++ } ++ } ++ } ++ } ++ ++} diff --git a/patches/server/0770-Ensure-valid-vehicle-status.patch b/patches/server/0770-Ensure-valid-vehicle-status.patch new file mode 100644 index 0000000000..2d1f854e7d --- /dev/null +++ b/patches/server/0770-Ensure-valid-vehicle-status.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 28 Sep 2021 09:47:47 +0200 +Subject: [PATCH] Ensure valid vehicle status + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 951ccf3526dc2f5e4e2f16952036683ad132fbe0..97de35c614e1e9b0e825f9914173a3e1e0e53221 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -513,7 +513,7 @@ public class ServerPlayer extends Player { + } + } + +- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { ++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper + // CraftBukkit end + CompoundTag nbttagcompound2 = new CompoundTag(); + CompoundTag nbttagcompound3 = new CompoundTag(); diff --git a/patches/server/0770-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch b/patches/server/0770-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch deleted file mode 100644 index 81bcca30af..0000000000 --- a/patches/server/0770-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Bjarne Koll -Date: Sat, 6 Nov 2021 23:15:20 +0100 -Subject: [PATCH] Fix setPatternColor on tropical fish bucket meta - -Prior to this commit, the tropical fish bucket meta would set the body -color to the previous metas pattern colour when updating the pattern -colour. - -This commit hence simply fixes this by using the proper body colour -value when updating the pattern color. - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java -index 41eca0171faa5dc03b4e426a75f29b7bb4c3f6e2..acdc5c81850f9191061d4a804bd3524e717d24a2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java -@@ -113,7 +113,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB - if (this.variant == null) { - this.variant = 0; - } -- this.variant = CraftTropicalFish.getData(color, this.getPatternColor(), this.getPattern()); -+ this.variant = CraftTropicalFish.getData(color, this.getBodyColor(), this.getPattern()); // Paper - properly set tropical fish pattern color without mutating body color - } - - @Override -diff --git a/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2e7f8ef88ae74c7cbfdb7f397951cbc8479a995f ---- /dev/null -+++ b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java -@@ -0,0 +1,40 @@ -+package io.papermc.paper.inventory; -+ -+import org.bukkit.DyeColor; -+import org.bukkit.Material; -+import org.bukkit.entity.TropicalFish; -+import org.bukkit.inventory.ItemStack; -+import org.bukkit.inventory.meta.TropicalFishBucketMeta; -+import org.bukkit.support.AbstractTestingBase; -+import org.junit.Assert; -+import org.junit.Test; -+ -+public class CraftMetaTropicalFishBucketTest extends AbstractTestingBase { -+ -+ @Test -+ public void testAllCombinations() { -+ final var rawMeta = new ItemStack(Material.TROPICAL_FISH_BUCKET).getItemMeta(); -+ Assert.assertTrue("Meta was not a tropical fish bucket", rawMeta instanceof TropicalFishBucketMeta); -+ -+ final var meta = (TropicalFishBucketMeta) rawMeta; -+ -+ for (final var bodyColor : DyeColor.values()) { -+ for (final var pattern : TropicalFish.Pattern.values()) { -+ for (final var patternColor : DyeColor.values()) { -+ meta.setBodyColor(bodyColor); -+ Assert.assertEquals("Body color did not match post body color!", bodyColor, meta.getBodyColor()); -+ -+ meta.setPattern(pattern); -+ Assert.assertEquals("Pattern did not match post pattern!", pattern, meta.getPattern()); -+ Assert.assertEquals("Body color did not match post pattern!", bodyColor, meta.getBodyColor()); -+ -+ meta.setPatternColor(patternColor); -+ Assert.assertEquals("Pattern did not match post pattern color!", pattern, meta.getPattern()); -+ Assert.assertEquals("Body color did not match post pattern color!", bodyColor, meta.getBodyColor()); -+ Assert.assertEquals("Pattern color did not match post pattern color!", patternColor, meta.getPatternColor()); -+ } -+ } -+ } -+ } -+ -+} diff --git a/patches/server/0771-Ensure-valid-vehicle-status.patch b/patches/server/0771-Ensure-valid-vehicle-status.patch deleted file mode 100644 index 2d1f854e7d..0000000000 --- a/patches/server/0771-Ensure-valid-vehicle-status.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 28 Sep 2021 09:47:47 +0200 -Subject: [PATCH] Ensure valid vehicle status - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 951ccf3526dc2f5e4e2f16952036683ad132fbe0..97de35c614e1e9b0e825f9914173a3e1e0e53221 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -513,7 +513,7 @@ public class ServerPlayer extends Player { - } - } - -- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { -+ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - // CraftBukkit end - CompoundTag nbttagcompound2 = new CompoundTag(); - CompoundTag nbttagcompound3 = new CompoundTag(); diff --git a/patches/server/0771-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0771-Prevent-softlocked-end-exit-portal-generation.patch new file mode 100644 index 0000000000..2286afa5f2 --- /dev/null +++ b/patches/server/0771-Prevent-softlocked-end-exit-portal-generation.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Mon, 30 Aug 2021 15:22:18 +0200 +Subject: [PATCH] Prevent softlocked end exit portal generation + + +diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +index 99b175625c79fe5c4d944810e3fe11be5eed997f..f8d846345c1cc3c78f9ac14635b26f2affc77190 100644 +--- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java ++++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +@@ -413,6 +413,11 @@ public class EndDragonFight { + } + } + ++ // Paper start - Prevent "softlocked" exit portal generation ++ if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) { ++ this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1); ++ } ++ // Paper end + endPodiumFeature.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation); + } + diff --git a/patches/server/0772-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch b/patches/server/0772-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch new file mode 100644 index 0000000000..b415f668ab --- /dev/null +++ b/patches/server/0772-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 7 Sep 2021 21:29:38 +0100 +Subject: [PATCH] Fix CocaoDecorator causing a crash when trying to generate + without logs + + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java +index 42b4b306ee89a9e422d234bdaa9b43b118f8bd0a..0fc355bd847749f7ce716b283dd571f143824795 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java +@@ -25,6 +25,7 @@ public class CocoaDecorator extends TreeDecorator { + + @Override + public void place(TreeDecorator.Context generator) { ++ if (generator.logs().isEmpty()) return; // Paper + RandomSource randomSource = generator.random(); + if (!(randomSource.nextFloat() >= this.probability)) { + List list = generator.logs(); diff --git a/patches/server/0772-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0772-Prevent-softlocked-end-exit-portal-generation.patch deleted file mode 100644 index 2286afa5f2..0000000000 --- a/patches/server/0772-Prevent-softlocked-end-exit-portal-generation.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Mon, 30 Aug 2021 15:22:18 +0200 -Subject: [PATCH] Prevent softlocked end exit portal generation - - -diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index 99b175625c79fe5c4d944810e3fe11be5eed997f..f8d846345c1cc3c78f9ac14635b26f2affc77190 100644 ---- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -+++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -413,6 +413,11 @@ public class EndDragonFight { - } - } - -+ // Paper start - Prevent "softlocked" exit portal generation -+ if (this.portalLocation.getY() <= this.level.getMinBuildHeight()) { -+ this.portalLocation = this.portalLocation.atY(this.level.getMinBuildHeight() + 1); -+ } -+ // Paper end - endPodiumFeature.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation); - } - diff --git a/patches/server/0773-Don-t-log-debug-logging-being-disabled.patch b/patches/server/0773-Don-t-log-debug-logging-being-disabled.patch new file mode 100644 index 0000000000..83460a961a --- /dev/null +++ b/patches/server/0773-Don-t-log-debug-logging-being-disabled.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Tue, 14 Sep 2021 16:24:45 +0200 +Subject: [PATCH] Don't log debug logging being disabled + + +diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java +index edc5f195cc3de8885b839469656650ba465346be..5d162f59fc5ef9adf7fa762b137bbcfca745d9c5 100644 +--- a/src/main/java/org/spigotmc/SpigotConfig.java ++++ b/src/main/java/org/spigotmc/SpigotConfig.java +@@ -382,7 +382,7 @@ public class SpigotConfig + Bukkit.getLogger().info( "Debug logging is enabled" ); + } else + { +- Bukkit.getLogger().info( "Debug logging is disabled" ); ++ // Bukkit.getLogger().info( "Debug logging is disabled" ); // Paper - Don't log if debug logging isn't enabled. + } + } + diff --git a/patches/server/0773-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch b/patches/server/0773-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch deleted file mode 100644 index b415f668ab..0000000000 --- a/patches/server/0773-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 7 Sep 2021 21:29:38 +0100 -Subject: [PATCH] Fix CocaoDecorator causing a crash when trying to generate - without logs - - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java -index 42b4b306ee89a9e422d234bdaa9b43b118f8bd0a..0fc355bd847749f7ce716b283dd571f143824795 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java -@@ -25,6 +25,7 @@ public class CocoaDecorator extends TreeDecorator { - - @Override - public void place(TreeDecorator.Context generator) { -+ if (generator.logs().isEmpty()) return; // Paper - RandomSource randomSource = generator.random(); - if (!(randomSource.nextFloat() >= this.probability)) { - List list = generator.logs(); diff --git a/patches/server/0774-Don-t-log-debug-logging-being-disabled.patch b/patches/server/0774-Don-t-log-debug-logging-being-disabled.patch deleted file mode 100644 index 83460a961a..0000000000 --- a/patches/server/0774-Don-t-log-debug-logging-being-disabled.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Tue, 14 Sep 2021 16:24:45 +0200 -Subject: [PATCH] Don't log debug logging being disabled - - -diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index edc5f195cc3de8885b839469656650ba465346be..5d162f59fc5ef9adf7fa762b137bbcfca745d9c5 100644 ---- a/src/main/java/org/spigotmc/SpigotConfig.java -+++ b/src/main/java/org/spigotmc/SpigotConfig.java -@@ -382,7 +382,7 @@ public class SpigotConfig - Bukkit.getLogger().info( "Debug logging is enabled" ); - } else - { -- Bukkit.getLogger().info( "Debug logging is disabled" ); -+ // Bukkit.getLogger().info( "Debug logging is disabled" ); // Paper - Don't log if debug logging isn't enabled. - } - } - diff --git a/patches/server/0774-fix-various-menus-with-empty-level-accesses.patch b/patches/server/0774-fix-various-menus-with-empty-level-accesses.patch new file mode 100644 index 0000000000..3bda24b9c6 --- /dev/null +++ b/patches/server/0774-fix-various-menus-with-empty-level-accesses.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 11 Jul 2021 12:52:56 -0700 +Subject: [PATCH] fix various menus with empty level accesses + + +diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +index c96b04f045dda384cdee9254a1765ef97e5f7f03..f00a957a0f55e69f93e6d7dc80193304447c3dcb 100644 +--- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java ++++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java +@@ -27,6 +27,12 @@ public interface ContainerLevelAccess { + public Optional evaluate(BiFunction getter) { + return Optional.empty(); + } ++ // Paper start ++ @Override ++ public org.bukkit.Location getLocation() { ++ return null; ++ } ++ // Paper end + }; + + static ContainerLevelAccess create(final Level world, final BlockPos pos) { diff --git a/patches/server/0775-Preserve-overstacked-loot.patch b/patches/server/0775-Preserve-overstacked-loot.patch new file mode 100644 index 0000000000..e39eb87bbc --- /dev/null +++ b/patches/server/0775-Preserve-overstacked-loot.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lexikiq +Date: Mon, 21 Jun 2021 23:21:58 -0400 +Subject: [PATCH] Preserve overstacked loot + +Preserves overstacked items in loot tables, such as shulker box drops, to prevent the items +from being deleted (as they'd overflow past the bounds of the container)-- or worse, causing +chunk bans via the large amount of NBT created by unstacking the items. + +Fixes GH-5140 and GH-4748. + +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +index 9459b912615c692de7d0ceb6cf5a1cd3516d438b..375d26ac2453f637bac3fa89873b6760095916b7 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java +@@ -56,9 +56,17 @@ public class LootTable { + this.compositeFunction = LootItemFunctions.compose(functions); + } + ++ @Deprecated // Paper - preserve overstacked items + public static Consumer createStackSplitter(Consumer lootConsumer) { ++ // Paper start - preserve overstacked items ++ return createStackSplitter(lootConsumer, null); ++ } ++ ++ public static Consumer createStackSplitter(Consumer lootConsumer, @org.jetbrains.annotations.Nullable net.minecraft.server.level.ServerLevel world) { ++ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; ++ // Paper end + return (itemstack) -> { +- if (itemstack.getCount() < itemstack.getMaxStackSize()) { ++ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items + lootConsumer.accept(itemstack); + } else { + int i = itemstack.getCount(); +@@ -95,7 +103,7 @@ public class LootTable { + } + + public void getRandomItems(LootContext context, Consumer lootConsumer) { +- this.getRandomItemsRaw(context, LootTable.createStackSplitter(lootConsumer)); ++ this.getRandomItemsRaw(context, LootTable.createStackSplitter(lootConsumer, context.getLevel())); // Paper - preserve overstacked items + } + + public ObjectArrayList getRandomItems(LootContext context) { +diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java +index 057676201aa2d19032537832849f3857425d357a..b5c6b7280a9c6964e2ad4aa9bd4517146c98e727 100644 +--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java ++++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java +@@ -46,7 +46,7 @@ public class SetContainerContents extends LootItemConditionalFunction { + NonNullList nonNullList = NonNullList.create(); + this.entries.forEach((entry) -> { + entry.expand(context, (choice) -> { +- choice.createItemStack(LootTable.createStackSplitter(nonNullList::add), context); ++ choice.createItemStack(LootTable.createStackSplitter(nonNullList::add, context.getLevel()), context); // Paper - preserve overstacked items + }); + }); + CompoundTag compoundTag = new CompoundTag(); diff --git a/patches/server/0775-fix-various-menus-with-empty-level-accesses.patch b/patches/server/0775-fix-various-menus-with-empty-level-accesses.patch deleted file mode 100644 index 3bda24b9c6..0000000000 --- a/patches/server/0775-fix-various-menus-with-empty-level-accesses.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 11 Jul 2021 12:52:56 -0700 -Subject: [PATCH] fix various menus with empty level accesses - - -diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -index c96b04f045dda384cdee9254a1765ef97e5f7f03..f00a957a0f55e69f93e6d7dc80193304447c3dcb 100644 ---- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -+++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java -@@ -27,6 +27,12 @@ public interface ContainerLevelAccess { - public Optional evaluate(BiFunction getter) { - return Optional.empty(); - } -+ // Paper start -+ @Override -+ public org.bukkit.Location getLocation() { -+ return null; -+ } -+ // Paper end - }; - - static ContainerLevelAccess create(final Level world, final BlockPos pos) { diff --git a/patches/server/0776-Preserve-overstacked-loot.patch b/patches/server/0776-Preserve-overstacked-loot.patch deleted file mode 100644 index e39eb87bbc..0000000000 --- a/patches/server/0776-Preserve-overstacked-loot.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lexikiq -Date: Mon, 21 Jun 2021 23:21:58 -0400 -Subject: [PATCH] Preserve overstacked loot - -Preserves overstacked items in loot tables, such as shulker box drops, to prevent the items -from being deleted (as they'd overflow past the bounds of the container)-- or worse, causing -chunk bans via the large amount of NBT created by unstacking the items. - -Fixes GH-5140 and GH-4748. - -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -index 9459b912615c692de7d0ceb6cf5a1cd3516d438b..375d26ac2453f637bac3fa89873b6760095916b7 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java -@@ -56,9 +56,17 @@ public class LootTable { - this.compositeFunction = LootItemFunctions.compose(functions); - } - -+ @Deprecated // Paper - preserve overstacked items - public static Consumer createStackSplitter(Consumer lootConsumer) { -+ // Paper start - preserve overstacked items -+ return createStackSplitter(lootConsumer, null); -+ } -+ -+ public static Consumer createStackSplitter(Consumer lootConsumer, @org.jetbrains.annotations.Nullable net.minecraft.server.level.ServerLevel world) { -+ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; -+ // Paper end - return (itemstack) -> { -- if (itemstack.getCount() < itemstack.getMaxStackSize()) { -+ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items - lootConsumer.accept(itemstack); - } else { - int i = itemstack.getCount(); -@@ -95,7 +103,7 @@ public class LootTable { - } - - public void getRandomItems(LootContext context, Consumer lootConsumer) { -- this.getRandomItemsRaw(context, LootTable.createStackSplitter(lootConsumer)); -+ this.getRandomItemsRaw(context, LootTable.createStackSplitter(lootConsumer, context.getLevel())); // Paper - preserve overstacked items - } - - public ObjectArrayList getRandomItems(LootContext context) { -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java -index 057676201aa2d19032537832849f3857425d357a..b5c6b7280a9c6964e2ad4aa9bd4517146c98e727 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/SetContainerContents.java -@@ -46,7 +46,7 @@ public class SetContainerContents extends LootItemConditionalFunction { - NonNullList nonNullList = NonNullList.create(); - this.entries.forEach((entry) -> { - entry.expand(context, (choice) -> { -- choice.createItemStack(LootTable.createStackSplitter(nonNullList::add), context); -+ choice.createItemStack(LootTable.createStackSplitter(nonNullList::add, context.getLevel()), context); // Paper - preserve overstacked items - }); - }); - CompoundTag compoundTag = new CompoundTag(); diff --git a/patches/server/0776-Update-head-rotation-in-missing-places.patch b/patches/server/0776-Update-head-rotation-in-missing-places.patch new file mode 100644 index 0000000000..9b18797ea0 --- /dev/null +++ b/patches/server/0776-Update-head-rotation-in-missing-places.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 21:55:23 -0400 +Subject: [PATCH] Update head rotation in missing places + +In certain areas the player's head rotation could be desynced when teleported/moved. +This is because bukkit uses a separate head rotation field for yaw. +This issue only applies to players. + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 51239bc47d6d2cfd8d345fb67fe0d92fabe53209..cef2cad3c278c4d2258f68dcbe6936c6637ca90a 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1763,6 +1763,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); + this.yRotO = this.getYRot(); + this.xRotO = this.getXRot(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public void absMoveTo(double x, double y, double z) { +@@ -1801,6 +1802,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.setXRot(pitch); + this.setOldPosAndRot(); + this.reapplyPosition(); ++ this.setYHeadRot(yaw); // Paper - Update head rotation + } + + public final void setOldPosAndRot() { diff --git a/patches/server/0777-Update-head-rotation-in-missing-places.patch b/patches/server/0777-Update-head-rotation-in-missing-places.patch deleted file mode 100644 index 04499b065f..0000000000 --- a/patches/server/0777-Update-head-rotation-in-missing-places.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 21:55:23 -0400 -Subject: [PATCH] Update head rotation in missing places - -In certain areas the player's head rotation could be desynced when teleported/moved. -This is because bukkit uses a separate head rotation field for yaw. -This issue only applies to players. - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 37123198bdf0188f59f289a31570663938fdc3c1..71fd35d9d89d878109b0d4d0ae73a99063059eec 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1749,6 +1749,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); - this.yRotO = this.getYRot(); - this.xRotO = this.getXRot(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public void absMoveTo(double x, double y, double z) { -@@ -1787,6 +1788,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.setXRot(pitch); - this.setOldPosAndRot(); - this.reapplyPosition(); -+ this.setYHeadRot(yaw); // Paper - Update head rotation - } - - public final void setOldPosAndRot() { diff --git a/patches/server/0777-prevent-unintended-light-block-manipulation.patch b/patches/server/0777-prevent-unintended-light-block-manipulation.patch new file mode 100644 index 0000000000..08bd6164f5 --- /dev/null +++ b/patches/server/0777-prevent-unintended-light-block-manipulation.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 13 Sep 2021 18:55:45 -0700 +Subject: [PATCH] prevent unintended light block manipulation + + +diff --git a/src/main/java/net/minecraft/world/level/block/LightBlock.java b/src/main/java/net/minecraft/world/level/block/LightBlock.java +index d648902737350103c2078c80796038a054f16acc..98124ee3bea51e40a9a3cb9014ee84bfc26e91f7 100644 +--- a/src/main/java/net/minecraft/world/level/block/LightBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LightBlock.java +@@ -46,6 +46,7 @@ public class LightBlock extends Block implements SimpleWaterloggedBlock { + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (!world.isClientSide) { ++ if (player.getItemInHand(hand).getItem() != Items.LIGHT || !player.mayInteract(world, pos) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return InteractionResult.FAIL; } // Paper + world.setBlock(pos, state.cycle(LEVEL), 2); + return InteractionResult.SUCCESS; + } else { diff --git a/patches/server/0778-Fix-CraftCriteria-defaults-map.patch b/patches/server/0778-Fix-CraftCriteria-defaults-map.patch new file mode 100644 index 0000000000..65eeffccea --- /dev/null +++ b/patches/server/0778-Fix-CraftCriteria-defaults-map.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 4 Oct 2021 22:31:51 -0700 +Subject: [PATCH] Fix CraftCriteria defaults map + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java +index a8728102499ec8a0b4946bcc9b59c16193731f8c..d849ef9a51dc901c8045d63218b8ee5fa5c7ee7a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java +@@ -54,7 +54,7 @@ public final class CraftCriteria implements Criteria { + } + + static CraftCriteria getFromNMS(Objective objective) { +- return CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()); ++ return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()), () -> new CraftCriteria(objective.getCriteria())); // Paper + } + + public static CraftCriteria getFromBukkit(String name) { diff --git a/patches/server/0778-prevent-unintended-light-block-manipulation.patch b/patches/server/0778-prevent-unintended-light-block-manipulation.patch deleted file mode 100644 index 08bd6164f5..0000000000 --- a/patches/server/0778-prevent-unintended-light-block-manipulation.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 13 Sep 2021 18:55:45 -0700 -Subject: [PATCH] prevent unintended light block manipulation - - -diff --git a/src/main/java/net/minecraft/world/level/block/LightBlock.java b/src/main/java/net/minecraft/world/level/block/LightBlock.java -index d648902737350103c2078c80796038a054f16acc..98124ee3bea51e40a9a3cb9014ee84bfc26e91f7 100644 ---- a/src/main/java/net/minecraft/world/level/block/LightBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LightBlock.java -@@ -46,6 +46,7 @@ public class LightBlock extends Block implements SimpleWaterloggedBlock { - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (!world.isClientSide) { -+ if (player.getItemInHand(hand).getItem() != Items.LIGHT || !player.mayInteract(world, pos) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return InteractionResult.FAIL; } // Paper - world.setBlock(pos, state.cycle(LEVEL), 2); - return InteractionResult.SUCCESS; - } else { diff --git a/patches/server/0779-Fix-CraftCriteria-defaults-map.patch b/patches/server/0779-Fix-CraftCriteria-defaults-map.patch deleted file mode 100644 index 65eeffccea..0000000000 --- a/patches/server/0779-Fix-CraftCriteria-defaults-map.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 4 Oct 2021 22:31:51 -0700 -Subject: [PATCH] Fix CraftCriteria defaults map - - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java -index a8728102499ec8a0b4946bcc9b59c16193731f8c..d849ef9a51dc901c8045d63218b8ee5fa5c7ee7a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java -@@ -54,7 +54,7 @@ public final class CraftCriteria implements Criteria { - } - - static CraftCriteria getFromNMS(Objective objective) { -- return CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()); -+ return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()), () -> new CraftCriteria(objective.getCriteria())); // Paper - } - - public static CraftCriteria getFromBukkit(String name) { diff --git a/patches/server/0779-Fix-upstreams-block-state-factories.patch b/patches/server/0779-Fix-upstreams-block-state-factories.patch new file mode 100644 index 0000000000..221799c0b1 --- /dev/null +++ b/patches/server/0779-Fix-upstreams-block-state-factories.patch @@ -0,0 +1,370 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Oct 2021 20:50:48 -0700 +Subject: [PATCH] Fix upstreams block state factories + +Sometimes, blocks are changed and then logic is called before the associated +tile entity is removed. When this happens, the factories were relying on the +block at the position, not the tile entity. This change prioritizes using the +tile entity type to determine the block state factory and falls back on +the material type of the block at that location. + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index b0174aedb7358af1e80278e2f5f13e00c35ab3c6..d62181bd8bccfcfdd7da8f635bdf7ebc36294705 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -250,7 +250,7 @@ public abstract class BlockEntity { + // Paper end + if (this.level == null) return null; + org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); +- if (block.getType() == org.bukkit.Material.AIR) return null; ++ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists + 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/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 87c25170fbe8b0591d452612496ee1a627138de7..a2894f02ceb7c58f6eafe055e1ff47b197b100f2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -6,7 +6,7 @@ import org.bukkit.World; + import org.bukkit.block.TileState; + import org.bukkit.persistence.PersistentDataContainer; + +-public class CraftBlockEntityState extends CraftBlockState implements TileState { ++public abstract class CraftBlockEntityState extends CraftBlockState implements TileState { // Paper - revert upstream's revert of the block state changes + + private final T tileEntity; + private final T snapshot; +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +index e609cfeaf5aa6807f57360dde9b0dccf40a23eb1..594b071cbb25d6e8f184c8558c3d1f490fe4712f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -19,6 +19,7 @@ import net.minecraft.world.level.block.entity.BeehiveBlockEntity; + import net.minecraft.world.level.block.entity.BellBlockEntity; + import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; + import net.minecraft.world.level.block.entity.BlockEntity; ++import net.minecraft.world.level.block.entity.BlockEntityType; // Paper + import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; + import net.minecraft.world.level.block.entity.CampfireBlockEntity; + import net.minecraft.world.level.block.entity.ChestBlockEntity; +@@ -108,187 +109,57 @@ public final class CraftBlockStates { + private static final BlockStateFactory DEFAULT_FACTORY = new BlockStateFactory(CraftBlockState.class) { + @Override + public CraftBlockState createBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { +- // SPIGOT-6754, SPIGOT-6817: Restore previous behaviour for tile entities with removed blocks (loot generation post-destroy) +- if (tileEntity != null) { +- // block with unhandled TileEntity: +- return new CraftBlockEntityState<>(world, tileEntity); +- } ++ // Paper - revert upstream's revert of the block state changes. Block entities that have already had the block type set to AIR are still valid, upstream decided to ignore them + Preconditions.checkState(tileEntity == null, "Unexpected BlockState for %s", CraftMagicNumbers.getMaterial(blockData.getBlock())); + return new CraftBlockState(world, blockPosition, blockData); + } + }; ++ // Paper start ++ private static final Map, BlockStateFactory> FACTORIES_BY_BLOCK_ENTITY_TYPE = new HashMap<>(); ++ private static void register(BlockEntityType type, BlockStateFactory factory) { ++ FACTORIES_BY_BLOCK_ENTITY_TYPE.put(type, factory); ++ } ++ // Paper end + + static { +- register( +- Arrays.asList( +- Material.ACACIA_SIGN, +- Material.ACACIA_WALL_SIGN, +- Material.BIRCH_SIGN, +- Material.BIRCH_WALL_SIGN, +- Material.CRIMSON_SIGN, +- Material.CRIMSON_WALL_SIGN, +- Material.DARK_OAK_SIGN, +- Material.DARK_OAK_WALL_SIGN, +- Material.JUNGLE_SIGN, +- Material.JUNGLE_WALL_SIGN, +- Material.MANGROVE_SIGN, +- Material.MANGROVE_WALL_SIGN, +- Material.OAK_SIGN, +- Material.OAK_WALL_SIGN, +- Material.SPRUCE_SIGN, +- Material.SPRUCE_WALL_SIGN, +- Material.WARPED_SIGN, +- Material.WARPED_WALL_SIGN +- ), CraftSign.class, CraftSign::new, SignBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.CREEPER_HEAD, +- Material.CREEPER_WALL_HEAD, +- Material.DRAGON_HEAD, +- Material.DRAGON_WALL_HEAD, +- Material.PLAYER_HEAD, +- Material.PLAYER_WALL_HEAD, +- Material.SKELETON_SKULL, +- Material.SKELETON_WALL_SKULL, +- Material.WITHER_SKELETON_SKULL, +- Material.WITHER_SKELETON_WALL_SKULL, +- Material.ZOMBIE_HEAD, +- Material.ZOMBIE_WALL_HEAD +- ), CraftSkull.class, CraftSkull::new, SkullBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.COMMAND_BLOCK, +- Material.REPEATING_COMMAND_BLOCK, +- Material.CHAIN_COMMAND_BLOCK +- ), CraftCommandBlock.class, CraftCommandBlock::new, CommandBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BLACK_BANNER, +- Material.BLACK_WALL_BANNER, +- Material.BLUE_BANNER, +- Material.BLUE_WALL_BANNER, +- Material.BROWN_BANNER, +- Material.BROWN_WALL_BANNER, +- Material.CYAN_BANNER, +- Material.CYAN_WALL_BANNER, +- Material.GRAY_BANNER, +- Material.GRAY_WALL_BANNER, +- Material.GREEN_BANNER, +- Material.GREEN_WALL_BANNER, +- Material.LIGHT_BLUE_BANNER, +- Material.LIGHT_BLUE_WALL_BANNER, +- Material.LIGHT_GRAY_BANNER, +- Material.LIGHT_GRAY_WALL_BANNER, +- Material.LIME_BANNER, +- Material.LIME_WALL_BANNER, +- Material.MAGENTA_BANNER, +- Material.MAGENTA_WALL_BANNER, +- Material.ORANGE_BANNER, +- Material.ORANGE_WALL_BANNER, +- Material.PINK_BANNER, +- Material.PINK_WALL_BANNER, +- Material.PURPLE_BANNER, +- Material.PURPLE_WALL_BANNER, +- Material.RED_BANNER, +- Material.RED_WALL_BANNER, +- Material.WHITE_BANNER, +- Material.WHITE_WALL_BANNER, +- Material.YELLOW_BANNER, +- Material.YELLOW_WALL_BANNER +- ), CraftBanner.class, CraftBanner::new, BannerBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.SHULKER_BOX, +- Material.WHITE_SHULKER_BOX, +- Material.ORANGE_SHULKER_BOX, +- Material.MAGENTA_SHULKER_BOX, +- Material.LIGHT_BLUE_SHULKER_BOX, +- Material.YELLOW_SHULKER_BOX, +- Material.LIME_SHULKER_BOX, +- Material.PINK_SHULKER_BOX, +- Material.GRAY_SHULKER_BOX, +- Material.LIGHT_GRAY_SHULKER_BOX, +- Material.CYAN_SHULKER_BOX, +- Material.PURPLE_SHULKER_BOX, +- Material.BLUE_SHULKER_BOX, +- Material.BROWN_SHULKER_BOX, +- Material.GREEN_SHULKER_BOX, +- Material.RED_SHULKER_BOX, +- Material.BLACK_SHULKER_BOX +- ), CraftShulkerBox.class, CraftShulkerBox::new, ShulkerBoxBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BLACK_BED, +- Material.BLUE_BED, +- Material.BROWN_BED, +- Material.CYAN_BED, +- Material.GRAY_BED, +- Material.GREEN_BED, +- Material.LIGHT_BLUE_BED, +- Material.LIGHT_GRAY_BED, +- Material.LIME_BED, +- Material.MAGENTA_BED, +- Material.ORANGE_BED, +- Material.PINK_BED, +- Material.PURPLE_BED, +- Material.RED_BED, +- Material.WHITE_BED, +- Material.YELLOW_BED +- ), CraftBed.class, CraftBed::new, BedBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.BEEHIVE, +- Material.BEE_NEST +- ), CraftBeehive.class, CraftBeehive::new, BeehiveBlockEntity::new +- ); +- +- register( +- Arrays.asList( +- Material.CAMPFIRE, +- Material.SOUL_CAMPFIRE +- ), CraftCampfire.class, CraftCampfire::new, CampfireBlockEntity::new +- ); +- +- register(Material.BARREL, CraftBarrel.class, CraftBarrel::new, BarrelBlockEntity::new); +- register(Material.BEACON, CraftBeacon.class, CraftBeacon::new, BeaconBlockEntity::new); +- register(Material.BELL, CraftBell.class, CraftBell::new, BellBlockEntity::new); +- register(Material.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new, BlastFurnaceBlockEntity::new); +- register(Material.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new, BrewingStandBlockEntity::new); +- register(Material.CHEST, CraftChest.class, CraftChest::new, ChestBlockEntity::new); +- register(Material.COMPARATOR, CraftComparator.class, CraftComparator::new, ComparatorBlockEntity::new); +- register(Material.CONDUIT, CraftConduit.class, CraftConduit::new, ConduitBlockEntity::new); +- register(Material.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new, DaylightDetectorBlockEntity::new); +- register(Material.DISPENSER, CraftDispenser.class, CraftDispenser::new, DispenserBlockEntity::new); +- register(Material.DROPPER, CraftDropper.class, CraftDropper::new, DropperBlockEntity::new); +- register(Material.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new, EnchantmentTableBlockEntity::new); +- register(Material.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new, EnderChestBlockEntity::new); +- register(Material.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new, TheEndGatewayBlockEntity::new); +- register(Material.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new, TheEndPortalBlockEntity::new); +- register(Material.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new, FurnaceBlockEntity::new); +- register(Material.HOPPER, CraftHopper.class, CraftHopper::new, HopperBlockEntity::new); +- register(Material.JIGSAW, CraftJigsaw.class, CraftJigsaw::new, JigsawBlockEntity::new); +- register(Material.JUKEBOX, CraftJukebox.class, CraftJukebox::new, JukeboxBlockEntity::new); +- register(Material.LECTERN, CraftLectern.class, CraftLectern::new, LecternBlockEntity::new); +- register(Material.MOVING_PISTON, CraftMovingPiston.class, CraftMovingPiston::new, PistonMovingBlockEntity::new); +- register(Material.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new, SculkCatalystBlockEntity::new); +- register(Material.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new, SculkSensorBlockEntity::new); +- register(Material.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new, SculkShriekerBlockEntity::new); +- register(Material.SMOKER, CraftSmoker.class, CraftSmoker::new, SmokerBlockEntity::new); +- register(Material.SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new, SpawnerBlockEntity::new); +- register(Material.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new, StructureBlockEntity::new); +- register(Material.TRAPPED_CHEST, CraftChest.class, CraftChest::new, TrappedChestBlockEntity::new); ++ // Paper start - simplify ++ register(BlockEntityType.SIGN, CraftSign.class, CraftSign::new); ++ register(BlockEntityType.SKULL, CraftSkull.class, CraftSkull::new); ++ register(BlockEntityType.COMMAND_BLOCK, CraftCommandBlock.class, CraftCommandBlock::new); ++ register(BlockEntityType.BANNER, CraftBanner.class, CraftBanner::new); ++ register(BlockEntityType.SHULKER_BOX, CraftShulkerBox.class, CraftShulkerBox::new); ++ register(BlockEntityType.BED, CraftBed.class, CraftBed::new); ++ register(BlockEntityType.BEEHIVE, CraftBeehive.class, CraftBeehive::new); ++ register(BlockEntityType.CAMPFIRE, CraftCampfire.class, CraftCampfire::new); ++ register(BlockEntityType.BARREL, CraftBarrel.class, CraftBarrel::new); ++ register(BlockEntityType.BEACON, CraftBeacon.class, CraftBeacon::new); ++ register(BlockEntityType.BELL, CraftBell.class, CraftBell::new); ++ register(BlockEntityType.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new); ++ register(BlockEntityType.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new); ++ register(BlockEntityType.CHEST, CraftChest.class, CraftChest::new); ++ register(BlockEntityType.COMPARATOR, CraftComparator.class, CraftComparator::new); ++ register(BlockEntityType.CONDUIT, CraftConduit.class, CraftConduit::new); ++ register(BlockEntityType.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new); ++ register(BlockEntityType.DISPENSER, CraftDispenser.class, CraftDispenser::new); ++ register(BlockEntityType.DROPPER, CraftDropper.class, CraftDropper::new); ++ register(BlockEntityType.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new); ++ register(BlockEntityType.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new); ++ register(BlockEntityType.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new); ++ register(BlockEntityType.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new); ++ register(BlockEntityType.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new); ++ register(BlockEntityType.HOPPER, CraftHopper.class, CraftHopper::new); ++ register(BlockEntityType.JIGSAW, CraftJigsaw.class, CraftJigsaw::new); ++ register(BlockEntityType.JUKEBOX, CraftJukebox.class, CraftJukebox::new); ++ register(BlockEntityType.LECTERN, CraftLectern.class, CraftLectern::new); ++ register(BlockEntityType.PISTON, CraftMovingPiston.class, CraftMovingPiston::new); ++ register(BlockEntityType.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new); ++ register(BlockEntityType.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new); ++ register(BlockEntityType.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new); ++ register(BlockEntityType.SMOKER, CraftSmoker.class, CraftSmoker::new); ++ register(BlockEntityType.MOB_SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new); ++ register(BlockEntityType.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new); ++ register(BlockEntityType.TRAPPED_CHEST, CraftChest.class, CraftChest::new); ++ // Paper end + } + + private static void register(Material blockType, BlockStateFactory factory) { +@@ -296,30 +167,33 @@ public final class CraftBlockStates { + } + + private static > void register( +- Material blockType, +- Class blockStateType, +- BiFunction blockStateConstructor, +- BiFunction tileEntityConstructor +- ) { +- CraftBlockStates.register(Collections.singletonList(blockType), blockStateType, blockStateConstructor, tileEntityConstructor); +- } +- +- private static > void register( +- List blockTypes, ++ net.minecraft.world.level.block.entity.BlockEntityType blockEntityType, // Paper + Class blockStateType, +- BiFunction blockStateConstructor, +- BiFunction tileEntityConstructor ++ BiFunction blockStateConstructor // Paper + ) { +- BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, tileEntityConstructor); +- for (Material blockType : blockTypes) { +- CraftBlockStates.register(blockType, factory); ++ // Paper start ++ BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, blockEntityType::create); ++ for (net.minecraft.world.level.block.Block block : blockEntityType.validBlocks) { ++ CraftBlockStates.register(CraftMagicNumbers.getMaterial(block), factory); + } ++ CraftBlockStates.register(blockEntityType, factory); ++ // Paper end + } + + private static BlockStateFactory getFactory(Material material) { + return CraftBlockStates.FACTORIES.getOrDefault(material, DEFAULT_FACTORY); + } + ++ // Paper start ++ private static BlockStateFactory getFactory(Material material, BlockEntityType type) { ++ if (type != null) { ++ return CraftBlockStates.FACTORIES_BY_BLOCK_ENTITY_TYPE.getOrDefault(type, getFactory(material)); ++ } else { ++ return getFactory(material); ++ } ++ } ++ // Paper end ++ + public static Class getBlockStateType(Material material) { + Preconditions.checkNotNull(material, "material is null"); + return CraftBlockStates.getFactory(material).blockStateType; +@@ -335,6 +209,13 @@ public final class CraftBlockStates { + return null; + } + ++ // Paper start ++ public static Class getBlockStateType(BlockEntityType blockEntityType) { ++ Preconditions.checkNotNull(blockEntityType, "blockEntityType is null"); ++ return CraftBlockStates.getFactory(null, blockEntityType).blockStateType; ++ } ++ // Paper end ++ + public static BlockState getBlockState(Block block) { + // Paper start + return CraftBlockStates.getBlockState(block, true); +@@ -392,7 +273,7 @@ public final class CraftBlockStates { + if (world != null && tileEntity == null && CraftBlockStates.isTileEntityOptional(material)) { + factory = CraftBlockStates.DEFAULT_FACTORY; + } else { +- factory = CraftBlockStates.getFactory(material); ++ factory = CraftBlockStates.getFactory(material, tileEntity != null ? tileEntity.getType() : null); // Paper + } + return factory.createBlockState(world, blockPosition, blockData, tileEntity); + } +diff --git a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java +index 15f760c4c71a0493c7c18c3908d229f6b8778a43..95e4a4080a4a168b53f69d9cd7d67c27263c1b7f 100644 +--- a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java ++++ b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java +@@ -45,4 +45,11 @@ public class BlockStateTest extends AbstractTestingBase { + } + } + } ++ ++ @Test ++ public void testBlockEntityTypes() { ++ for (var blockEntityType : Registry.BLOCK_ENTITY_TYPE) { ++ org.junit.Assert.assertNotNull(CraftBlockStates.getBlockStateType(blockEntityType)); ++ } ++ } + } diff --git a/patches/server/0780-Add-config-option-for-logging-player-ip-addresses.patch b/patches/server/0780-Add-config-option-for-logging-player-ip-addresses.patch new file mode 100644 index 0000000000..4e93cf35ca --- /dev/null +++ b/patches/server/0780-Add-config-option-for-logging-player-ip-addresses.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Tue, 5 Oct 2021 20:04:21 +0200 +Subject: [PATCH] Add config option for logging player ip addresses + + +diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +index d6f3869f5725c7f081efb7f486f74dbb99d4d005..8bc0cb9ad5bb4e76d962ff54305e2c08e279a17b 100644 +--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java ++++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java +@@ -51,10 +51,11 @@ public class PacketUtils { + packet.handle(listener); + } catch (Exception exception) { + net.minecraft.network.Connection networkmanager = listener.getConnection(); ++ String playerIP = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : ""; // Paper + if (networkmanager.getPlayer() != null) { +- LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception); ++ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), playerIP, exception); // Paper + } else { +- LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception); ++ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, playerIP, exception); // Paper + } + net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error"); + networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error))); +diff --git a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +index 37cd7b44559e5705b31296df87c94d2ab200138d..37e52ea9ab694b466358981bc2a3c99f28a70c7d 100644 +--- a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java ++++ b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java +@@ -185,7 +185,7 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { + buf.release(); + this.buf = null; + +- LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); ++ LOGGER.debug("Ping: (1.6) from {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? ctx.channel().remoteAddress() : ""); // Paper + + InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); + com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest( +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index 2985271132c9ae822dcb0d7a7e6f0c268d1736cc..cfdbcd024de6ad0f9d4e83b2f912b36ef3299458 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -206,7 +206,7 @@ public class ServerConnectionListener { + throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection")); + } + +- ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", networkmanager.getRemoteAddress(), exception); ++ ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : "", exception); // Paper + MutableComponent ichatmutablecomponent = Component.literal("Internal server error"); + + networkmanager.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> { +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 77cb18da4f89bb89aea7d1ef5ebe3dd7acfe000d..acd581d14e0ef1fe5a6545ee67be00deff589879 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -236,7 +236,10 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + } + + public String getUserName() { +- return this.gameProfile != null ? this.gameProfile + " (" + this.connection.getRemoteAddress() + ")" : String.valueOf(this.connection.getRemoteAddress()); ++ // Paper start ++ String ip = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(this.connection.getRemoteAddress()) : ""; ++ return this.gameProfile != null ? this.gameProfile + " (" + ip + ")" : String.valueOf(ip); ++ // Paper end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 5ee0c3bb27ffbadc1e088983e643eed974753b65..fc14fc8017d89c27b0aeb10a5f38dafde5c15f53 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -243,7 +243,7 @@ public abstract class PlayerList { + final String s1; + + if (connection.getRemoteAddress() != null) { +- s1 = connection.getRemoteAddress().toString(); ++ s1 = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? connection.getRemoteAddress().toString() : ""; // Paper + } else { + s1 = "local"; + } diff --git a/patches/server/0780-Fix-upstreams-block-state-factories.patch b/patches/server/0780-Fix-upstreams-block-state-factories.patch deleted file mode 100644 index 221799c0b1..0000000000 --- a/patches/server/0780-Fix-upstreams-block-state-factories.patch +++ /dev/null @@ -1,370 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Oct 2021 20:50:48 -0700 -Subject: [PATCH] Fix upstreams block state factories - -Sometimes, blocks are changed and then logic is called before the associated -tile entity is removed. When this happens, the factories were relying on the -block at the position, not the tile entity. This change prioritizes using the -tile entity type to determine the block state factory and falls back on -the material type of the block at that location. - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index b0174aedb7358af1e80278e2f5f13e00c35ab3c6..d62181bd8bccfcfdd7da8f635bdf7ebc36294705 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -250,7 +250,7 @@ public abstract class BlockEntity { - // Paper end - if (this.level == null) return null; - org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); -- if (block.getType() == org.bukkit.Material.AIR) return null; -+ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists - 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/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 87c25170fbe8b0591d452612496ee1a627138de7..a2894f02ceb7c58f6eafe055e1ff47b197b100f2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -6,7 +6,7 @@ import org.bukkit.World; - import org.bukkit.block.TileState; - import org.bukkit.persistence.PersistentDataContainer; - --public class CraftBlockEntityState extends CraftBlockState implements TileState { -+public abstract class CraftBlockEntityState extends CraftBlockState implements TileState { // Paper - revert upstream's revert of the block state changes - - private final T tileEntity; - private final T snapshot; -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index e609cfeaf5aa6807f57360dde9b0dccf40a23eb1..594b071cbb25d6e8f184c8558c3d1f490fe4712f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -19,6 +19,7 @@ import net.minecraft.world.level.block.entity.BeehiveBlockEntity; - import net.minecraft.world.level.block.entity.BellBlockEntity; - import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity; - import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntityType; // Paper - import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; - import net.minecraft.world.level.block.entity.CampfireBlockEntity; - import net.minecraft.world.level.block.entity.ChestBlockEntity; -@@ -108,187 +109,57 @@ public final class CraftBlockStates { - private static final BlockStateFactory DEFAULT_FACTORY = new BlockStateFactory(CraftBlockState.class) { - @Override - public CraftBlockState createBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) { -- // SPIGOT-6754, SPIGOT-6817: Restore previous behaviour for tile entities with removed blocks (loot generation post-destroy) -- if (tileEntity != null) { -- // block with unhandled TileEntity: -- return new CraftBlockEntityState<>(world, tileEntity); -- } -+ // Paper - revert upstream's revert of the block state changes. Block entities that have already had the block type set to AIR are still valid, upstream decided to ignore them - Preconditions.checkState(tileEntity == null, "Unexpected BlockState for %s", CraftMagicNumbers.getMaterial(blockData.getBlock())); - return new CraftBlockState(world, blockPosition, blockData); - } - }; -+ // Paper start -+ private static final Map, BlockStateFactory> FACTORIES_BY_BLOCK_ENTITY_TYPE = new HashMap<>(); -+ private static void register(BlockEntityType type, BlockStateFactory factory) { -+ FACTORIES_BY_BLOCK_ENTITY_TYPE.put(type, factory); -+ } -+ // Paper end - - static { -- register( -- Arrays.asList( -- Material.ACACIA_SIGN, -- Material.ACACIA_WALL_SIGN, -- Material.BIRCH_SIGN, -- Material.BIRCH_WALL_SIGN, -- Material.CRIMSON_SIGN, -- Material.CRIMSON_WALL_SIGN, -- Material.DARK_OAK_SIGN, -- Material.DARK_OAK_WALL_SIGN, -- Material.JUNGLE_SIGN, -- Material.JUNGLE_WALL_SIGN, -- Material.MANGROVE_SIGN, -- Material.MANGROVE_WALL_SIGN, -- Material.OAK_SIGN, -- Material.OAK_WALL_SIGN, -- Material.SPRUCE_SIGN, -- Material.SPRUCE_WALL_SIGN, -- Material.WARPED_SIGN, -- Material.WARPED_WALL_SIGN -- ), CraftSign.class, CraftSign::new, SignBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.CREEPER_HEAD, -- Material.CREEPER_WALL_HEAD, -- Material.DRAGON_HEAD, -- Material.DRAGON_WALL_HEAD, -- Material.PLAYER_HEAD, -- Material.PLAYER_WALL_HEAD, -- Material.SKELETON_SKULL, -- Material.SKELETON_WALL_SKULL, -- Material.WITHER_SKELETON_SKULL, -- Material.WITHER_SKELETON_WALL_SKULL, -- Material.ZOMBIE_HEAD, -- Material.ZOMBIE_WALL_HEAD -- ), CraftSkull.class, CraftSkull::new, SkullBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.COMMAND_BLOCK, -- Material.REPEATING_COMMAND_BLOCK, -- Material.CHAIN_COMMAND_BLOCK -- ), CraftCommandBlock.class, CraftCommandBlock::new, CommandBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BLACK_BANNER, -- Material.BLACK_WALL_BANNER, -- Material.BLUE_BANNER, -- Material.BLUE_WALL_BANNER, -- Material.BROWN_BANNER, -- Material.BROWN_WALL_BANNER, -- Material.CYAN_BANNER, -- Material.CYAN_WALL_BANNER, -- Material.GRAY_BANNER, -- Material.GRAY_WALL_BANNER, -- Material.GREEN_BANNER, -- Material.GREEN_WALL_BANNER, -- Material.LIGHT_BLUE_BANNER, -- Material.LIGHT_BLUE_WALL_BANNER, -- Material.LIGHT_GRAY_BANNER, -- Material.LIGHT_GRAY_WALL_BANNER, -- Material.LIME_BANNER, -- Material.LIME_WALL_BANNER, -- Material.MAGENTA_BANNER, -- Material.MAGENTA_WALL_BANNER, -- Material.ORANGE_BANNER, -- Material.ORANGE_WALL_BANNER, -- Material.PINK_BANNER, -- Material.PINK_WALL_BANNER, -- Material.PURPLE_BANNER, -- Material.PURPLE_WALL_BANNER, -- Material.RED_BANNER, -- Material.RED_WALL_BANNER, -- Material.WHITE_BANNER, -- Material.WHITE_WALL_BANNER, -- Material.YELLOW_BANNER, -- Material.YELLOW_WALL_BANNER -- ), CraftBanner.class, CraftBanner::new, BannerBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.SHULKER_BOX, -- Material.WHITE_SHULKER_BOX, -- Material.ORANGE_SHULKER_BOX, -- Material.MAGENTA_SHULKER_BOX, -- Material.LIGHT_BLUE_SHULKER_BOX, -- Material.YELLOW_SHULKER_BOX, -- Material.LIME_SHULKER_BOX, -- Material.PINK_SHULKER_BOX, -- Material.GRAY_SHULKER_BOX, -- Material.LIGHT_GRAY_SHULKER_BOX, -- Material.CYAN_SHULKER_BOX, -- Material.PURPLE_SHULKER_BOX, -- Material.BLUE_SHULKER_BOX, -- Material.BROWN_SHULKER_BOX, -- Material.GREEN_SHULKER_BOX, -- Material.RED_SHULKER_BOX, -- Material.BLACK_SHULKER_BOX -- ), CraftShulkerBox.class, CraftShulkerBox::new, ShulkerBoxBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BLACK_BED, -- Material.BLUE_BED, -- Material.BROWN_BED, -- Material.CYAN_BED, -- Material.GRAY_BED, -- Material.GREEN_BED, -- Material.LIGHT_BLUE_BED, -- Material.LIGHT_GRAY_BED, -- Material.LIME_BED, -- Material.MAGENTA_BED, -- Material.ORANGE_BED, -- Material.PINK_BED, -- Material.PURPLE_BED, -- Material.RED_BED, -- Material.WHITE_BED, -- Material.YELLOW_BED -- ), CraftBed.class, CraftBed::new, BedBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.BEEHIVE, -- Material.BEE_NEST -- ), CraftBeehive.class, CraftBeehive::new, BeehiveBlockEntity::new -- ); -- -- register( -- Arrays.asList( -- Material.CAMPFIRE, -- Material.SOUL_CAMPFIRE -- ), CraftCampfire.class, CraftCampfire::new, CampfireBlockEntity::new -- ); -- -- register(Material.BARREL, CraftBarrel.class, CraftBarrel::new, BarrelBlockEntity::new); -- register(Material.BEACON, CraftBeacon.class, CraftBeacon::new, BeaconBlockEntity::new); -- register(Material.BELL, CraftBell.class, CraftBell::new, BellBlockEntity::new); -- register(Material.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new, BlastFurnaceBlockEntity::new); -- register(Material.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new, BrewingStandBlockEntity::new); -- register(Material.CHEST, CraftChest.class, CraftChest::new, ChestBlockEntity::new); -- register(Material.COMPARATOR, CraftComparator.class, CraftComparator::new, ComparatorBlockEntity::new); -- register(Material.CONDUIT, CraftConduit.class, CraftConduit::new, ConduitBlockEntity::new); -- register(Material.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new, DaylightDetectorBlockEntity::new); -- register(Material.DISPENSER, CraftDispenser.class, CraftDispenser::new, DispenserBlockEntity::new); -- register(Material.DROPPER, CraftDropper.class, CraftDropper::new, DropperBlockEntity::new); -- register(Material.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new, EnchantmentTableBlockEntity::new); -- register(Material.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new, EnderChestBlockEntity::new); -- register(Material.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new, TheEndGatewayBlockEntity::new); -- register(Material.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new, TheEndPortalBlockEntity::new); -- register(Material.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new, FurnaceBlockEntity::new); -- register(Material.HOPPER, CraftHopper.class, CraftHopper::new, HopperBlockEntity::new); -- register(Material.JIGSAW, CraftJigsaw.class, CraftJigsaw::new, JigsawBlockEntity::new); -- register(Material.JUKEBOX, CraftJukebox.class, CraftJukebox::new, JukeboxBlockEntity::new); -- register(Material.LECTERN, CraftLectern.class, CraftLectern::new, LecternBlockEntity::new); -- register(Material.MOVING_PISTON, CraftMovingPiston.class, CraftMovingPiston::new, PistonMovingBlockEntity::new); -- register(Material.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new, SculkCatalystBlockEntity::new); -- register(Material.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new, SculkSensorBlockEntity::new); -- register(Material.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new, SculkShriekerBlockEntity::new); -- register(Material.SMOKER, CraftSmoker.class, CraftSmoker::new, SmokerBlockEntity::new); -- register(Material.SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new, SpawnerBlockEntity::new); -- register(Material.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new, StructureBlockEntity::new); -- register(Material.TRAPPED_CHEST, CraftChest.class, CraftChest::new, TrappedChestBlockEntity::new); -+ // Paper start - simplify -+ register(BlockEntityType.SIGN, CraftSign.class, CraftSign::new); -+ register(BlockEntityType.SKULL, CraftSkull.class, CraftSkull::new); -+ register(BlockEntityType.COMMAND_BLOCK, CraftCommandBlock.class, CraftCommandBlock::new); -+ register(BlockEntityType.BANNER, CraftBanner.class, CraftBanner::new); -+ register(BlockEntityType.SHULKER_BOX, CraftShulkerBox.class, CraftShulkerBox::new); -+ register(BlockEntityType.BED, CraftBed.class, CraftBed::new); -+ register(BlockEntityType.BEEHIVE, CraftBeehive.class, CraftBeehive::new); -+ register(BlockEntityType.CAMPFIRE, CraftCampfire.class, CraftCampfire::new); -+ register(BlockEntityType.BARREL, CraftBarrel.class, CraftBarrel::new); -+ register(BlockEntityType.BEACON, CraftBeacon.class, CraftBeacon::new); -+ register(BlockEntityType.BELL, CraftBell.class, CraftBell::new); -+ register(BlockEntityType.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new); -+ register(BlockEntityType.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new); -+ register(BlockEntityType.CHEST, CraftChest.class, CraftChest::new); -+ register(BlockEntityType.COMPARATOR, CraftComparator.class, CraftComparator::new); -+ register(BlockEntityType.CONDUIT, CraftConduit.class, CraftConduit::new); -+ register(BlockEntityType.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new); -+ register(BlockEntityType.DISPENSER, CraftDispenser.class, CraftDispenser::new); -+ register(BlockEntityType.DROPPER, CraftDropper.class, CraftDropper::new); -+ register(BlockEntityType.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new); -+ register(BlockEntityType.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new); -+ register(BlockEntityType.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new); -+ register(BlockEntityType.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new); -+ register(BlockEntityType.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new); -+ register(BlockEntityType.HOPPER, CraftHopper.class, CraftHopper::new); -+ register(BlockEntityType.JIGSAW, CraftJigsaw.class, CraftJigsaw::new); -+ register(BlockEntityType.JUKEBOX, CraftJukebox.class, CraftJukebox::new); -+ register(BlockEntityType.LECTERN, CraftLectern.class, CraftLectern::new); -+ register(BlockEntityType.PISTON, CraftMovingPiston.class, CraftMovingPiston::new); -+ register(BlockEntityType.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new); -+ register(BlockEntityType.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new); -+ register(BlockEntityType.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new); -+ register(BlockEntityType.SMOKER, CraftSmoker.class, CraftSmoker::new); -+ register(BlockEntityType.MOB_SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new); -+ register(BlockEntityType.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new); -+ register(BlockEntityType.TRAPPED_CHEST, CraftChest.class, CraftChest::new); -+ // Paper end - } - - private static void register(Material blockType, BlockStateFactory factory) { -@@ -296,30 +167,33 @@ public final class CraftBlockStates { - } - - private static > void register( -- Material blockType, -- Class blockStateType, -- BiFunction blockStateConstructor, -- BiFunction tileEntityConstructor -- ) { -- CraftBlockStates.register(Collections.singletonList(blockType), blockStateType, blockStateConstructor, tileEntityConstructor); -- } -- -- private static > void register( -- List blockTypes, -+ net.minecraft.world.level.block.entity.BlockEntityType blockEntityType, // Paper - Class blockStateType, -- BiFunction blockStateConstructor, -- BiFunction tileEntityConstructor -+ BiFunction blockStateConstructor // Paper - ) { -- BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, tileEntityConstructor); -- for (Material blockType : blockTypes) { -- CraftBlockStates.register(blockType, factory); -+ // Paper start -+ BlockStateFactory factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, blockEntityType::create); -+ for (net.minecraft.world.level.block.Block block : blockEntityType.validBlocks) { -+ CraftBlockStates.register(CraftMagicNumbers.getMaterial(block), factory); - } -+ CraftBlockStates.register(blockEntityType, factory); -+ // Paper end - } - - private static BlockStateFactory getFactory(Material material) { - return CraftBlockStates.FACTORIES.getOrDefault(material, DEFAULT_FACTORY); - } - -+ // Paper start -+ private static BlockStateFactory getFactory(Material material, BlockEntityType type) { -+ if (type != null) { -+ return CraftBlockStates.FACTORIES_BY_BLOCK_ENTITY_TYPE.getOrDefault(type, getFactory(material)); -+ } else { -+ return getFactory(material); -+ } -+ } -+ // Paper end -+ - public static Class getBlockStateType(Material material) { - Preconditions.checkNotNull(material, "material is null"); - return CraftBlockStates.getFactory(material).blockStateType; -@@ -335,6 +209,13 @@ public final class CraftBlockStates { - return null; - } - -+ // Paper start -+ public static Class getBlockStateType(BlockEntityType blockEntityType) { -+ Preconditions.checkNotNull(blockEntityType, "blockEntityType is null"); -+ return CraftBlockStates.getFactory(null, blockEntityType).blockStateType; -+ } -+ // Paper end -+ - public static BlockState getBlockState(Block block) { - // Paper start - return CraftBlockStates.getBlockState(block, true); -@@ -392,7 +273,7 @@ public final class CraftBlockStates { - if (world != null && tileEntity == null && CraftBlockStates.isTileEntityOptional(material)) { - factory = CraftBlockStates.DEFAULT_FACTORY; - } else { -- factory = CraftBlockStates.getFactory(material); -+ factory = CraftBlockStates.getFactory(material, tileEntity != null ? tileEntity.getType() : null); // Paper - } - return factory.createBlockState(world, blockPosition, blockData, tileEntity); - } -diff --git a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -index 15f760c4c71a0493c7c18c3908d229f6b8778a43..95e4a4080a4a168b53f69d9cd7d67c27263c1b7f 100644 ---- a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -+++ b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java -@@ -45,4 +45,11 @@ public class BlockStateTest extends AbstractTestingBase { - } - } - } -+ -+ @Test -+ public void testBlockEntityTypes() { -+ for (var blockEntityType : Registry.BLOCK_ENTITY_TYPE) { -+ org.junit.Assert.assertNotNull(CraftBlockStates.getBlockStateType(blockEntityType)); -+ } -+ } - } diff --git a/patches/server/0781-Add-config-option-for-logging-player-ip-addresses.patch b/patches/server/0781-Add-config-option-for-logging-player-ip-addresses.patch deleted file mode 100644 index 4e93cf35ca..0000000000 --- a/patches/server/0781-Add-config-option-for-logging-player-ip-addresses.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Tue, 5 Oct 2021 20:04:21 +0200 -Subject: [PATCH] Add config option for logging player ip addresses - - -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index d6f3869f5725c7f081efb7f486f74dbb99d4d005..8bc0cb9ad5bb4e76d962ff54305e2c08e279a17b 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -51,10 +51,11 @@ public class PacketUtils { - packet.handle(listener); - } catch (Exception exception) { - net.minecraft.network.Connection networkmanager = listener.getConnection(); -+ String playerIP = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : ""; // Paper - if (networkmanager.getPlayer() != null) { -- LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception); -+ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), playerIP, exception); // Paper - } else { -- LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception); -+ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, playerIP, exception); // Paper - } - net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error"); - networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error))); -diff --git a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java -index 37cd7b44559e5705b31296df87c94d2ab200138d..37e52ea9ab694b466358981bc2a3c99f28a70c7d 100644 ---- a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java -+++ b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java -@@ -185,7 +185,7 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { - buf.release(); - this.buf = null; - -- LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); -+ LOGGER.debug("Ping: (1.6) from {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? ctx.channel().remoteAddress() : ""); // Paper - - InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); - com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest( -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index 2985271132c9ae822dcb0d7a7e6f0c268d1736cc..cfdbcd024de6ad0f9d4e83b2f912b36ef3299458 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -206,7 +206,7 @@ public class ServerConnectionListener { - throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection")); - } - -- ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", networkmanager.getRemoteAddress(), exception); -+ ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : "", exception); // Paper - MutableComponent ichatmutablecomponent = Component.literal("Internal server error"); - - networkmanager.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> { -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 77cb18da4f89bb89aea7d1ef5ebe3dd7acfe000d..acd581d14e0ef1fe5a6545ee67be00deff589879 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -236,7 +236,10 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - } - - public String getUserName() { -- return this.gameProfile != null ? this.gameProfile + " (" + this.connection.getRemoteAddress() + ")" : String.valueOf(this.connection.getRemoteAddress()); -+ // Paper start -+ String ip = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(this.connection.getRemoteAddress()) : ""; -+ return this.gameProfile != null ? this.gameProfile + " (" + ip + ")" : String.valueOf(ip); -+ // Paper end - } - - @Nullable -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 5ee0c3bb27ffbadc1e088983e643eed974753b65..fc14fc8017d89c27b0aeb10a5f38dafde5c15f53 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -243,7 +243,7 @@ public abstract class PlayerList { - final String s1; - - if (connection.getRemoteAddress() != null) { -- s1 = connection.getRemoteAddress().toString(); -+ s1 = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? connection.getRemoteAddress().toString() : ""; // Paper - } else { - s1 = "local"; - } diff --git a/patches/server/0781-Configurable-feature-seeds.patch b/patches/server/0781-Configurable-feature-seeds.patch new file mode 100644 index 0000000000..0b298aa5e7 --- /dev/null +++ b/patches/server/0781-Configurable-feature-seeds.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 31 Aug 2021 17:05:27 +0200 +Subject: [PATCH] Configurable feature seeds + +Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 46297ac0a19fd2398ab777a381eff4d0a256161e..78280fb3bcd8d792a58ece6d735e0824ea4be536 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -283,7 +283,7 @@ public class TimingsExport extends Thread { + 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")) { ++ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld") || key.equals("feature-seeds")) { + continue; + } + final Object val = config.get(key); +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 28f01f29796a8a8e6e6331da5525a4306d78230e..bdcfa5aac4cd0bd5841922295cc8fbb6ca69bd68 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -606,7 +606,14 @@ public abstract class ChunkGenerator { + return (String) optional.orElseGet(placedfeature::toString); + }; + +- seededrandom.setFeatureSeed(i, l1, l); ++ // Paper start - change populationSeed used in random ++ long featurePopulationSeed = i; ++ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature()); ++ if (configFeatureSeed != -1) { ++ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above ++ } ++ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l); ++ // Paper end + + try { + generatoraccessseed.setCurrentlyGenerating(supplier1); diff --git a/patches/server/0782-Configurable-feature-seeds.patch b/patches/server/0782-Configurable-feature-seeds.patch deleted file mode 100644 index 0b298aa5e7..0000000000 --- a/patches/server/0782-Configurable-feature-seeds.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 31 Aug 2021 17:05:27 +0200 -Subject: [PATCH] Configurable feature seeds - -Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> - -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index 46297ac0a19fd2398ab777a381eff4d0a256161e..78280fb3bcd8d792a58ece6d735e0824ea4be536 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -283,7 +283,7 @@ public class TimingsExport extends Thread { - 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")) { -+ if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld") || key.equals("feature-seeds")) { - continue; - } - final Object val = config.get(key); -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 28f01f29796a8a8e6e6331da5525a4306d78230e..bdcfa5aac4cd0bd5841922295cc8fbb6ca69bd68 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -606,7 +606,14 @@ public abstract class ChunkGenerator { - return (String) optional.orElseGet(placedfeature::toString); - }; - -- seededrandom.setFeatureSeed(i, l1, l); -+ // Paper start - change populationSeed used in random -+ long featurePopulationSeed = i; -+ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature()); -+ if (configFeatureSeed != -1) { -+ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above -+ } -+ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l); -+ // Paper end - - try { - generatoraccessseed.setCurrentlyGenerating(supplier1); diff --git a/patches/server/0782-VanillaCommandWrapper-didnt-account-for-entity-sende.patch b/patches/server/0782-VanillaCommandWrapper-didnt-account-for-entity-sende.patch new file mode 100644 index 0000000000..54bf3f7028 --- /dev/null +++ b/patches/server/0782-VanillaCommandWrapper-didnt-account-for-entity-sende.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 10 Sep 2021 15:49:12 -0700 +Subject: [PATCH] VanillaCommandWrapper didnt account for entity senders + + +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 8dca2ad7d25f740941187698d77819af8ebc2805..6df44aa60d2b41b95fe79ed4cf92a6d3369400ea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -64,8 +64,10 @@ public final class VanillaCommandWrapper extends BukkitCommand { + } + + public static CommandSourceStack getListener(CommandSender sender) { +- if (sender instanceof Player) { +- return ((CraftPlayer) sender).getHandle().createCommandSourceStack(); ++ // Paper start - account for other entity command senders ++ if (sender instanceof org.bukkit.craftbukkit.entity.CraftEntity craftEntity) { ++ return craftEntity.getHandle().createCommandSourceStack(); ++ // Paper end + } + if (sender instanceof BlockCommandSender) { + return ((CraftBlockCommandSender) sender).getWrapper(); diff --git a/patches/server/0783-Add-root-admin-user-detection.patch b/patches/server/0783-Add-root-admin-user-detection.patch new file mode 100644 index 0000000000..404725a045 --- /dev/null +++ b/patches/server/0783-Add-root-admin-user-detection.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: egg82 +Date: Sat, 11 Sep 2021 22:55:14 +0200 +Subject: [PATCH] Add root/admin user detection + +This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. +The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. +We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. +Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. + +Co-authored-by: Noah van der Aa + +diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java +@@ -0,0 +1,40 @@ ++package io.papermc.paper.util; ++ ++import com.sun.security.auth.module.NTSystem; ++import com.sun.security.auth.module.UnixSystem; ++import org.apache.commons.lang.SystemUtils; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.util.Set; ++ ++public class ServerEnvironment { ++ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; ++ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; ++ ++ static { ++ if (SystemUtils.IS_OS_WINDOWS) { ++ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); ++ } else { ++ boolean isRunningAsRoot = false; ++ if (new UnixSystem().getUid() == 0) { ++ // Due to an OpenJDK bug (https://bugs.openjdk.java.net/browse/JDK-8274721), UnixSystem#getUid incorrectly ++ // returns 0 when the user doesn't have a username. Because of this, we'll have to double-check if the user ID is ++ // actually 0 by running the id -u command. ++ try { ++ Process process = new ProcessBuilder("id", "-u").start(); ++ process.waitFor(); ++ InputStream inputStream = process.getInputStream(); ++ isRunningAsRoot = new String(inputStream.readAllBytes()).trim().equals("0"); ++ } catch (InterruptedException | IOException ignored) { ++ isRunningAsRoot = false; ++ } ++ } ++ RUNNING_AS_ROOT_OR_ADMIN = isRunningAsRoot; ++ } ++ } ++ ++ public static boolean userIsRootOrAdmin() { ++ return RUNNING_AS_ROOT_OR_ADMIN; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 2d01a1d4b2f7fdd38a6b1022f2476ba68b663171..20670bc075c387ee0422eb1014207e26105efccd 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -184,6 +184,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); + } + ++ // Paper start - detect running as root ++ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { ++ DedicatedServer.LOGGER.warn("****************************"); ++ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); ++ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); ++ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); ++ DedicatedServer.LOGGER.warn("****************************"); ++ } ++ // Paper end ++ + DedicatedServer.LOGGER.info("Loading properties"); + DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); + diff --git a/patches/server/0783-VanillaCommandWrapper-didnt-account-for-entity-sende.patch b/patches/server/0783-VanillaCommandWrapper-didnt-account-for-entity-sende.patch deleted file mode 100644 index 54bf3f7028..0000000000 --- a/patches/server/0783-VanillaCommandWrapper-didnt-account-for-entity-sende.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 10 Sep 2021 15:49:12 -0700 -Subject: [PATCH] VanillaCommandWrapper didnt account for entity senders - - -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index 8dca2ad7d25f740941187698d77819af8ebc2805..6df44aa60d2b41b95fe79ed4cf92a6d3369400ea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -64,8 +64,10 @@ public final class VanillaCommandWrapper extends BukkitCommand { - } - - public static CommandSourceStack getListener(CommandSender sender) { -- if (sender instanceof Player) { -- return ((CraftPlayer) sender).getHandle().createCommandSourceStack(); -+ // Paper start - account for other entity command senders -+ if (sender instanceof org.bukkit.craftbukkit.entity.CraftEntity craftEntity) { -+ return craftEntity.getHandle().createCommandSourceStack(); -+ // Paper end - } - if (sender instanceof BlockCommandSender) { - return ((CraftBlockCommandSender) sender).getWrapper(); diff --git a/patches/server/0784-Add-root-admin-user-detection.patch b/patches/server/0784-Add-root-admin-user-detection.patch deleted file mode 100644 index 404725a045..0000000000 --- a/patches/server/0784-Add-root-admin-user-detection.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: egg82 -Date: Sat, 11 Sep 2021 22:55:14 +0200 -Subject: [PATCH] Add root/admin user detection - -This patch detects whether or not the server is currently executing as a privileged user and spits out a warning. -The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root. -We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past. -Hopefully this helps mitigate some potential damage to servers, even if it is just a warning. - -Co-authored-by: Noah van der Aa - -diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6bd0afddbcc461149dfe9a5c7a86fff6ea13a5f1 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java -@@ -0,0 +1,40 @@ -+package io.papermc.paper.util; -+ -+import com.sun.security.auth.module.NTSystem; -+import com.sun.security.auth.module.UnixSystem; -+import org.apache.commons.lang.SystemUtils; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.util.Set; -+ -+public class ServerEnvironment { -+ private static final boolean RUNNING_AS_ROOT_OR_ADMIN; -+ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288"; -+ -+ static { -+ if (SystemUtils.IS_OS_WINDOWS) { -+ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL); -+ } else { -+ boolean isRunningAsRoot = false; -+ if (new UnixSystem().getUid() == 0) { -+ // Due to an OpenJDK bug (https://bugs.openjdk.java.net/browse/JDK-8274721), UnixSystem#getUid incorrectly -+ // returns 0 when the user doesn't have a username. Because of this, we'll have to double-check if the user ID is -+ // actually 0 by running the id -u command. -+ try { -+ Process process = new ProcessBuilder("id", "-u").start(); -+ process.waitFor(); -+ InputStream inputStream = process.getInputStream(); -+ isRunningAsRoot = new String(inputStream.readAllBytes()).trim().equals("0"); -+ } catch (InterruptedException | IOException ignored) { -+ isRunningAsRoot = false; -+ } -+ } -+ RUNNING_AS_ROOT_OR_ADMIN = isRunningAsRoot; -+ } -+ } -+ -+ public static boolean userIsRootOrAdmin() { -+ return RUNNING_AS_ROOT_OR_ADMIN; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 2d01a1d4b2f7fdd38a6b1022f2476ba68b663171..20670bc075c387ee0422eb1014207e26105efccd 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -184,6 +184,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); - } - -+ // Paper start - detect running as root -+ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) { -+ DedicatedServer.LOGGER.warn("****************************"); -+ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED."); -+ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS."); -+ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/"); -+ DedicatedServer.LOGGER.warn("****************************"); -+ } -+ // Paper end -+ - DedicatedServer.LOGGER.info("Loading properties"); - DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties(); - diff --git a/patches/server/0784-Always-allow-item-changing-in-Fireball.patch b/patches/server/0784-Always-allow-item-changing-in-Fireball.patch new file mode 100644 index 0000000000..fe4cbf2ef1 --- /dev/null +++ b/patches/server/0784-Always-allow-item-changing-in-Fireball.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 22:12:53 -0400 +Subject: [PATCH] Always allow item changing in Fireball + + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/Fireball.java b/src/main/java/net/minecraft/world/entity/projectile/Fireball.java +index 838ba52969550f783d26e626267c556ab09b5f3e..7f4e3dfab421591151fda7ec39d9c00b464d62de 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/Fireball.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/Fireball.java +@@ -28,7 +28,7 @@ public abstract class Fireball extends AbstractHurtingProjectile implements Item + } + + public void setItem(ItemStack stack) { +- if (!stack.is(Items.FIRE_CHARGE) || stack.hasTag()) { ++ if (true || !stack.is(Items.FIRE_CHARGE) || stack.hasTag()) { // Paper - always allow item changing + this.getEntityData().set(Fireball.DATA_ITEM_STACK, (ItemStack) Util.make(stack.copy(), (itemstack1) -> { + itemstack1.setCount(1); + })); diff --git a/patches/server/0785-Always-allow-item-changing-in-Fireball.patch b/patches/server/0785-Always-allow-item-changing-in-Fireball.patch deleted file mode 100644 index fe4cbf2ef1..0000000000 --- a/patches/server/0785-Always-allow-item-changing-in-Fireball.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 22:12:53 -0400 -Subject: [PATCH] Always allow item changing in Fireball - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Fireball.java b/src/main/java/net/minecraft/world/entity/projectile/Fireball.java -index 838ba52969550f783d26e626267c556ab09b5f3e..7f4e3dfab421591151fda7ec39d9c00b464d62de 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Fireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Fireball.java -@@ -28,7 +28,7 @@ public abstract class Fireball extends AbstractHurtingProjectile implements Item - } - - public void setItem(ItemStack stack) { -- if (!stack.is(Items.FIRE_CHARGE) || stack.hasTag()) { -+ if (true || !stack.is(Items.FIRE_CHARGE) || stack.hasTag()) { // Paper - always allow item changing - this.getEntityData().set(Fireball.DATA_ITEM_STACK, (ItemStack) Util.make(stack.copy(), (itemstack1) -> { - itemstack1.setCount(1); - })); diff --git a/patches/server/0785-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0785-don-t-attempt-to-teleport-dead-entities.patch new file mode 100644 index 0000000000..0d9f0564a4 --- /dev/null +++ b/patches/server/0785-don-t-attempt-to-teleport-dead-entities.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: sulu5890 +Date: Sun, 24 Oct 2021 22:48:14 -0500 +Subject: [PATCH] don't attempt to teleport dead entities + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index cef2cad3c278c4d2258f68dcbe6936c6637ca90a..036894c89e6dcc98b4e53c859d531163ed155a32 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -779,7 +779,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer)) { ++ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities + this.handleNetherPortal(); + } + } diff --git a/patches/server/0786-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0786-Prevent-excessive-velocity-through-repeated-crits.patch new file mode 100644 index 0000000000..51d947382e --- /dev/null +++ b/patches/server/0786-Prevent-excessive-velocity-through-repeated-crits.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 25 Nov 2021 10:25:09 +0100 +Subject: [PATCH] Prevent excessive velocity through repeated crits + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 76ef3f561e3f8e0c0f9732feb64aacca93b57431..8ac7d25e6e1a22829d6c45522409763bbb1328a0 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -2648,14 +2648,27 @@ public abstract class LivingEntity extends Entity { + return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; + } + ++ protected long lastJumpTime = 0L; // Paper + protected void jumpFromGround() { + double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); + Vec3 vec3d = this.getDeltaMovement(); ++ // Paper start ++ long time = System.nanoTime(); ++ boolean canCrit = true; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ canCrit = false; ++ if (time - this.lastJumpTime > (long)(0.250e9)) { ++ this.lastJumpTime = time; ++ canCrit = true; ++ } ++ } ++ // Paper end + + this.setDeltaMovement(vec3d.x, d0, vec3d.z); + if (this.isSprinting()) { + float f = this.getYRot() * 0.017453292F; + ++ if (canCrit) // Paper + this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); + } + diff --git a/patches/server/0786-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0786-don-t-attempt-to-teleport-dead-entities.patch deleted file mode 100644 index 2685387e30..0000000000 --- a/patches/server/0786-don-t-attempt-to-teleport-dead-entities.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: sulu5890 -Date: Sun, 24 Oct 2021 22:48:14 -0500 -Subject: [PATCH] don't attempt to teleport dead entities - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 71fd35d9d89d878109b0d4d0ae73a99063059eec..bf039f3e2d1a228f0a169b42031d9708f3193a92 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -779,7 +779,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - // CraftBukkit start - public void postTick() { - // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle -- if (!(this instanceof ServerPlayer)) { -+ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities - this.handleNetherPortal(); - } - } diff --git a/patches/server/0787-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0787-Prevent-excessive-velocity-through-repeated-crits.patch deleted file mode 100644 index 2b1bccc6b8..0000000000 --- a/patches/server/0787-Prevent-excessive-velocity-through-repeated-crits.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 25 Nov 2021 10:25:09 +0100 -Subject: [PATCH] Prevent excessive velocity through repeated crits - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index bc01742e3e762fbb5b7eb712a9211e4a8d411e03..4091253c84b5a722a75fa6dc8f5f0002b29ebf8e 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2626,14 +2626,27 @@ public abstract class LivingEntity extends Entity { - return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; - } - -+ protected long lastJumpTime = 0L; // Paper - protected void jumpFromGround() { - double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); - Vec3 vec3d = this.getDeltaMovement(); -+ // Paper start -+ long time = System.nanoTime(); -+ boolean canCrit = true; -+ if (this instanceof net.minecraft.world.entity.player.Player) { -+ canCrit = false; -+ if (time - this.lastJumpTime > (long)(0.250e9)) { -+ this.lastJumpTime = time; -+ canCrit = true; -+ } -+ } -+ // Paper end - - this.setDeltaMovement(vec3d.x, d0, vec3d.z); - if (this.isSprinting()) { - float f = this.getYRot() * 0.017453292F; - -+ if (canCrit) // Paper - this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); - } - diff --git a/patches/server/0787-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0787-Remove-client-side-code-using-deprecated-for-removal.patch new file mode 100644 index 0000000000..d96ebe539e --- /dev/null +++ b/patches/server/0787-Remove-client-side-code-using-deprecated-for-removal.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 26 Nov 2021 15:09:58 -0800 +Subject: [PATCH] Remove client-side code using deprecated for removal + AccessController + +Fixes warnings on build + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 23a999c19809a4fb62b37400e3767dc44692adb3..cdb7aea969b56f59d88f60bc3744e4932228c50a 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -889,17 +889,7 @@ public class Util { + } + + public void openUrl(URL url) { +- try { +- Process process = AccessController.doPrivileged((PrivilegedExceptionAction)(() -> { +- return Runtime.getRuntime().exec(this.getOpenUrlArguments(url)); +- })); +- process.getInputStream().close(); +- process.getErrorStream().close(); +- process.getOutputStream().close(); +- } catch (IOException | PrivilegedActionException var3) { +- Util.LOGGER.error("Couldn't open url '{}'", url, var3); +- } +- ++ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper + } + + public void openUri(URI uri) { diff --git a/patches/server/0788-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0788-Remove-client-side-code-using-deprecated-for-removal.patch deleted file mode 100644 index d96ebe539e..0000000000 --- a/patches/server/0788-Remove-client-side-code-using-deprecated-for-removal.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Fri, 26 Nov 2021 15:09:58 -0800 -Subject: [PATCH] Remove client-side code using deprecated for removal - AccessController - -Fixes warnings on build - -diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java -index 23a999c19809a4fb62b37400e3767dc44692adb3..cdb7aea969b56f59d88f60bc3744e4932228c50a 100644 ---- a/src/main/java/net/minecraft/Util.java -+++ b/src/main/java/net/minecraft/Util.java -@@ -889,17 +889,7 @@ public class Util { - } - - public void openUrl(URL url) { -- try { -- Process process = AccessController.doPrivileged((PrivilegedExceptionAction)(() -> { -- return Runtime.getRuntime().exec(this.getOpenUrlArguments(url)); -- })); -- process.getInputStream().close(); -- process.getErrorStream().close(); -- process.getOutputStream().close(); -- } catch (IOException | PrivilegedActionException var3) { -- Util.LOGGER.error("Couldn't open url '{}'", url, var3); -- } -- -+ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - } - - public void openUri(URI uri) { diff --git a/patches/server/0788-Rewrite-the-light-engine.patch b/patches/server/0788-Rewrite-the-light-engine.patch new file mode 100644 index 0000000000..1be70f40a4 --- /dev/null +++ b/patches/server/0788-Rewrite-the-light-engine.patch @@ -0,0 +1,5290 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 28 Oct 2020 16:51:55 -0700 +Subject: [PATCH] Rewrite the light engine + +The standard vanilla light engine is plagued by +awful performance. Paper's changes to the light engine +help a bit, however they appear to cause some lighting +errors - most easily noticed in coral generation. + +The vanilla light engine's is too abstract to be modified - +so an entirely new implementation is required to fix the +performance and lighting errors. + +The new implementation is designed primarily to optimise +light level propagations (increase and decrease). Unlike +the vanilla light engine, this implementation tracks more +information per queued value when performing a +breadth first search. Vanilla just tracks coordinate, which +means every time they handle a queued value, they must +also determine the coordinate's target light level +from its neighbours - very wasteful, especially considering +these checks read neighbour block data. +The new light engine tracks both position and target level, +as well as whether the target block needs to be read at all +(for checking sided propagation). So, the work done per coordinate +is significantly reduced because no work is done for calculating +the target level. +In my testing, the block get calls were reduced by approximately +an order of magnitude. However, the light read checks were only +reduced by approximately 2x - but this is fine, light read checks +are extremely cheap compared to block gets. + +Generation testing showed that the new light engine improved +total generation (not lighting itself, but the whole generation process) +by 2x. According to cpu time, the light engine itself spent 10x less time +lighting chunks for generation. + +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4a04eb6449d33d3f15c354b2ac98198f4ac12758 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java +@@ -0,0 +1,288 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.ImposterProtoChunk; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public final class BlockStarLightEngine extends StarLightEngine { ++ ++ public BlockStarLightEngine(final Level world) { ++ super(false, world); ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { ++ return chunk.getBlockEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { ++ chunk.setBlockEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { ++ return chunk.getBlockNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setBlockNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final ChunkAccess chunk) { ++ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); ++ } ++ ++ @Override ++ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically ++ // because a block was removed - which can decrease light. with sky data, block breaking can only result ++ // in increases, and thus the existing sky block check will actually correctly propagate light through ++ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove ++ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running ++ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence ++ // of vanilla data management we "hide" them. ++ nibble.setHidden(); ++ } ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); ++ } ++ } else { ++ nibble.setNonNull(); ++ } ++ } ++ ++ @Override ++ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change emitted light ++ // blocks can change direction of propagation ++ ++ final int encodeOffset = this.coordinateOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); ++ final int emittedLevel = blockState.getLightEmission() & emittedMask; ++ ++ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); ++ // this accounts for change in emitted light that would cause an increase ++ if (emittedLevel != 0) { ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ } ++ // this also accounts for a change in emitted light that would cause a decrease ++ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) ++ // as it checks all neighbours (even if current level is 0) ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ // always keep sided transparent false here, new block might be conditionally transparent which would ++ // prevent us from decreasing sources in the directions where the new block is opaque ++ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always ++ // catch that and fix it. ++ ); ++ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block ++ } ++ ++ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); ++ ++ @Override ++ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect) { ++ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); ++ int level = centerState.getLightEmission() & 0xF; ++ ++ if (level >= (15 - 1) || level > expect) { ++ return level; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final BlockState conditionallyOpaqueState; ++ int opacity = centerState.getOpacityIfCached(); ++ ++ if (opacity == -1) { ++ this.recalcCenterPos.set(worldX, worldY, worldZ); ++ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else if (opacity >= 15) { ++ return level; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ opacity = Math.max(1, opacity); ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.set(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ // passed transparency, ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { ++ for (final BlockPos pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected Iterator getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { ++ if (chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk) { ++ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is ++ // skipping empty sections, and the far more optimised reading of types. ++ List sources = new ArrayList<>(); ++ ++ int offX = chunk.getPos().x << 4; ++ int offZ = chunk.getPos().z << 4; ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { ++ final LevelChunkSection section = sections[sectionY - this.minSection]; ++ if (section == null || section.hasOnlyAir()) { ++ // no sources in empty sections ++ continue; ++ } ++ final PalettedContainer states = section.states; ++ final int offY = sectionY << 4; ++ ++ for (int index = 0; index < (16 * 16 * 16); ++index) { ++ final BlockState state = states.get(index); ++ if (state.getLightEmission() <= 0) { ++ continue; ++ } ++ ++ // index = x | (z << 4) | (y << 8) ++ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); ++ } ++ } ++ ++ return sources.iterator(); ++ } else { ++ // world gen and lighting run in parallel, and if lighting keeps up it can be lighting chunks that are ++ // being generated. In the nether, lava will add a lot of sources. This resulted in quite a few CME crashes. ++ // So all we do spinloop until we can collect a list of sources, and even if it is out of date we will pick up ++ // the missing sources from checkBlock. ++ for (;;) { ++ try { ++ return chunk.getLights().collect(Collectors.toList()).iterator(); ++ } catch (final Exception cme) { ++ continue; ++ } ++ } ++ } ++ } ++ ++ @Override ++ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { ++ // setup sources ++ final int emittedMask = this.emittedLightMask; ++ for (final Iterator positions = this.getSources(lightAccess, chunk); positions.hasNext();) { ++ final BlockPos pos = positions.next(); ++ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ ++ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { ++ // some other source is brighter ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (emittedLight & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) ++ ); ++ ++ ++ // propagation wont set this for us ++ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); ++ } ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ // verify neighbour edges ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ } else { ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4ffb4ffe01c4628d52742c5c0bbd35220eea6294 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java +@@ -0,0 +1,440 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import net.minecraft.world.level.chunk.DataLayer; ++import java.util.ArrayDeque; ++import java.util.Arrays; ++ ++// SWMR -> Single Writer Multi Reader Nibble Array ++public final class SWMRNibbleArray { ++ ++ /* ++ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null ++ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised ++ * nibbles can be written to. ++ * ++ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. ++ * ++ * Initialised nibble - Has light data. ++ */ ++ ++ protected static final int INIT_STATE_NULL = 0; // null ++ protected static final int INIT_STATE_UNINIT = 1; // uninitialised ++ protected static final int INIT_STATE_INIT = 2; // initialised ++ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL ++ ++ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block ++ // this allows us to maintain only 1 byte array when we're not updating ++ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); ++ ++ private static byte[] allocateBytes() { ++ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); ++ if (inPool != null) { ++ return inPool; ++ } ++ ++ return new byte[ARRAY_SIZE]; ++ } ++ ++ private static void freeBytes(final byte[] bytes) { ++ WORKING_BYTES_POOL.get().addFirst(bytes); ++ } ++ ++ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { ++ if (nibble == null) { ++ return new SWMRNibbleArray(null, true); ++ } else if (nibble.isEmpty()) { ++ return new SWMRNibbleArray(); ++ } else { ++ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later ++ } ++ } ++ ++ protected int stateUpdating; ++ protected volatile int stateVisible; ++ ++ protected byte[] storageUpdating; ++ protected boolean updatingDirty; // only returns whether storageUpdating is dirty ++ protected volatile byte[] storageVisible; ++ ++ public SWMRNibbleArray() { ++ this(null, false); // lazy init ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes) { ++ this(bytes, false); ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { ++ if (bytes != null && bytes.length != ARRAY_SIZE) { ++ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); ++ } ++ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; ++ this.storageUpdating = this.storageVisible = bytes; ++ } ++ ++ public SWMRNibbleArray(final byte[] bytes, final int state) { ++ if (bytes != null && bytes.length != ARRAY_SIZE) { ++ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); ++ } ++ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { ++ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); ++ } ++ this.stateUpdating = this.stateVisible = state; ++ this.storageUpdating = this.storageVisible = bytes; ++ } ++ ++ @Override ++ public String toString() { ++ StringBuilder stringBuilder = new StringBuilder(); ++ stringBuilder.append("State: "); ++ switch (this.stateVisible) { ++ case INIT_STATE_NULL: ++ stringBuilder.append("null"); ++ break; ++ case INIT_STATE_UNINIT: ++ stringBuilder.append("uninitialised"); ++ break; ++ case INIT_STATE_INIT: ++ stringBuilder.append("initialised"); ++ break; ++ case INIT_STATE_HIDDEN: ++ stringBuilder.append("hidden"); ++ break; ++ default: ++ stringBuilder.append("unknown"); ++ break; ++ } ++ stringBuilder.append("\nData:\n"); ++ ++ final byte[] data = this.storageVisible; ++ if (data != null) { ++ for (int i = 0; i < 4096; ++i) { ++ // Copied from NibbleArray#toString ++ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); ++ ++ stringBuilder.append(Integer.toHexString(level)); ++ if ((i & 15) == 15) { ++ stringBuilder.append("\n"); ++ } ++ ++ if ((i & 255) == 255) { ++ stringBuilder.append("\n"); ++ } ++ } ++ } else { ++ stringBuilder.append("null"); ++ } ++ ++ return stringBuilder.toString(); ++ } ++ ++ public SaveState getSaveState() { ++ synchronized (this) { ++ final int state = this.stateVisible; ++ final byte[] data = this.storageVisible; ++ if (state == INIT_STATE_NULL) { ++ return null; ++ } ++ if (state == INIT_STATE_UNINIT) { ++ return new SaveState(null, state); ++ } ++ final boolean zero = isAllZero(data); ++ if (zero) { ++ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; ++ } else { ++ return new SaveState(data.clone(), state); ++ } ++ } ++ } ++ ++ protected static boolean isAllZero(final byte[] data) { ++ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { ++ byte whole = data[i << 4]; ++ ++ for (int k = 1; k < (1 << 4); ++k) { ++ whole |= data[(i << 4) | k]; ++ } ++ ++ if (whole != 0) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ ++ // operation type: updating on src, updating on other ++ public void extrudeLower(final SWMRNibbleArray other) { ++ if (other.stateUpdating == INIT_STATE_NULL) { ++ throw new IllegalArgumentException(); ++ } ++ ++ if (other.storageUpdating == null) { ++ this.setUninitialised(); ++ return; ++ } ++ ++ final byte[] src = other.storageUpdating; ++ final byte[] into; ++ ++ if (!this.updatingDirty) { ++ if (this.storageUpdating != null) { ++ into = this.storageUpdating = allocateBytes(); ++ } else { ++ this.storageUpdating = into = allocateBytes(); ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ this.updatingDirty = true; ++ } else { ++ into = this.storageUpdating; ++ } ++ ++ final int start = 0; ++ final int end = (15 | (15 << 4)) >>> 1; ++ ++ /* x | (z << 4) | (y << 8) */ ++ for (int y = 0; y <= 15; ++y) { ++ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); ++ } ++ } ++ ++ // operation type: updating ++ public void setFull() { ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setZero() { ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public void setNonNull() { ++ if (this.stateUpdating == INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ return; ++ } ++ if (this.stateUpdating != INIT_STATE_NULL) { ++ return; ++ } ++ this.stateUpdating = INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public void setNull() { ++ this.stateUpdating = INIT_STATE_NULL; ++ if (this.updatingDirty && this.storageUpdating != null) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public void setUninitialised() { ++ this.stateUpdating = INIT_STATE_UNINIT; ++ if (this.storageUpdating != null && this.updatingDirty) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = null; ++ this.updatingDirty = false; ++ } ++ ++ // operation type: updating ++ public void setHidden() { ++ if (this.stateUpdating == INIT_STATE_HIDDEN) { ++ return; ++ } ++ if (this.stateUpdating != INIT_STATE_INIT) { ++ this.setNull(); ++ } else { ++ this.stateUpdating = INIT_STATE_HIDDEN; ++ } ++ } ++ ++ // operation type: updating ++ public boolean isDirty() { ++ return this.stateUpdating != this.stateVisible || this.updatingDirty; ++ } ++ ++ // operation type: updating ++ public boolean isNullNibbleUpdating() { ++ return this.stateUpdating == INIT_STATE_NULL; ++ } ++ ++ // operation type: visible ++ public boolean isNullNibbleVisible() { ++ return this.stateVisible == INIT_STATE_NULL; ++ } ++ ++ // opeartion type: updating ++ public boolean isUninitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: visible ++ public boolean isUninitialisedVisible() { ++ return this.stateVisible == INIT_STATE_UNINIT; ++ } ++ ++ // operation type: updating ++ public boolean isInitialisedUpdating() { ++ return this.stateUpdating == INIT_STATE_INIT; ++ } ++ ++ // operation type: visible ++ public boolean isInitialisedVisible() { ++ return this.stateVisible == INIT_STATE_INIT; ++ } ++ ++ // operation type: updating ++ public boolean isHiddenUpdating() { ++ return this.stateUpdating == INIT_STATE_HIDDEN; ++ } ++ ++ // operation type: updating ++ public boolean isHiddenVisible() { ++ return this.stateVisible == INIT_STATE_HIDDEN; ++ } ++ ++ // operation type: updating ++ protected void swapUpdatingAndMarkDirty() { ++ if (this.updatingDirty) { ++ return; ++ } ++ ++ if (this.storageUpdating == null) { ++ this.storageUpdating = allocateBytes(); ++ Arrays.fill(this.storageUpdating, (byte)0); ++ } else { ++ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); ++ } ++ ++ if (this.stateUpdating != INIT_STATE_HIDDEN) { ++ this.stateUpdating = INIT_STATE_INIT; ++ } ++ this.updatingDirty = true; ++ } ++ ++ // operation type: updating ++ public boolean updateVisible() { ++ if (!this.isDirty()) { ++ return false; ++ } ++ ++ synchronized (this) { ++ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { ++ this.storageVisible = null; ++ } else { ++ if (this.storageVisible == null) { ++ this.storageVisible = this.storageUpdating.clone(); ++ } else { ++ if (this.storageUpdating != this.storageVisible) { ++ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); ++ } ++ } ++ ++ if (this.storageUpdating != this.storageVisible) { ++ freeBytes(this.storageUpdating); ++ } ++ this.storageUpdating = this.storageVisible; ++ } ++ this.updatingDirty = false; ++ this.stateVisible = this.stateUpdating; ++ } ++ ++ return true; ++ } ++ ++ // operation type: visible ++ public DataLayer toVanillaNibble() { ++ synchronized (this) { ++ switch (this.stateVisible) { ++ case INIT_STATE_HIDDEN: ++ case INIT_STATE_NULL: ++ return null; ++ case INIT_STATE_UNINIT: ++ return new DataLayer(); ++ case INIT_STATE_INIT: ++ return new DataLayer(this.storageVisible.clone()); ++ default: ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ /* x | (z << 4) | (y << 8) */ ++ ++ // operation type: updating ++ public int getUpdating(final int x, final int y, final int z) { ++ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: updating ++ public int getUpdating(final int index) { ++ // indices range from 0 -> 4096 ++ final byte[] bytes = this.storageUpdating; ++ if (bytes == null) { ++ return 0; ++ } ++ final byte value = bytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int x, final int y, final int z) { ++ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); ++ } ++ ++ // operation type: visible ++ public int getVisible(final int index) { ++ // indices range from 0 -> 4096 ++ final byte[] visibleBytes = this.storageVisible; ++ if (visibleBytes == null) { ++ return 0; ++ } ++ final byte value = visibleBytes[index >>> 1]; ++ ++ // if we are an even index, we want lower 4 bits ++ // if we are an odd index, we want upper 4 bits ++ return ((value >>> ((index & 1) << 2)) & 0xF); ++ } ++ ++ // operation type: updating ++ public void set(final int x, final int y, final int z, final int value) { ++ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); ++ } ++ ++ // operation type: updating ++ public void set(final int index, final int value) { ++ if (!this.updatingDirty) { ++ this.swapUpdatingAndMarkDirty(); ++ } ++ final int shift = (index & 1) << 2; ++ final int i = index >>> 1; ++ ++ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); ++ } ++ ++ public static final class SaveState { ++ ++ public final byte[] data; ++ public final int state; ++ ++ public SaveState(final byte[] data, final int state) { ++ this.data = data; ++ this.state = state; ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f771962afb44175d446f138c8e7453230f48c6c +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java +@@ -0,0 +1,709 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.core.BlockPos; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.Arrays; ++import java.util.Set; ++ ++public final class SkyStarLightEngine extends StarLightEngine { ++ ++ /* ++ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: ++ ++ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. ++ ++ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. ++ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees ++ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise ++ our own) - we need a radius of 2 to de-initialise neighbour nibbles. ++ How do we solve this? ++ ++ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. ++ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the ++ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last ++ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data ++ to see if any of its nibbles need to be de-initialised. ++ ++ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, ++ and if it doesn't have data then we know it will correctly de-initialise once it fills up. ++ ++ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking ++ around those. ++ */ ++ ++ protected final int[] heightMapBlockChange = new int[16 * 16]; ++ { ++ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap ++ } ++ ++ protected final boolean[] nullPropagationCheckCache; ++ ++ public SkyStarLightEngine(final Level world) { ++ super(true, world); ++ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; ++ } ++ ++ @Override ++ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { ++ return; ++ } ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble == null) { ++ if (!initRemovedNibbles) { ++ throw new IllegalStateException(); ++ } else { ++ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); ++ } ++ } ++ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); ++ } ++ ++ @Override ++ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ nibble.setNull(); ++ } ++ } ++ ++ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { ++ if (!currNibble.isNullNibbleUpdating()) { ++ // already initialised ++ return; ++ } ++ ++ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = this.minLightSection - 1; ++ for (int currY = this.maxSection; currY >= this.minSection; --currY) { ++ if (emptinessMap == null) { ++ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. ++ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); ++ if (current == null || current.hasOnlyAir()) { ++ continue; ++ } ++ } else { ++ if (emptinessMap[currY - this.minSection]) { ++ continue; ++ } ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (chunkY > lowestY) { ++ // we need to set this one to full ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ nibble.setNonNull(); ++ nibble.setFull(); ++ return; ++ } ++ ++ if (extrude) { ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ currNibble.setNonNull(); ++ currNibble.extrudeLower(nibble); ++ break; ++ } ++ } ++ } else { ++ currNibble.setNonNull(); ++ } ++ } ++ ++ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (nibble != null && nibble.isNullNibbleUpdating()) { ++ // stop propagation in these areas ++ this.nibbleCache[index] = null; ++ nibble.updateVisible(); ++ } ++ } ++ } ++ ++ // rets whether neighbours were init'd ++ ++ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, ++ final boolean extrudeInitialised) { ++ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are ++ // non-null. Propagation to these neighbours is necessary. ++ // What makes this easy is we know none of these neighbours are non-empty (otherwise ++ // this nibble would be initialised). So, we don't have to initialise ++ // the neighbours in the full 1 radius, because there's no worry that any "paths" ++ // to the neighbours on this horizontal plane are blocked. ++ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { ++ return false; ++ } ++ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; ++ ++ // check horizontal neighbours ++ boolean needInitNeighbours = false; ++ neighbour_search: ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); ++ if (nibble != null && !nibble.isNullNibbleUpdating()) { ++ needInitNeighbours = true; ++ break neighbour_search; ++ } ++ } ++ } ++ ++ if (needInitNeighbours) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); ++ } ++ } ++ } ++ ++ return needInitNeighbours; ++ } ++ ++ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { ++ final int chunkX = worldX >> 4; ++ int chunkY = worldY >> 4; ++ final int chunkZ = worldZ >> 4; ++ ++ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, worldY, worldZ); ++ } ++ ++ for (;;) { ++ if (++chunkY > this.maxLightSection) { ++ return 15; ++ } ++ ++ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ ++ if (nibble != null) { ++ return nibble.getUpdating(worldX, 0, worldZ); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { ++ return chunk.getSkyEmptinessMap(); ++ } ++ ++ @Override ++ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { ++ chunk.setSkyEmptinessMap(to); ++ } ++ ++ @Override ++ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { ++ return chunk.getSkyNibbles(); ++ } ++ ++ @Override ++ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { ++ chunk.setSkyNibbles(to); ++ } ++ ++ @Override ++ protected boolean canUseChunk(final ChunkAccess chunk) { ++ // can only use chunks for sky stuff if their sections have been init'd ++ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, ++ final int toSection) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (int y = toSection; y >= fromSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); ++ } ++ ++ @Override ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ this.rewriteNibbleCacheForSkylight(chunk); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ final int y = (int)iterator.nextShort(); ++ this.checkNullSection(chunkX, y, chunkZ, true); ++ } ++ ++ super.checkChunkEdges(lightAccess, chunk, sections); ++ } ++ ++ @Override ++ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { ++ // blocks can change opacity ++ // blocks can change direction of propagation ++ ++ // same logic applies from BlockStarLightEngine#checkBlock ++ ++ final int encodeOffset = this.coordinateOffset; ++ ++ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); ++ ++ if (currentLevel == 15) { ++ // must re-propagate clobbered source ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent ++ ); ++ } else { ++ this.setLightLevel(worldX, worldY, worldZ, 0); ++ } ++ ++ this.appendToDecreaseQueue( ++ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (currentLevel & 0xFL) << (6 + 6 + 16) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ ); ++ } ++ ++ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); ++ ++ @Override ++ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect) { ++ if (expect == 15) { ++ return expect; ++ } ++ ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); ++ int opacity = centerState.getOpacityIfCached(); ++ ++ final BlockState conditionallyOpaqueState; ++ if (opacity < 0) { ++ this.recalcCenterPos.set(worldX, worldY, worldZ); ++ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); ++ if (centerState.isConditionallyFullOpaque()) { ++ conditionallyOpaqueState = centerState; ++ } else { ++ conditionallyOpaqueState = null; ++ } ++ } else { ++ conditionallyOpaqueState = null; ++ opacity = Math.max(1, opacity); ++ } ++ ++ int level = 0; ++ ++ for (final AxisDirection direction : AXIS_DIRECTIONS) { ++ final int offX = worldX + direction.x; ++ final int offY = worldY + direction.y; ++ final int offZ = worldZ + direction.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ ++ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); ++ ++ if ((neighbourLevel - 1) <= level) { ++ // don't need to test transparency, we know it wont affect the result. ++ continue; ++ } ++ ++ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); ++ ++ if (neighbourState.isConditionallyFullOpaque()) { ++ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that ++ // we don't read the blockstate because most of the time this is false, so using the faster ++ // known transparency lookup results in a net win ++ this.recalcNeighbourPos.set(offX, offY, offZ); ++ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); ++ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); ++ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { ++ // not allowed to propagate ++ continue; ++ } ++ } ++ ++ final int calculated = neighbourLevel - opacity; ++ level = Math.max(calculated, level); ++ if (level > expect) { ++ return level; ++ } ++ } ++ ++ return level; ++ } ++ ++ @Override ++ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { ++ this.rewriteNibbleCacheForSkylight(atChunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final BlockGetter world = lightAccess.getLevel(); ++ final int chunkX = atChunk.getPos().x; ++ final int chunkZ = atChunk.getPos().z; ++ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); ++ ++ // setup heightmap for changes ++ for (final BlockPos pos : positions) { ++ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; ++ final int curr = this.heightMapBlockChange[index]; ++ if (pos.getY() > curr) { ++ this.heightMapBlockChange[index] = pos.getY(); ++ } ++ } ++ ++ // note: light sets are delayed while processing skylight source changes due to how ++ // nibbles are initialised, as we want to avoid clobbering nibble values so what when ++ // below nibbles are initialised they aren't reading from partially modified nibbles ++ ++ // now we can recalculate the sources for the changed columns ++ for (int index = 0; index < (16 * 16); ++index) { ++ final int maxY = this.heightMapBlockChange[index]; ++ if (maxY == Integer.MIN_VALUE) { ++ // not changed ++ continue; ++ } ++ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller ++ ++ final int columnX = (index & 15) | (chunkX << 4); ++ final int columnZ = (index >>> 4) | (chunkZ << 4); ++ ++ // try and propagate from the above y ++ // delay light set until after processing all sources to setup ++ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); ++ ++ // maxPropagationY is now the highest block that could not be propagated to ++ ++ // remove all sources below that are 15 ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; ++ final int encodeOffset = this.coordinateOffset; ++ ++ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); ++ ++ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { ++ if ((currY & 15) == 15) { ++ // ensure section is checked ++ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); ++ } ++ ++ // ensure section below is always checked ++ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); ++ if (nibble == null) { ++ // advance currY to the the top of the section below ++ currY = (currY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ continue; ++ } ++ ++ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { ++ break; ++ } ++ ++ // delay light set until after processing all sources to setup ++ this.appendToDecreaseQueue( ++ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // do not set transparent blocks for the same reason we don't in the checkBlock method ++ ); ++ } ++ } ++ } ++ ++ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads ++ // immediate light value ++ this.processDelayedIncreases(); ++ this.processDelayedDecreases(); ++ ++ for (final BlockPos pos : positions) { ++ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ protected final int[] heightMapGen = new int[32 * 32]; ++ ++ @Override ++ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { ++ this.rewriteNibbleCacheForSkylight(chunk); ++ Arrays.fill(this.nullPropagationCheckCache, false); ++ ++ final BlockGetter world = lightAccess.getLevel(); ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ ++ int highestNonEmptySection = this.maxSection; ++ while (highestNonEmptySection == (this.minSection - 1) || ++ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) { ++ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); ++ // try propagate FULL to neighbours ++ ++ // check neighbours to see if we need to propagate into them ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourX = chunkX + direction.x; ++ final int neighbourZ = chunkZ + direction.z; ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); ++ if (neighbourNibble == null) { ++ // unloaded neighbour ++ // most of the time we fall here ++ continue; ++ } ++ ++ // it looks like we need to propagate into the neighbour ++ ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (direction.x != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (direction.z < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction ++ ++ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) ++ ); ++ } ++ } ++ } ++ ++ if (highestNonEmptySection-- == (this.minSection - 1)) { ++ break; ++ } ++ } ++ ++ if (highestNonEmptySection >= this.minSection) { ++ // fill out our other sources ++ final int minX = chunkPos.x << 4; ++ final int maxX = chunkPos.x << 4 | 15; ++ final int minZ = chunkPos.z << 4; ++ final int maxZ = chunkPos.z << 4 | 15; ++ final int startY = highestNonEmptySection << 4 | 15; ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); ++ } ++ } ++ } // else: apparently the chunk is empty ++ ++ if (needsEdgeChecks) { ++ // not required to propagate here, but this will reduce the hit of the edge checks ++ this.performLightIncrease(lightAccess); ++ ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ // no need to rewrite the nibble cache again ++ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ } else { ++ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { ++ this.checkNullSection(chunkX, y, chunkZ, false); ++ } ++ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); ++ ++ this.performLightIncrease(lightAccess); ++ } ++ } ++ ++ protected final void processDelayedIncreases() { ++ // copied from performLightIncrease ++ final long[] queue = this.increaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ ++ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); ++ } ++ } ++ ++ protected final void processDelayedDecreases() { ++ // copied from performLightDecrease ++ final long[] queue = this.decreaseQueue; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ ++ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { ++ final long queueValue = queue[i]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ ++ this.setLightLevel(posX, posY, posZ, 0); ++ } ++ } ++ ++ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays ++ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so ++ // clobbering the light values will result in broken propagation) ++ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, ++ final boolean extrudeInitialised, final boolean delayLightSet) { ++ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; ++ final int encodeOffset = this.coordinateOffset; ++ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. ++ ++ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { ++ return startY; ++ } ++ ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ ++ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); ++ ++ for (;startY >= (this.minLightSection << 4); --startY) { ++ if ((startY & 15) == 15) { ++ // ensure this section is always checked ++ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); ++ } ++ final BlockState current = this.getBlockState(worldX, startY, worldZ); ++ ++ final VoxelShape fromShape; ++ if (above.isConditionallyFullOpaque()) { ++ this.mutablePos2.set(worldX, startY + 1, worldZ); ++ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); ++ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ // above wont let us propagate ++ break; ++ } ++ } else { ++ fromShape = Shapes.empty(); ++ } ++ ++ final int opacityIfCached = current.getOpacityIfCached(); ++ // does light propagate from the top down? ++ if (opacityIfCached != -1) { ++ if (opacityIfCached != 0) { ++ // we cannot propagate 15 through this ++ break; ++ } ++ // most of the time it falls here. ++ // add to propagate ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ ); ++ } else { ++ mutablePos.set(worldX, startY, worldZ); ++ long flags = 0L; ++ if (current.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ // can't propagate here, we're done on this column. ++ break; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = current.getLightBlock(world, mutablePos); ++ if (opacity > 0) { ++ // let the queued value (if any) handle it from here. ++ break; ++ } ++ ++ // light set delayed until we determine if this nibble section is null ++ this.appendToIncreaseQueue( ++ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | (15L << (6 + 6 + 16)) // we know we're at full lit here ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | flags ++ ); ++ } ++ ++ above = current; ++ ++ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { ++ // we skip empty sections here, as this is just an easy way of making sure the above block ++ // can propagate through air. ++ ++ // nothing can propagate in null sections, remove the queue entry for it ++ --this.increaseQueueInitialLength; ++ ++ // advance currY to the the top of the section below ++ startY = (startY) & (~15); ++ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually ++ // end up there ++ ++ // make sure this is marked as AIR ++ above = AIR_BLOCK_STATE; ++ } else if (!delayLightSet) { ++ this.setLightLevel(worldX, startY, worldZ, 15); ++ } ++ } ++ ++ return startY; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1b0d92c68407cdb09ed8aac271b625d92db87017 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java +@@ -0,0 +1,1572 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.CoordinateUtils; ++import ca.spottedleaf.starlight.common.util.IntegerUtil; ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortIterator; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.core.SectionPos; ++import net.minecraft.world.level.BlockGetter; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelHeightAccessor; ++import net.minecraft.world.level.LightLayer; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.Set; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public abstract class StarLightEngine { ++ ++ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); ++ ++ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); ++ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; ++ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { ++ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, ++ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z ++ }; ++ ++ protected static enum AxisDirection { ++ ++ // Declaration order is important and relied upon. Do not change without modifying propagation code. ++ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), ++ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), ++ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); ++ ++ static { ++ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; ++ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; ++ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; ++ } ++ ++ protected AxisDirection opposite; ++ ++ public final int x; ++ public final int y; ++ public final int z; ++ public final Direction nms; ++ public final long everythingButThisDirection; ++ public final long everythingButTheOppositeDirection; ++ ++ AxisDirection(final int x, final int y, final int z) { ++ this.x = x; ++ this.y = y; ++ this.z = z; ++ this.nms = Direction.fromNormal(x, y, z); ++ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); ++ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. ++ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); ++ } ++ ++ public AxisDirection getOpposite() { ++ return this.opposite; ++ } ++ } ++ ++ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 ++ // for explaining how light propagates via breadth-first search ++ ++ // While the above is a good start to understanding the general idea of what the general principles are, it's not ++ // exactly how the vanilla light engine should behave for minecraft. ++ ++ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ // null index indicates the chunk section doesn't exist (empty or out of bounds) ++ protected final LevelChunkSection[] sectionCache; ++ ++ // the exact same as above, except for storing fast access to SWMRNibbleArray ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final SWMRNibbleArray[] nibbleCache; ++ ++ // the exact same as above, except for storing fast access to nibbles to call change callbacks for ++ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] ++ // index = x + (z * 5) + (y * 25) ++ protected final boolean[] notifyUpdateCache; ++ ++ // always initialsed during start of lighting. ++ // index = x + (z * 5) ++ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; ++ ++ // index = x + (z * 5) ++ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; ++ ++ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); ++ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); ++ ++ protected int encodeOffsetX; ++ protected int encodeOffsetY; ++ protected int encodeOffsetZ; ++ ++ protected int coordinateOffset; ++ ++ protected int chunkOffsetX; ++ protected int chunkOffsetY; ++ protected int chunkOffsetZ; ++ ++ protected int chunkIndexOffset; ++ protected int chunkSectionIndexOffset; ++ ++ protected final boolean skylightPropagator; ++ protected final int emittedLightMask; ++ protected final boolean isClientSide; ++ ++ protected final Level world; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ protected final int minSection; ++ protected final int maxSection; ++ ++ protected StarLightEngine(final boolean skylightPropagator, final Level world) { ++ this.skylightPropagator = skylightPropagator; ++ this.emittedLightMask = skylightPropagator ? 0 : 0xF; ++ this.isClientSide = world.isClientSide; ++ this.world = world; ++ this.minLightSection = WorldUtil.getMinLightSection(world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(world); ++ this.minSection = WorldUtil.getMinSection(world); ++ this.maxSection = WorldUtil.getMaxSection(world); ++ ++ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer ++ } ++ ++ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { ++ // 31 = center + encodeOffset ++ this.encodeOffsetX = 31 - centerX; ++ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value ++ this.encodeOffsetZ = 31 - centerZ; ++ ++ // coordinateIndex = x | (z << 6) | (y << 12) ++ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); ++ ++ // 2 = (centerX >> 4) + chunkOffset ++ this.chunkOffsetX = 2 - (centerX >> 4); ++ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 ++ this.chunkOffsetZ = 2 - (centerZ >> 4); ++ ++ // chunk index = x + (5 * z) ++ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); ++ ++ // chunk section index = x + (5 * z) + ((5*5) * y) ++ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); ++ } ++ ++ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, ++ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { ++ final int centerChunkX = centerX >> 4; ++ final int centerChunkY = centerY >> 4; ++ final int centerChunkZ = centerZ >> 4; ++ ++ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); ++ ++ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; ++ ++ for (int dz = -radius; dz <= radius; ++dz) { ++ for (int dx = -radius; dx <= radius; ++dx) { ++ final int cx = centerChunkX + dx; ++ final int cz = centerChunkZ + dz; ++ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; ++ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); ++ ++ if (chunk == null) { ++ if (relaxed | isTwoRadius) { ++ continue; ++ } ++ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); ++ } ++ ++ if (!this.canUseChunk(chunk)) { ++ continue; ++ } ++ ++ this.setChunkInCache(cx, cz, chunk); ++ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); ++ if (!isTwoRadius) { ++ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); ++ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); ++ } ++ } ++ } ++ } ++ ++ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { ++ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { ++ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; ++ } ++ ++ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { ++ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; ++ } ++ ++ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setChunkSectionInCache(chunkX, cy, chunkZ, ++ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null)); ++ } ++ } ++ ++ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { ++ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; ++ } ++ ++ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; ++ ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; ++ } ++ ++ return ret; ++ } ++ ++ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { ++ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; ++ } ++ ++ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { ++ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { ++ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); ++ } ++ } ++ ++ protected final void updateVisible(final LightChunkGetter lightAccess) { ++ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { ++ final SWMRNibbleArray nibble = this.nibbleCache[index]; ++ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { ++ continue; ++ } ++ ++ final int chunkX = (index % 5) - this.chunkOffsetX; ++ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; ++ final int ySections = (this.maxSection - this.minSection) + 1; ++ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY; ++ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { ++ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); ++ } ++ } ++ } ++ ++ protected final void destroyCaches() { ++ Arrays.fill(this.sectionCache, null); ++ Arrays.fill(this.nibbleCache, null); ++ Arrays.fill(this.chunkCache, null); ++ Arrays.fill(this.emptinessMapCache, null); ++ if (this.isClientSide) { ++ Arrays.fill(this.notifyUpdateCache, false); ++ } ++ } ++ ++ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { ++ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ if (section != null) { ++ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); ++ } ++ ++ return AIR_BLOCK_STATE; ++ } ++ ++ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { ++ final LevelChunkSection section = this.sectionCache[sectionIndex]; ++ ++ if (section != null) { ++ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex); ++ } ++ ++ return AIR_BLOCK_STATE; ++ } ++ ++ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { ++ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; ++ ++ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); ++ } ++ ++ protected final int getLightLevel(final int sectionIndex, final int localIndex) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ return nibble == null ? 0 : nibble.getUpdating(localIndex); ++ } ++ ++ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { ++ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { ++ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; ++ ++ if (nibble != null) { ++ nibble.set(localIndex, level); ++ if (this.isClientSide) { ++ int cx1 = (worldX - 1) >> 4; ++ int cx2 = (worldX + 1) >> 4; ++ int cy1 = (worldY - 1) >> 4; ++ int cy2 = (worldY + 1) >> 4; ++ int cz1 = (worldZ - 1) >> 4; ++ int cz2 = (worldZ + 1) >> 4; ++ for (int x = cx1; x <= cx2; ++x) { ++ for (int y = cy1; y <= cy2; ++y) { ++ for (int z = cz1; z <= cz2; ++z) { ++ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { ++ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; ++ } ++ ++ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { ++ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; ++ } ++ ++ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { ++ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); ++ } ++ ++ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { ++ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; ++ ++ for (int i = 0, len = ret.length; i < len; ++i) { ++ ret[i] = new SWMRNibbleArray(null, true); ++ } ++ ++ return ret; ++ } ++ ++ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); ++ ++ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); ++ ++ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); ++ ++ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); ++ ++ protected abstract boolean canUseChunk(final ChunkAccess chunk); ++ ++ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, ++ final Set positions, final Boolean[] changedSections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ if (changedSections != null) { ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ } ++ if (!positions.isEmpty()) { ++ this.propagateBlockChanges(lightAccess, chunk, positions); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); ++ ++ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); ++ ++ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) ++ // if ret == expect, then expect is the correct light value for pos ++ // if ret < expect, then ret is the real light value ++ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, ++ final int expect); ++ ++ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; ++ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; ++ ++ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, ++ final int chunkX, final int chunkY, final int chunkZ) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); ++ if (currNibble == null) { ++ return; ++ } ++ ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ chunkY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null) { ++ continue; ++ } ++ ++ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { ++ // both are zero, nothing to check. ++ continue; ++ } ++ ++ // this chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = chunkX << 4; ++ } else { ++ startX = chunkX << 4 | 15; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = chunkZ << 4; ++ } else { ++ startZ = chunkZ << 4 | 15; ++ } ++ startX = chunkX << 4; ++ } ++ ++ int centerDelayedChecks = 0; ++ int neighbourDelayedChecks = 0; ++ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int neighbourX = currX + neighbourOffX; ++ final int neighbourZ = currZ + neighbourOffZ; ++ ++ final int currentIndex = (currX & 15) | ++ ((currZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int currentLevel = currNibble.getUpdating(currentIndex); ++ ++ final int neighbourIndex = ++ (neighbourX & 15) | ++ ((neighbourZ & 15)) << 4 | ++ ((currY & 15) << 8); ++ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); ++ ++ // the checks are delayed because the checkBlock method clobbers light values - which then ++ // affect later calculate light value operations. While they don't affect it in a behaviourly significant ++ // way, they do have a negative performance impact due to simply queueing more values ++ ++ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { ++ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; ++ } ++ ++ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { ++ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; ++ } ++ } ++ } ++ ++ final int currentChunkOffX = chunkX << 4; ++ final int currentChunkOffZ = chunkZ << 4; ++ final int neighbourChunkOffX = (chunkX + direction.x) << 4; ++ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; ++ final int chunkOffY = chunkY << 4; ++ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { ++ // try to queue neighbouring data together ++ // index = x | (z << 4) | (y << 8) ++ if (i < centerDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesCenter[i]; ++ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ currentChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ if (i < neighbourDelayedChecks) { ++ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; ++ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), ++ chunkOffY | (value >>> 8), ++ neighbourChunkOffZ | ((value >>> 4) & 0xF)); ++ } ++ } ++ } ++ } ++ ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours ++ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). ++ // This does not resolve skylight source problems. ++ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); ++ } ++ ++ this.performLightDecrease(lightAccess); ++ } ++ ++ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. ++ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { ++ final ChunkPos chunkPos = chunk.getPos(); ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ ++ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { ++ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); ++ if (currNibble == null) { ++ continue; ++ } ++ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { ++ final int neighbourOffX = direction.x; ++ final int neighbourOffZ = direction.z; ++ ++ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, ++ currSectionY, chunkZ + neighbourOffZ); ++ ++ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { ++ // can't pull from 0 ++ continue; ++ } ++ ++ // neighbour chunk ++ final int incX; ++ final int incZ; ++ final int startX; ++ final int startZ; ++ ++ if (neighbourOffX != 0) { ++ // x direction ++ incX = 0; ++ incZ = 1; ++ ++ if (direction.x < 0) { ++ // negative ++ startX = (chunkX << 4) - 1; ++ } else { ++ startX = (chunkX << 4) + 16; ++ } ++ startZ = chunkZ << 4; ++ } else { ++ // z direction ++ incX = 1; ++ incZ = 0; ++ ++ if (neighbourOffZ < 0) { ++ // negative ++ startZ = (chunkZ << 4) - 1; ++ } else { ++ startZ = (chunkZ << 4) + 16; ++ } ++ startX = chunkX << 4; ++ } ++ ++ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk ++ final int encodeOffset = this.coordinateOffset; ++ ++ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { ++ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { ++ final int level = neighbourNibble.getUpdating( ++ (currX & 15) ++ | ((currZ & 15) << 4) ++ | ((currY & 15) << 8) ++ ); ++ ++ if (level <= 1) { ++ // nothing to propagate ++ continue; ++ } ++ ++ this.appendToIncreaseQueue( ++ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((level & 0xFL) << (6 + 6 + 16)) ++ | (propagateDirection << (6 + 6 + 16 + 4)) ++ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. ++ ); ++ } ++ } ++ } ++ } ++ } ++ ++ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { ++ final LevelChunkSection[] sections = chunk.getSections(); ++ final Boolean[] ret = new Boolean[sections.length]; ++ ++ for (int i = 0; i < sections.length; ++i) { ++ if (sections[i] == null || sections[i].hasOnlyAir()) { ++ ret[i] = Boolean.TRUE; ++ } else { ++ ret[i] = Boolean.FALSE; ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, ++ final Boolean[] emptinessChanges) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); ++ ++ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // subclasses are guaranteed that this is always called before a changed block set ++ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks ++ // rets non-null when the emptiness map changed and needs to be updated ++ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, ++ final Boolean[] emptinessChanges, final boolean unlit) { ++ final Level world = (Level)lightAccess.getLevel(); ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ ++ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); ++ boolean[] ret = null; ++ final boolean needsInit = unlit || chunkEmptinessMap == null; ++ if (needsInit) { ++ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); ++ } ++ ++ // update emptiness map ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ if (valueBoxed == null) { ++ if (!needsInit) { ++ continue; ++ } ++ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ); ++ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE; ++ } ++ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); ++ } ++ ++ // now init neighbour nibbles ++ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { ++ final Boolean valueBoxed = emptinessChanges[sectionIndex]; ++ final int sectionY = sectionIndex + this.minSection; ++ if (valueBoxed == null) { ++ continue; ++ } ++ ++ final boolean empty = valueBoxed.booleanValue(); ++ ++ if (empty) { ++ continue; ++ } ++ ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // if we're not empty, we also need to initialise nibbles ++ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ for (int dy = 1; dy >= -1; --dy) { ++ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ // check for de-init and lazy-init ++ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running ++ // init checks. ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ // does this neighbour have 1 radius loaded? ++ boolean neighboursLoaded = true; ++ neighbour_loaded_search: ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { ++ neighboursLoaded = false; ++ break neighbour_loaded_search; ++ } ++ } ++ } ++ ++ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { ++ // check neighbours to see if we need to de-init this one ++ boolean allEmpty = true; ++ neighbour_search: ++ for (int dy2 = -1; dy2 <= 1; ++dy2) { ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int y = sectionY + dy2; ++ if (y < this.minSection || y > this.maxSection) { ++ // empty ++ continue; ++ } ++ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); ++ if (emptinessMap != null) { ++ if (!emptinessMap[y - this.minSection]) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } else { ++ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); ++ if (section != null && !section.hasOnlyAir()) { ++ allEmpty = false; ++ break neighbour_search; ++ } ++ } ++ } ++ } ++ } ++ ++ if (allEmpty & neighboursLoaded) { ++ // can only de-init when neighbours are loaded ++ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting ++ // to be correct ++ ++ // all were empty, so de-init ++ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); ++ } else if (!allEmpty) { ++ // must init ++ final boolean extrude = (dx | dz) != 0 || !unlit; ++ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); ++ try { ++ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); ++ if (chunk == null) { ++ return; ++ } ++ this.checkChunkEdges(lightAccess, chunk, sections); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // subclasses should not initialise caches, as this will always be done by the super call ++ // subclasses should not invoke updateVisible, as this will always be done by the super call ++ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current ++ // chunks light values with respect to neighbours ++ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function ++ // does not need to detect empty chunks itself (and it should do no handling for them either!) ++ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); ++ ++ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { ++ final int chunkX = chunk.getPos().x; ++ final int chunkZ = chunk.getPos().z; ++ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); ++ ++ try { ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); ++ // force current chunk into cache ++ this.setChunkInCache(chunkX, chunkZ, chunk); ++ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); ++ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); ++ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); ++ ++ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); ++ if (ret != null) { ++ this.setEmptinessMap(chunk, ret); ++ } ++ this.lightChunk(lightAccess, chunk, true); ++ this.setNibbles(chunk, nibbles); ++ this.updateVisible(lightAccess); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, ++ final Consumer chunkLightCallback, final IntConsumer onComplete) { ++ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of ++ // the region of chunks to relight ++ // it's required that tickets are added for each chunk to keep them loaded ++ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); ++ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); ++ ++ final int[] neighbourLightOrder = new int[] { ++ // d = 0 ++ 0, 0, ++ // d = 1 ++ -1, 0, ++ 0, -1, ++ 1, 0, ++ 0, 1, ++ // d = 2 ++ -1, 1, ++ 1, 1, ++ -1, -1, ++ 1, -1, ++ }; ++ ++ int lightCalls = 0; ++ ++ for (final ChunkPos chunkPos : chunks) { ++ final int chunkX = chunkPos.x; ++ final int chunkZ = chunkPos.z; ++ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); ++ if (chunk == null || !this.canUseChunk(chunk)) { ++ throw new IllegalStateException(); ++ } ++ ++ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { ++ final int dx = neighbourLightOrder[i]; ++ final int dz = neighbourLightOrder[i + 1]; ++ final int neighbourX = dx + chunkX; ++ final int neighbourZ = dz + chunkZ; ++ ++ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); ++ if (neighbour == null || !this.canUseChunk(neighbour)) { ++ continue; ++ } ++ ++ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { ++ // lit already called for neighbour, no need to light it now ++ continue; ++ } ++ ++ // light neighbour chunk ++ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); ++ try { ++ // insert all neighbouring chunks for this neighbour that we have data for ++ for (int dz2 = -1; dz2 <= 1; ++dz2) { ++ for (int dx2 = -1; dx2 <= 1; ++dx2) { ++ final int neighbourX2 = neighbourX + dx2; ++ final int neighbourZ2 = neighbourZ + dz2; ++ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); ++ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); ++ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); ++ if (nibbles == null) { ++ // we haven't lit this chunk ++ continue; ++ } ++ ++ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); ++ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); ++ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); ++ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); ++ } ++ } ++ ++ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); ++ ++ // now insert the neighbour chunk and light it ++ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); ++ nibblesByChunk.put(key, nibbles); ++ ++ this.setChunkInCache(neighbourX, neighbourZ, neighbour); ++ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); ++ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); ++ ++ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); ++ emptinessMapByChunk.put(key, neighbourEmptiness); ++ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { ++ this.setEmptinessMap(neighbour, neighbourEmptiness); ++ } ++ ++ this.lightChunk(lightAccess, neighbour, false); ++ } finally { ++ this.destroyCaches(); ++ } ++ } ++ ++ // done lighting all neighbours, so the chunk is now fully lit ++ ++ // make sure nibbles are fully updated before calling back ++ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ for (final SWMRNibbleArray nibble : nibbles) { ++ nibble.updateVisible(); ++ } ++ ++ this.setNibbles(chunk, nibbles); ++ ++ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { ++ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); ++ } ++ ++ // now do callback ++ if (chunkLightCallback != null) { ++ chunkLightCallback.accept(chunkPos); ++ } ++ ++lightCalls; ++ } ++ ++ if (onComplete != null) { ++ onComplete.accept(lightCalls); ++ } ++ } ++ ++ // contains: ++ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) ++ // next 4 bits: propagated light level (0, 15] ++ // next 6 bits: propagation direction bitset ++ // next 24 bits: unused ++ // last 3 bits: state flags ++ // state flags: ++ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light ++ // updates for block sources ++ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 2; ++ // whether the propagation needs to check if its current level is equal to the expected level ++ // used only in increase propagation ++ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; ++ // whether the propagation needs to consider if its block is conditionally transparent ++ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; ++ ++ protected long[] increaseQueue = new long[16 * 16 * 16]; ++ protected int increaseQueueInitialLength; ++ protected long[] decreaseQueue = new long[16 * 16 * 16]; ++ protected int decreaseQueueInitialLength; ++ ++ protected final long[] resizeIncreaseQueue() { ++ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); ++ } ++ ++ protected final long[] resizeDecreaseQueue() { ++ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); ++ } ++ ++ protected final void appendToIncreaseQueue(final long value) { ++ final int idx = this.increaseQueueInitialLength++; ++ long[] queue = this.increaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected final void appendToDecreaseQueue(final long value) { ++ final int idx = this.decreaseQueueInitialLength++; ++ long[] queue = this.decreaseQueue; ++ if (idx >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ queue[idx] = value; ++ } else { ++ queue[idx] = value; ++ } ++ } ++ ++ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; ++ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; ++ static { ++ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { ++ final List directions = new ArrayList<>(); ++ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { ++ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); ++ } ++ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); ++ } ++ } ++ ++ protected final void performLightIncrease(final LightChunkGetter lightAccess) { ++ final BlockGetter world = lightAccess.getLevel(); ++ long[] queue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.increaseQueueInitialLength; ++ this.increaseQueueInitialLength = 0; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; ++ ++ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { ++ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { ++ // not at the level we expect, so something changed. ++ continue; ++ } ++ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { ++ // these are used to restore block sources after a propagation decrease ++ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); ++ } ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want or unloaded ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.set(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ ++ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ continue; ++ } ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int currentLevel; ++ ++ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { ++ continue; // already at the level we want ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); ++ if (targetLevel > currentLevel) { ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); ++ if (targetLevel <= currentLevel) { ++ continue; ++ } ++ ++ currentNibble.set(localIndex, targetLevel); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 1) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeIncreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) ++ | (flags); ++ } ++ continue; ++ } ++ } ++ } ++ } ++ } ++ ++ protected final void performLightDecrease(final LightChunkGetter lightAccess) { ++ final BlockGetter world = lightAccess.getLevel(); ++ long[] queue = this.decreaseQueue; ++ long[] increaseQueue = this.increaseQueue; ++ int queueReadIndex = 0; ++ int queueLength = this.decreaseQueueInitialLength; ++ this.decreaseQueueInitialLength = 0; ++ int increaseQueueLength = this.increaseQueueInitialLength; ++ final int decodeOffsetX = -this.encodeOffsetX; ++ final int decodeOffsetY = -this.encodeOffsetY; ++ final int decodeOffsetZ = -this.encodeOffsetZ; ++ final int encodeOffset = this.coordinateOffset; ++ final int sectionOffset = this.chunkSectionIndexOffset; ++ final int emittedMask = this.emittedLightMask; ++ ++ while (queueReadIndex < queueLength) { ++ final long queueValue = queue[queueReadIndex++]; ++ ++ final int posX = ((int)queueValue & 63) + decodeOffsetX; ++ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; ++ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; ++ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); ++ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; ++ ++ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { ++ // we don't need to worry about our state here. ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } else { ++ // we actually need to worry about our state here ++ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); ++ this.mutablePos2.set(posX, posY, posZ); ++ for (final AxisDirection propagate : checkDirections) { ++ final int offX = posX + propagate.x; ++ final int offY = posY + propagate.y; ++ final int offZ = posZ + propagate.z; ++ ++ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; ++ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); ++ ++ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); ++ ++ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { ++ continue; ++ } ++ ++ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; ++ final int lightLevel; ++ ++ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { ++ // already at lowest (or unloaded), nothing we can do ++ continue; ++ } ++ ++ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); ++ if (blockState == null) { ++ continue; ++ } ++ final int opacityCached = blockState.getOpacityIfCached(); ++ if (opacityCached != -1) { ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | FLAG_RECHECK_LEVEL; ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); ++ continue; ++ } ++ continue; ++ } else { ++ this.mutablePos1.set(offX, offY, offZ); ++ long flags = 0; ++ if (blockState.isConditionallyFullOpaque()) { ++ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); ++ ++ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { ++ continue; ++ } ++ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; ++ } ++ ++ final int opacity = blockState.getLightBlock(world, this.mutablePos1); ++ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); ++ if (lightLevel > targetLevel) { ++ // it looks like another source propagated here, so re-propagate it ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((lightLevel & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (FLAG_RECHECK_LEVEL | flags); ++ continue; ++ } ++ final int emittedLight = blockState.getLightEmission() & emittedMask; ++ if (emittedLight != 0) { ++ // re-propagate source ++ // note: do not set recheck level, or else the propagation will fail ++ if (increaseQueueLength >= increaseQueue.length) { ++ increaseQueue = this.resizeIncreaseQueue(); ++ } ++ increaseQueue[increaseQueueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((emittedLight & 0xFL) << (6 + 6 + 16)) ++ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) ++ | (flags | FLAG_WRITE_LEVEL); ++ } ++ ++ currentNibble.set(localIndex, 0); ++ this.postLightUpdate(offX, offY, offZ); ++ ++ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... ++ if (queueLength >= queue.length) { ++ queue = this.resizeDecreaseQueue(); ++ } ++ queue[queueLength++] = ++ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) ++ | ((targetLevel & 0xFL) << (6 + 6 + 16)) ++ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) ++ | flags; ++ } ++ continue; ++ } ++ } ++ } ++ } ++ ++ // propagate sources we clobbered ++ this.increaseQueueInitialLength = increaseQueueLength; ++ this.performLightIncrease(lightAccess); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ef8dcbb6bbc0769e9ccfdadb05e6a46c070eda98 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java +@@ -0,0 +1,665 @@ ++package ca.spottedleaf.starlight.common.light; ++ ++import ca.spottedleaf.starlight.common.util.CoordinateUtils; ++import ca.spottedleaf.starlight.common.util.WorldUtil; ++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.shorts.ShortCollection; ++import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.TicketType; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.DataLayer; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.chunk.LightChunkGetter; ++import net.minecraft.world.level.lighting.LayerLightEventListener; ++import net.minecraft.world.level.lighting.LevelLightEngine; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; ++ ++public final class StarLightInterface { ++ ++ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); ++ ++ /** ++ * Can be {@code null}, indicating the light is all empty. ++ */ ++ protected final Level world; ++ protected final LightChunkGetter lightAccess; ++ ++ protected final ArrayDeque cachedSkyPropagators; ++ protected final ArrayDeque cachedBlockPropagators; ++ ++ protected final LightQueue lightQueue = new LightQueue(this); ++ ++ protected final LayerLightEventListener skyReader; ++ protected final LayerLightEventListener blockReader; ++ protected final boolean isClientSide; ++ ++ protected final int minSection; ++ protected final int maxSection; ++ protected final int minLightSection; ++ protected final int maxLightSection; ++ ++ public final LevelLightEngine lightEngine; ++ ++ private final boolean hasBlockLight; ++ private final boolean hasSkyLight; ++ ++ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { ++ this.lightAccess = lightAccess; ++ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); ++ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; ++ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; ++ this.isClientSide = !(this.world instanceof ServerLevel); ++ if (this.world == null) { ++ this.minSection = -4; ++ this.maxSection = 19; ++ this.minLightSection = -5; ++ this.maxLightSection = 20; ++ } else { ++ this.minSection = WorldUtil.getMinSection(this.world); ++ this.maxSection = WorldUtil.getMaxSection(this.world); ++ this.minLightSection = WorldUtil.getMinLightSection(this.world); ++ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); ++ } ++ this.lightEngine = lightEngine; ++ this.hasBlockLight = hasBlockLight; ++ this.hasSkyLight = hasSkyLight; ++ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { ++ @Override ++ public void checkBlock(final BlockPos blockPos) { ++ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); ++ } ++ ++ @Override ++ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { ++ // skylight doesn't care ++ } ++ ++ @Override ++ public boolean hasLightWork() { ++ // not really correct... ++ return StarLightInterface.this.hasUpdates(); ++ } ++ ++ @Override ++ public int runUpdates(final int i, final boolean bl, final boolean bl2) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public DataLayer getDataLayerData(final SectionPos pos) { ++ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ return null; ++ } ++ ++ final int sectionY = pos.getY(); ++ ++ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { ++ return null; ++ } ++ ++ if (chunk.getSkyEmptinessMap() == null) { ++ return null; ++ } ++ ++ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); ++ } ++ ++ @Override ++ public int getLightValue(final BlockPos blockPos) { ++ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); ++ } ++ ++ @Override ++ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { ++ StarLightInterface.this.sectionChange(pos, notReady); ++ } ++ }; ++ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { ++ @Override ++ public void checkBlock(final BlockPos blockPos) { ++ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); ++ } ++ ++ @Override ++ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { ++ this.checkBlock(blockPos); ++ } ++ ++ @Override ++ public boolean hasLightWork() { ++ // not really correct... ++ return StarLightInterface.this.hasUpdates(); ++ } ++ ++ @Override ++ public int runUpdates(final int i, final boolean bl, final boolean bl2) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public DataLayer getDataLayerData(final SectionPos pos) { ++ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); ++ ++ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { ++ return null; ++ } ++ ++ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); ++ } ++ ++ @Override ++ public int getLightValue(final BlockPos blockPos) { ++ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); ++ } ++ ++ @Override ++ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { ++ StarLightInterface.this.sectionChange(pos, notReady); ++ } ++ }; ++ } ++ ++ protected int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ if (!this.hasSkyLight) { ++ return 0; ++ } ++ final int x = blockPos.getX(); ++ int y = blockPos.getY(); ++ final int z = blockPos.getZ(); ++ ++ final int minSection = this.minSection; ++ final int maxSection = this.maxSection; ++ final int minLightSection = this.minLightSection; ++ final int maxLightSection = this.maxLightSection; ++ ++ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ return 15; ++ } ++ ++ int sectionY = y >> 4; ++ ++ if (sectionY > maxLightSection) { ++ return 15; ++ } ++ ++ if (sectionY < minLightSection) { ++ sectionY = minLightSection; ++ y = sectionY << 4; ++ } ++ ++ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); ++ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection]; ++ ++ if (!immediate.isNullNibbleVisible()) { ++ return immediate.getVisible(x, y, z); ++ } ++ ++ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); ++ ++ if (emptinessMap == null) { ++ return 15; ++ } ++ ++ // are we above this chunk's lowest empty section? ++ int lowestY = minLightSection - 1; ++ for (int currY = maxSection; currY >= minSection; --currY) { ++ if (emptinessMap[currY - minSection]) { ++ continue; ++ } ++ ++ // should always be full lit here ++ lowestY = currY; ++ break; ++ } ++ ++ if (sectionY > lowestY) { ++ return 15; ++ } ++ ++ // this nibble is going to depend solely on the skylight data above it ++ // find first non-null data above (there does exist one, as we just found it above) ++ for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) { ++ final SWMRNibbleArray nibble = nibbles[currY - minLightSection]; ++ if (!nibble.isNullNibbleVisible()) { ++ return nibble.getVisible(x, 0, z); ++ } ++ } ++ ++ // should never reach here ++ return 15; ++ } ++ ++ protected int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { ++ if (!this.hasBlockLight) { ++ return 0; ++ } ++ final int y = blockPos.getY(); ++ final int cy = y >> 4; ++ ++ final int minLightSection = this.minLightSection; ++ final int maxLightSection = this.maxLightSection; ++ ++ if (cy < minLightSection || cy > maxLightSection) { ++ return 0; ++ } ++ ++ if (chunk == null) { ++ return 0; ++ } ++ ++ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection]; ++ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ()); ++ } ++ ++ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { ++ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4); ++ ++ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness; ++ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. ++ if (sky == 15) return 15; ++ final int block = this.getBlockLightValue(pos, chunk); ++ return Math.max(sky, block); ++ } ++ ++ public LayerLightEventListener getSkyReader() { ++ return this.skyReader; ++ } ++ ++ public LayerLightEventListener getBlockReader() { ++ return this.blockReader; ++ } ++ ++ public boolean isClientSide() { ++ return this.isClientSide; ++ } ++ ++ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { ++ if (this.world == null) { ++ // empty world ++ return null; ++ } ++ ++ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); ++ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ); ++ if (fullLoaded != null) { ++ return fullLoaded; ++ } ++ ++ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ public boolean hasUpdates() { ++ return !this.lightQueue.isEmpty(); ++ } ++ ++ public Level getWorld() { ++ return this.world; ++ } ++ ++ public LightChunkGetter getLightAccess() { ++ return this.lightAccess; ++ } ++ ++ protected final SkyStarLightEngine getSkyLightEngine() { ++ if (this.cachedSkyPropagators == null) { ++ return null; ++ } ++ final SkyStarLightEngine ret; ++ synchronized (this.cachedSkyPropagators) { ++ ret = this.cachedSkyPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new SkyStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { ++ if (this.cachedSkyPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedSkyPropagators) { ++ this.cachedSkyPropagators.addFirst(engine); ++ } ++ } ++ ++ protected final BlockStarLightEngine getBlockLightEngine() { ++ if (this.cachedBlockPropagators == null) { ++ return null; ++ } ++ final BlockStarLightEngine ret; ++ synchronized (this.cachedBlockPropagators) { ++ ret = this.cachedBlockPropagators.pollFirst(); ++ } ++ ++ if (ret == null) { ++ return new BlockStarLightEngine(this.world); ++ } ++ return ret; ++ } ++ ++ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { ++ if (this.cachedBlockPropagators == null) { ++ return; ++ } ++ synchronized (this.cachedBlockPropagators) { ++ this.cachedBlockPropagators.addFirst(engine); ++ } ++ } ++ ++ public CompletableFuture blockChange(final BlockPos pos) { ++ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world ++ return null; ++ } ++ ++ return this.lightQueue.queueBlockChange(pos); ++ } ++ ++ public CompletableFuture sectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ if (this.world == null) { // empty world ++ return null; ++ } ++ ++ return this.lightQueue.queueSectionChange(pos, newEmptyValue); ++ } ++ ++ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ if (blockEngine != null) { ++ blockEngine.light(this.lightAccess, chunk, emptySections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, ++ final IntConsumer onComplete) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, ++ blockEngine == null ? onComplete : null); ++ } ++ if (blockEngine != null) { ++ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkChunkEdges(final int chunkX, final int chunkZ) { ++ this.checkSkyEdges(chunkX, chunkZ); ++ this.checkBlockEdges(chunkX, chunkZ); ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ ++ try { ++ if (skyEngine != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ } ++ } ++ ++ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ try { ++ if (blockEngine != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); ++ } ++ } finally { ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { ++ this.lightQueue.queueChunkLighting(pos, run); ++ } ++ ++ public void removeChunkTasks(final ChunkPos pos) { ++ this.lightQueue.removeChunk(pos); ++ } ++ ++ public void propagateChanges() { ++ if (this.lightQueue.isEmpty()) { ++ return; ++ } ++ ++ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); ++ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); ++ ++ try { ++ LightQueue.ChunkTasks task; ++ while ((task = this.lightQueue.removeFirstTask()) != null) { ++ if (task.lightTasks != null) { ++ for (final Runnable run : task.lightTasks) { ++ run.run(); ++ } ++ } ++ ++ final long coordinate = task.chunkCoordinate; ++ final int chunkX = CoordinateUtils.getChunkX(coordinate); ++ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); ++ ++ final Set positions = task.changedPositions; ++ final Boolean[] sectionChanges = task.changedSectionSet; ++ ++ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { ++ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); ++ } ++ ++ if (skyEngine != null && task.queuedEdgeChecksSky != null) { ++ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); ++ } ++ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { ++ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); ++ } ++ ++ task.onComplete.complete(null); ++ } ++ } finally { ++ this.releaseSkyLightEngine(skyEngine); ++ this.releaseBlockLightEngine(blockEngine); ++ } ++ } ++ ++ protected static final class LightQueue { ++ ++ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); ++ protected final StarLightInterface manager; ++ ++ public LightQueue(final StarLightInterface manager) { ++ this.manager = manager; ++ } ++ ++ public synchronized boolean isEmpty() { ++ return this.chunkTasks.isEmpty(); ++ } ++ ++ public synchronized CompletableFuture queueBlockChange(final BlockPos pos) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ tasks.changedPositions.add(pos.immutable()); ++ return tasks.onComplete; ++ } ++ ++ public synchronized CompletableFuture queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ if (tasks.changedSectionSet == null) { ++ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; ++ } ++ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); ++ ++ return tasks.onComplete; ++ } ++ ++ public synchronized CompletableFuture queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ if (tasks.lightTasks == null) { ++ tasks.lightTasks = new ArrayList<>(); ++ } ++ tasks.lightTasks.add(lightTask); ++ ++ return tasks.onComplete; ++ } ++ ++ public synchronized CompletableFuture queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ ++ return tasks.onComplete; ++ } ++ ++ public synchronized CompletableFuture queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { ++ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); ++ ++ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; ++ if (queuedEdges == null) { ++ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); ++ } ++ queuedEdges.addAll(sections); ++ ++ return tasks.onComplete; ++ } ++ ++ public void removeChunk(final ChunkPos pos) { ++ final ChunkTasks tasks; ++ synchronized (this) { ++ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); ++ } ++ if (tasks != null) { ++ tasks.onComplete.complete(null); ++ } ++ } ++ ++ public synchronized ChunkTasks removeFirstTask() { ++ if (this.chunkTasks.isEmpty()) { ++ return null; ++ } ++ return this.chunkTasks.removeFirst(); ++ } ++ ++ protected static final class ChunkTasks { ++ ++ public final Set changedPositions = new HashSet<>(); ++ public Boolean[] changedSectionSet; ++ public ShortOpenHashSet queuedEdgeChecksSky; ++ public ShortOpenHashSet queuedEdgeChecksBlock; ++ public List lightTasks; ++ ++ public final CompletableFuture onComplete = new CompletableFuture<>(); ++ ++ public final long chunkCoordinate; ++ ++ public ChunkTasks(final long chunkCoordinate) { ++ this.chunkCoordinate = chunkCoordinate; ++ } ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..16a4a14e7ccf9e4d7fdf1166674fe8f529c06d39 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java +@@ -0,0 +1,128 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.level.ChunkPos; ++ ++public final class CoordinateUtils { ++ ++ // dx, dz are relative to the target chunk ++ // dx, dz in [-radius, radius] ++ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { ++ return (dx + radius) + (2 * radius + 1)*(dz + radius); ++ } ++ ++ // the chunk keys are compatible with vanilla ++ ++ public static long getChunkKey(final BlockPos pos) { ++ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final Entity entity) { ++ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final ChunkPos pos) { ++ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final SectionPos pos) { ++ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); ++ } ++ ++ public static long getChunkKey(final int x, final int z) { ++ return ((long)z << 32) | (x & 0xFFFFFFFFL); ++ } ++ ++ public static int getChunkX(final long chunkKey) { ++ return (int)chunkKey; ++ } ++ ++ public static int getChunkZ(final long chunkKey) { ++ return (int)(chunkKey >>> 32); ++ } ++ ++ public static int getChunkCoordinate(final double blockCoordinate) { ++ return Mth.floor(blockCoordinate) >> 4; ++ } ++ ++ // the section keys are compatible with vanilla's ++ ++ static final int SECTION_X_BITS = 22; ++ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; ++ static final int SECTION_Y_BITS = 20; ++ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; ++ static final int SECTION_Z_BITS = 22; ++ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; ++ // format is y,z,x (in order of LSB to MSB) ++ static final int SECTION_Y_SHIFT = 0; ++ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; ++ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; ++ static final int SECTION_TO_BLOCK_SHIFT = 4; ++ ++ public static long getChunkSectionKey(final int x, final int y, final int z) { ++ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final SectionPos pos) { ++ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final ChunkPos pos, final int y) { ++ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) ++ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) ++ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); ++ } ++ ++ public static long getChunkSectionKey(final BlockPos pos) { ++ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static long getChunkSectionKey(final Entity entity) { ++ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | ++ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | ++ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); ++ } ++ ++ public static int getChunkSectionX(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); ++ } ++ ++ public static int getChunkSectionY(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); ++ } ++ ++ public static int getChunkSectionZ(final long key) { ++ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); ++ } ++ ++ // the block coordinates are not necessarily compatible with vanilla's ++ ++ public static int getBlockCoordinate(final double blockCoordinate) { ++ return Mth.floor(blockCoordinate); ++ } ++ ++ public static long getBlockKey(final int x, final int y, final int z) { ++ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); ++ } ++ ++ public static long getBlockKey(final BlockPos pos) { ++ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); ++ } ++ ++ public static long getBlockKey(final Entity entity) { ++ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); ++ } ++ ++ private CoordinateUtils() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..177d0a969f3d72a34e773e8309c3719a235ee06d +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java +@@ -0,0 +1,226 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++public final class IntegerUtil { ++ ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static int trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ // copied from hacker's delight (signed division magic value) ++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt ++ public static long getDivisorNumbers(final int d) { ++ final int ad = 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/ca/spottedleaf/starlight/common/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6df9e01731d7fcbe279736b8fc18396595b95574 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java +@@ -0,0 +1,192 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; ++import ca.spottedleaf.starlight.common.light.StarLightEngine; ++import com.mojang.logging.LogUtils; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.Level; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import org.slf4j.Logger; ++ ++public final class SaveUtil { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private static final int STARLIGHT_LIGHT_VERSION = 8; ++ ++ public static int getLightVersion() { ++ return STARLIGHT_LIGHT_VERSION; ++ } ++ ++ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; ++ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; ++ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ ++ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { ++ try { ++ saveLightHookReal(world, chunk, nbt); ++ } catch (final Throwable ex) { ++ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false ++ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex); ++ } ++ } ++ ++ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) { ++ if (tag == null) { ++ return; ++ } ++ ++ final int minSection = WorldUtil.getMinLightSection(world); ++ final int maxSection = WorldUtil.getMaxLightSection(world); ++ ++ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); ++ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); ++ ++ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel); ++ // diff start - store our tag for whether light data is init'd ++ if (lit) { ++ tag.putBoolean("isLightOn", false); ++ } ++ // diff end - store our tag for whether light data is init'd ++ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); ++ ++ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; ++ ++ ListTag sectionsStored = tag.getList("sections", 10); ++ ++ for (int i = 0; i < sectionsStored.size(); ++i) { ++ CompoundTag sectionStored = sectionsStored.getCompound(i); ++ int k = sectionStored.getByte("Y"); ++ ++ // strip light data ++ sectionStored.remove("BlockLight"); ++ sectionStored.remove("SkyLight"); ++ ++ if (!sectionStored.isEmpty()) { ++ sections[k - minSection] = sectionStored; ++ } ++ } ++ ++ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { ++ for (int i = minSection; i <= maxSection; ++i) { ++ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); ++ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); ++ if (blockNibble != null || skyNibble != null) { ++ CompoundTag section = sections[i - minSection]; ++ if (section == null) { ++ section = new CompoundTag(); ++ section.putByte("Y", (byte)i); ++ sections[i - minSection] = section; ++ } ++ ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ ++ if (blockNibble != null) { ++ if (blockNibble.data != null) { ++ section.putByteArray("BlockLight", blockNibble.data); ++ } ++ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); ++ } ++ ++ if (skyNibble != null) { ++ if (skyNibble.data != null) { ++ section.putByteArray("SkyLight", skyNibble.data); ++ } ++ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); ++ } ++ } ++ } ++ } ++ ++ // rewrite section list ++ sectionsStored.clear(); ++ for (CompoundTag section : sections) { ++ if (section != null) { ++ sectionsStored.add(section); ++ } ++ } ++ tag.put("sections", sectionsStored); ++ if (lit) { ++ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data ++ } ++ } ++ ++ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { ++ try { ++ loadLightHookReal(world, pos, tag, into); ++ } catch (final Throwable ex) { ++ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct ++ // lighting in both cases. ++ if (ex instanceof ThreadDeath) { ++ throw (ThreadDeath)ex; ++ } ++ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex); ++ } ++ } ++ ++ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { ++ if (into == null) { ++ return; ++ } ++ final int minSection = WorldUtil.getMinLightSection(world); ++ final int maxSection = WorldUtil.getMaxLightSection(world); ++ ++ into.setLightCorrect(false); // mark as unlit in case we fail parsing ++ ++ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world); ++ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world); ++ ++ ++ // start copy from the original method ++ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; ++ boolean canReadSky = world.dimensionType().hasSkyLight(); ++ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); ++ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here ++ ListTag sections = tag.getList("sections", 10); ++ ++ for (int i = 0; i < sections.size(); ++i) { ++ CompoundTag sectionData = sections.getCompound(i); ++ int y = sectionData.getByte("Y"); ++ ++ if (sectionData.contains("BlockLight", 7)) { ++ // this is where our diff is ++ blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); ++ } ++ ++ if (canReadSky) { ++ if (sectionData.contains("SkyLight", 7)) { ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); ++ } ++ } ++ } ++ } ++ // end copy from vanilla ++ ++ into.setBlockNibbles(blockNibbles); ++ into.setSkyNibbles(skyNibbles); ++ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data ++ } ++ ++ private SaveUtil() {} ++} +diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad034ba50d2 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java +@@ -0,0 +1,47 @@ ++package ca.spottedleaf.starlight.common.util; ++ ++import net.minecraft.world.level.LevelHeightAccessor; ++ ++public final class WorldUtil { ++ ++ // min, max are inclusive ++ ++ public static int getMaxSection(final LevelHeightAccessor world) { ++ return world.getMaxSection() - 1; // getMaxSection() is exclusive ++ } ++ ++ public static int getMinSection(final LevelHeightAccessor world) { ++ return world.getMinSection(); ++ } ++ ++ public static int getMaxLightSection(final LevelHeightAccessor world) { ++ return getMaxSection(world) + 1; ++ } ++ ++ public static int getMinLightSection(final LevelHeightAccessor world) { ++ return getMinSection(world) - 1; ++ } ++ ++ ++ ++ public static int getTotalSections(final LevelHeightAccessor world) { ++ return getMaxSection(world) - getMinSection(world) + 1; ++ } ++ ++ public static int getTotalLightSections(final LevelHeightAccessor world) { ++ return getMaxLightSection(world) - getMinLightSection(world) + 1; ++ } ++ ++ public static int getMinBlockY(final LevelHeightAccessor world) { ++ return getMinSection(world) << 4; ++ } ++ ++ public static int getMaxBlockY(final LevelHeightAccessor world) { ++ return (getMaxSection(world) << 4) | 15; ++ } ++ ++ private WorldUtil() { ++ throw new RuntimeException(); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +index 190df802cb24aa360f6cf4d291e38b4b3fe4a2ac..68645bbbab9b4225048b647252d8f462028a9c84 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.level.ThreadedLevelLightEngine; + import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.ChunkAccess; + import net.minecraft.world.level.chunk.LevelChunk; + import org.bukkit.command.CommandSender; + import org.bukkit.craftbukkit.entity.CraftPlayer; +@@ -19,6 +20,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; + import org.checkerframework.framework.qual.DefaultQualifier; + + import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; + import static net.kyori.adventure.text.format.NamedTextColor.GREEN; + import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@@ -44,7 +47,7 @@ public final class FixLightCommand implements PaperSubcommand { + sender.sendMessage(text("Radius cannot be negative!", RED)); + return; + } +- final int maxRadius = 5; ++ final int maxRadius = 32; // Paper - MOOOOOORE + radius = Math.min(maxRadius, parsed); + if (radius != parsed) { + post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); +@@ -59,12 +62,67 @@ public final class FixLightCommand implements PaperSubcommand { + ServerPlayer handle = player.getHandle(); + ServerLevel world = (ServerLevel) handle.level; + ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); ++ // Paper start - rewrite light engine ++ if (true) { ++ this.starlightFixLight(handle, world, lightengine, radius, post); ++ return; ++ } ++ // Paper end - rewrite light engine + + net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); + Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + updateLight(sender, world, lightengine, queue, post); + } + ++ // Paper start - rewrite light engine ++ private void starlightFixLight( ++ final ServerPlayer sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final int radius, ++ final @Nullable Runnable done ++ ) { ++ long start = System.nanoTime(); ++ java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos ++ ++ int[] pending = new int[1]; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { ++ final ChunkPos chunkPos = iterator.next(); ++ ++ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ ++pending[0]; ++ } ++ ++ int[] relitChunks = new int[1]; ++ lightengine.relight(chunks, ++ (ChunkPos chunkPos) -> { ++ ++relitChunks[0]; ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit chunk ", BLUE), text(chunkPos.toString()), ++ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") ++ )); ++ }, ++ (int totalRelit) -> { ++ final long end = System.nanoTime(); ++ final long diff = Math.round(1.0e-6 * (end - start)); ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit ", BLUE), text(totalRelit), ++ text(" chunks. Took ", BLUE), text(diff + "ms") ++ )); ++ if (done != null) { ++ done.run(); ++ } ++ }); ++ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); ++ } ++ // Paper end - rewrite light engine ++ + private void updateLight( + final CommandSender sender, + final ServerLevel world, +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 09f262de1b12b09013f8277b25d13ffcf53b96d8..73712d6b9c828427d4c066c6d8672534575f3793 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -55,7 +55,7 @@ public class ChunkHolder { + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage +- private CompletableFuture chunkToSave; ++ public CompletableFuture chunkToSave; // Paper - public + @Nullable + private final DebugBuffer chunkToSaveHistory; + public int oldTicketLevel; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 69b8f5dcae4ea75ea9d63c36b3f5b4383fe232f9..fe10c770b511fa8a38ece2bf9679492a85b28eff 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -133,7 +133,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final LongSet entitiesInLevel; + public final ServerLevel level; + private final ThreadedLevelLightEngine lightEngine; +- private final BlockableEventLoop mainThreadExecutor; ++ public final BlockableEventLoop mainThreadExecutor; // Paper - public + final java.util.concurrent.Executor mainInvokingExecutor; // Paper + public ChunkGenerator generator; + private RandomState randomState; +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 537d34a0325a985948c744929b90144a66a35ee3..06e4d3a02e0d1326b7029157856476db4ef3575e 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -545,7 +545,7 @@ public abstract class DistanceManager { + } + + public void removeTicketsOnClosing() { +- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve ++ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); + + while (objectiterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +index 5539f2a7e069cbe98997b734f3b1cd498148f09b..b57bffce30154b196b879209c1ce559d0b82456e 100644 +--- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java ++++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +@@ -23,6 +23,17 @@ import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.level.lighting.LevelLightEngine; + import org.slf4j.Logger; + ++// Paper start ++import ca.spottedleaf.starlight.common.light.StarLightEngine; ++import io.papermc.paper.util.CoordinateUtils; ++import java.util.function.Supplier; ++import net.minecraft.world.level.lighting.LayerLightEventListener; ++import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; ++import it.unimi.dsi.fastutil.longs.LongArrayList; ++import it.unimi.dsi.fastutil.longs.LongIterator; ++import net.minecraft.world.level.chunk.ChunkStatus; ++// Paper end ++ + public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { + private static final Logger LOGGER = LogUtils.getLogger(); + private final ProcessorMailbox taskMailbox; +@@ -157,13 +168,168 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + private volatile int taskPerBatch = 5; + private final AtomicBoolean scheduled = new AtomicBoolean(); + ++ // Paper start - replace light engine impl ++ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; ++ public final boolean hasBlockLight; ++ public final boolean hasSkyLight; ++ // Paper end - replace light engine impl ++ + public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { +- super(chunkProvider, true, hasBlockLight); ++ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state + this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper + this.sorterMailbox = executor; + this.taskMailbox = processor; ++ // Paper start - replace light engine impl ++ this.hasBlockLight = true; ++ this.hasSkyLight = hasBlockLight; // Nice variable name. ++ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); ++ // Paper end - replace light engine impl ++ } ++ ++// Paper start - replace light engine impl ++ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { ++ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); ++ } ++ ++ protected long relightCounter; ++ ++ public int relight(java.util.Set chunks_param, ++ java.util.function.Consumer chunkLightCallback, ++ java.util.function.IntConsumer onComplete) { ++ if (!org.bukkit.Bukkit.isPrimaryThread()) { ++ throw new IllegalStateException("Must only be called on the main thread"); ++ } ++ ++ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); ++ // add tickets ++ java.util.Map ticketIds = new java.util.HashMap<>(); ++ int totalChunks = 0; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final ChunkPos chunkPos = iterator.next(); ++ ++ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ final Long id = Long.valueOf(this.relightCounter++); ++ ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); ++ ticketIds.put(chunkPos, id); ++ ++ ++totalChunks; ++ } ++ ++ this.taskMailbox.tell(() -> { ++ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { ++ chunkLightCallback.accept(chunkPos); ++ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null, true), false); ++ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); ++ }); ++ }, onComplete); ++ }); ++ this.tryScheduleUpdate(); ++ ++ return totalChunks; ++ } ++ ++ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); ++ ++ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier> runnable) { ++ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); ++ ++ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); ++ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { ++ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing ++ // chunk scheduling, we could be lighting and generating a chunk at the same time ++ return; ++ } ++ ++ if (center.getStatus() != ChunkStatus.FULL) { ++ // do not keep chunk loaded, we are probably in a gen thread ++ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) ++ runnable.get(); ++ return; ++ } ++ ++ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { ++ // ticket logic is not safe to run off-main, re-schedule ++ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { ++ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); ++ }); ++ return; ++ } ++ ++ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ ++ final CompletableFuture updateFuture = runnable.get(); ++ ++ if (updateFuture == null) { ++ // not scheduled ++ return; ++ } ++ ++ final int references = this.chunksBeingWorkedOn.addTo(key, 1); ++ if (references == 0) { ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); ++ } ++ ++ // append future to this chunk and 1 radius neighbours chunk save futures ++ // this prevents us from saving the world without first waiting for the light engine ++ ++ for (int dx = -1; dx <= 1; ++dx) { ++ for (int dz = -1; dz <= 1; ++dz) { ++ ChunkHolder neighbour = world.getChunkSource().chunkMap.getUpdatingChunkIfPresent(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ)); ++ if (neighbour != null) { ++ neighbour.chunkToSave = neighbour.chunkToSave.thenCombine(updateFuture, (final ChunkAccess curr, final Void ignore) -> { ++ return curr; ++ }); ++ } ++ } ++ } ++ ++ updateFuture.thenAcceptAsync((final Void ignore) -> { ++ final int newReferences = this.chunksBeingWorkedOn.get(key); ++ if (newReferences == 1) { ++ this.chunksBeingWorkedOn.remove(key); ++ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); ++ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); ++ } else { ++ this.chunksBeingWorkedOn.put(key, newReferences - 1); ++ } ++ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { ++ if (thr != null) { ++ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); ++ } ++ }); ++ } ++ ++ @Override ++ public boolean hasLightWork() { ++ // route to new light engine ++ return this.theLightEngine.hasUpdates() || !this.queue.isEmpty(); + } + ++ @Override ++ public LayerLightEventListener getLayerListener(final LightLayer lightType) { ++ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); ++ } ++ ++ @Override ++ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { ++ // need to use new light hooks for this ++ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; ++ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. ++ if (sky == 15) return 15; ++ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); ++ return Math.max(sky, block); ++ } ++ // Paper end - replace light engine imp ++ + @Override + public void close() { + } +@@ -180,15 +346,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void checkBlock(BlockPos pos) { +- BlockPos blockPos = pos.immutable(); +- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.POST_UPDATE, Util.name(() -> { +- super.checkBlock(blockPos); +- }, () -> { +- return "checkBlock " + blockPos; +- })); ++ // Paper start - replace light engine impl ++ final BlockPos posCopy = pos.immutable(); ++ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { ++ return this.theLightEngine.blockChange(posCopy); ++ }); ++ // Paper end - replace light engine impl + } + + protected void updateChunkStatus(ChunkPos pos) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -211,17 +378,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void updateSectionStatus(SectionPos pos, boolean notReady) { +- this.addTask(pos.x(), pos.z(), () -> { +- return 0; +- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +- super.updateSectionStatus(pos, notReady); +- }, () -> { +- return "updateSectionStatus " + pos + " " + notReady; +- })); ++ // Paper start - replace light engine impl ++ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { ++ return this.theLightEngine.sectionChange(pos, notReady); ++ }); ++ // Paper end - replace light engine impl + } + + @Override + public void enableLightSources(ChunkPos pos, boolean retainData) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { + super.enableLightSources(pos, retainData); + }, () -> { +@@ -231,6 +397,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles, boolean nonEdge) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x(), pos.z(), () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -252,6 +419,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + + @Override + public void retainData(ChunkPos pos, boolean retainData) { ++ if (true) return; // Paper - replace light engine impl + this.addTask(pos.x, pos.z, () -> { + return 0; + }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { +@@ -274,6 +442,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { ++ // Paper start - replace light engine impl ++ if (true) { ++ boolean lit = excludeBlocks; ++ final ChunkPos chunkPos = chunk.getPos(); ++ ++ return CompletableFuture.supplyAsync(() -> { ++ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); ++ if (!lit) { ++ chunk.setLightCorrect(false); ++ this.theLightEngine.lightChunk(chunk, emptySections); ++ chunk.setLightCorrect(true); ++ } else { ++ this.theLightEngine.forceLoadInChunk(chunk, emptySections); ++ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have ++ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should ++ // catch what we miss here. ++ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); ++ } ++ ++ this.chunkMap.releaseLightTicket(chunkPos); ++ return chunk; ++ }, (runnable) -> { ++ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); ++ this.tryScheduleUpdate(); ++ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { ++ if (throwable != null) { ++ LOGGER.error("Failed to light chunk " + chunkPos, throwable); ++ } ++ }); ++ } ++ // Paper end - replace light engine impl + ChunkPos chunkPos = chunk.getPos(); + // Paper start + //ichunkaccess.b(false); // Don't need to disable this +@@ -316,7 +515,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + } + + public void tryScheduleUpdate() { +- if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper ++ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine + this.taskMailbox.tell(() -> { + this.runUpdate(); + this.scheduled.set(false); +@@ -333,12 +532,12 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + if (queue.poll(pre, post)) { + pre.forEach(Runnable::run); + pre.clear(); +- super.runUpdates(Integer.MAX_VALUE, true, true); ++ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine + post.forEach(Runnable::run); + post.clear(); + } else { + // might have level updates to go still +- super.runUpdates(Integer.MAX_VALUE, true, true); ++ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine + } + // Paper end + } +diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java +index 41ddcf6775f99c56cf4b13b284420061e5dd6bdc..ae46429264e6a7e5c88b6b6a41a6df4db7b3e70d 100644 +--- a/src/main/java/net/minecraft/server/level/TicketType.java ++++ b/src/main/java/net/minecraft/server/level/TicketType.java +@@ -32,6 +32,7 @@ public class TicketType { + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper + public static final TicketType REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail ++ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting + + public static TicketType create(String name, Comparator argumentComparator) { + return new TicketType<>(name, argumentComparator, 0L); +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 178d9ad7525b6743038ed45c6f85686a860ffd26..24b820484497714eb8be87e07ca1d37829d4f2c9 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -701,6 +701,7 @@ public abstract class BlockBehaviour { + this.hasPostProcess = blockbase_info.hasPostProcess; + this.emissiveRendering = blockbase_info.emissiveRendering; + this.offsetType = (BlockBehaviour.OffsetType) blockbase_info.offsetType.apply(this.asState()); ++ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Paper + } + // 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; +@@ -721,6 +722,18 @@ public abstract class BlockBehaviour { + protected boolean isTicking; + protected FluidState fluid; + // Paper end ++ // Paper start ++ protected int opacityIfCached = -1; ++ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] ++ public final int getOpacityIfCached() { ++ return this.opacityIfCached; ++ } ++ ++ protected final boolean conditionallyFullOpaque; ++ public final boolean isConditionallyFullOpaque() { ++ return this.conditionallyFullOpaque; ++ } ++ // Paper end + + public void initCache() { + this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() +@@ -729,6 +742,7 @@ public abstract class BlockBehaviour { + this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); + } + this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here ++ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +index a97909e77b9b28aede8c8716831c3f9a90618f09..b68625ebb32b8d1e5bc232d7cc791edbed923378 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -81,6 +81,47 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); + // CraftBukkit end ++ // Paper start - rewrite light engine ++ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles; ++ ++ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles; ++ ++ private volatile boolean[] skyEmptinessMap; ++ ++ private volatile boolean[] blockEmptinessMap; ++ ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.blockNibbles; ++ } ++ ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.blockNibbles = nibbles; ++ } ++ ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.skyNibbles; ++ } ++ ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.skyNibbles = nibbles; ++ } ++ ++ public boolean[] getSkyEmptinessMap() { ++ return this.skyEmptinessMap; ++ } ++ ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ this.skyEmptinessMap = emptinessMap; ++ } ++ ++ public boolean[] getBlockEmptinessMap() { ++ return this.blockEmptinessMap; ++ } ++ ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) { ++ this.blockEmptinessMap = emptinessMap; ++ } ++ // Paper end - rewrite light engine + + public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biome, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable BlendingData blendingData) { + this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups +diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +index a9c65c8d36e5c7080133706df1363b3ce52e3370..d1b175f2bb1bc96e4f044a97b14721feb44d78f5 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java +@@ -21,6 +21,38 @@ public class EmptyLevelChunk extends LevelChunk { + this.biome = holder; + } + ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); ++ } ++ ++ @Override ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} ++ ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); ++ } ++ ++ @Override ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return null; ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) {} ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return null; ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) {} ++ + // Paper start + @Override + public BlockState getBlockState(int x, int y, int z) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +index 7b320357973202423c29743d922b72dc4ec11efe..8ffc206a858864d277ff94de7c66ffdb07d8f491 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java +@@ -31,6 +31,48 @@ public class ImposterProtoChunk extends ProtoChunk { + private final LevelChunk wrapped; + private final boolean allowWrites; + ++ // Paper start - rewrite light engine ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { ++ return this.wrapped.getBlockNibbles(); ++ } ++ ++ @Override ++ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.wrapped.setBlockNibbles(nibbles); ++ } ++ ++ @Override ++ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { ++ return this.wrapped.getSkyNibbles(); ++ } ++ ++ @Override ++ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { ++ this.wrapped.setSkyNibbles(nibbles); ++ } ++ ++ @Override ++ public boolean[] getSkyEmptinessMap() { ++ return this.wrapped.getSkyEmptinessMap(); ++ } ++ ++ @Override ++ public void setSkyEmptinessMap(final boolean[] emptinessMap) { ++ this.wrapped.setSkyEmptinessMap(emptinessMap); ++ } ++ ++ @Override ++ public boolean[] getBlockEmptinessMap() { ++ return this.wrapped.getBlockEmptinessMap(); ++ } ++ ++ @Override ++ public void setBlockEmptinessMap(final boolean[] emptinessMap) { ++ this.wrapped.setBlockEmptinessMap(emptinessMap); ++ } ++ // Paper end - rewrite light engine ++ + public ImposterProtoChunk(LevelChunk wrapped, boolean bl) { + super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.levelHeightAccessor, wrapped.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), wrapped.getBlendingData()); + this.wrapped = wrapped; +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index c85380c3bf3bf4448a28a91af78f41c235a583e4..d870cefbe5b7485f423817f4f639e3e2a304640c 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -100,6 +100,10 @@ public class LevelChunk extends ChunkAccess { + + public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { + super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry ++ // Paper start - rewrite light engine ++ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); ++ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); ++ // Paper end - rewrite light engine + this.tickersInLevel = Maps.newHashMap(); + this.clientLightReady = false; + this.level = (ServerLevel) world; // CraftBukkit - type +@@ -330,6 +334,12 @@ public class LevelChunk extends ChunkAccess { + + public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { + this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); ++ // Paper start - rewrite light engine ++ this.setBlockNibbles(protoChunk.getBlockNibbles()); ++ this.setSkyNibbles(protoChunk.getSkyNibbles()); ++ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); ++ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); ++ // Paper end - rewrite light engine + Iterator iterator = protoChunk.getBlockEntities().values().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +index 5ebde3a4f99b8d017d9a10a30fefc0b7dd011319..7908360dd47937b2cb702e381802b7b278a5198e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -203,7 +203,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + return this.get(this.strategy.getIndex(x, y, z)); + } + +- protected T get(int index) { ++ public T get(int index) { // Paper - public + PalettedContainer.Data data = this.data; + return data.palette.valueFor(data.storage.get(index)); + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +index 9014331e4ceac9f77a911aead87bf452d29e3fb4..13b62e8e6569c154547bc0d5626488c5b0839f20 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java +@@ -55,6 +55,12 @@ public class ProtoChunk extends ChunkAccess { + + public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoChunkTicks blockTickScheduler, ProtoChunkTicks fluidTickScheduler, LevelHeightAccessor world, Registry biomeRegistry, @Nullable BlendingData blendingData) { + super(pos, upgradeData, world, biomeRegistry, 0L, sections, blendingData); ++ // Paper start - rewrite light engine ++ if (!(this instanceof ImposterProtoChunk)) { ++ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); ++ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); ++ } ++ // Paper end - rewrite light engine + this.blockTicks = blockTickScheduler; + this.fluidTicks = fluidTickScheduler; + } +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index be9c15fe141ede1132dbe07ba4bfcf22036ab194..4df5853781a2ac89dd391374d34d9096643a2ab8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -94,6 +94,14 @@ public class ChunkSerializer { + public static final String BLOCK_LIGHT_TAG = "BlockLight"; + public static final String SKY_LIGHT_TAG = "SkyLight"; + ++ // Paper start - replace light engine impl ++ private static final int STARLIGHT_LIGHT_VERSION = 8; ++ ++ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; ++ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; ++ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; ++ // Paper end - replace light engine impl ++ + public ChunkSerializer() {} + + // Paper start - guard against serializing mismatching coordinates +@@ -153,13 +161,20 @@ public class ChunkSerializer { + } + + UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; +- boolean flag = nbt.getBoolean("isLightOn"); ++ boolean flag = getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper + ListTag nbttaglist = nbt.getList("sections", 10); + int i = world.getSectionsCount(); + LevelChunkSection[] achunksection = new LevelChunkSection[i]; + boolean flag1 = world.dimensionType().hasSkyLight(); + ServerChunkCache chunkproviderserver = world.getChunkSource(); + LevelLightEngine lightengine = chunkproviderserver.getLightEngine(); ++ // Paper start ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); // Paper - replace light impl ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); // Paper - replace light impl ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); ++ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); ++ boolean canReadSky = world.dimensionType().hasSkyLight(); ++ // Paper end + Registry iregistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); + Codec>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write + boolean flag2 = false; +@@ -167,7 +182,7 @@ public class ChunkSerializer { + DataResult dataresult; + + for (int j = 0; j < nbttaglist.size(); ++j) { +- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); ++ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper + byte b0 = nbttagcompound1.getByte("Y"); + int k = world.getSectionIndexFromSectionY(b0); + +@@ -214,31 +229,45 @@ public class ChunkSerializer { + boolean flag3 = nbttagcompound1.contains("BlockLight", 7); + boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7); + +- if (flag3 || flag4) { +- if (!flag2) { ++ // Paper start - rewrite the light engine ++ if (flag) { ++ try { ++ if ((flag3 || flag4) && !flag2) { ++ // Paper end - rewrite the light engine + tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main + lightengine.retainData(chunkPos, true); + }); // Paper - delay this task since we're executing off-main + flag2 = true; + } + ++ int y = sectionData.getByte("Y"); + if (flag3) { +- // Paper start - delay this task since we're executing off-main +- DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight").clone()); +- tasksToExecuteOnMain.add(() -> { +- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), blockLight, true); +- }); +- // Paper end - delay this task since we're executing off-main ++ // Paper start - rewrite the light engine ++ // this is where our diff is ++ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety ++ } else { ++ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); + } ++ // Paper end - rewrite the light engine + + if (flag4) { +- // Paper start - delay this task since we're executing off-main +- DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight").clone()); +- tasksToExecuteOnMain.add(() -> { +- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), skyLight, true); +- }); +- // Paper end - delay this task since we're executing off-mai ++ // Paper start - rewrite the light engine ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety ++ } else if (flag1) { ++ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); + } ++ // Paper end - rewrite the light engine ++ // Paper start - rewrite the light engine ++ } catch (Exception ex) { ++ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); ++ flag = false; ++ } ++ // Paper end - rewrite light engine + } + } + +@@ -267,6 +296,8 @@ public class ChunkSerializer { + }, chunkPos); + + object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata); ++ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl ++ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl + } else { + ProtoChunkTicks protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> { + return Registry.BLOCK.getOptional(ResourceLocation.tryParse(s)); +@@ -275,6 +306,8 @@ public class ChunkSerializer { + return Registry.FLUID.getOptional(ResourceLocation.tryParse(s)); + }, chunkPos); + ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata); ++ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl ++ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl + + object1 = protochunk; + protochunk.setInhabitedTime(l); +@@ -420,7 +453,7 @@ public class ChunkSerializer { + DataLayer[] blockLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; + DataLayer[] skyLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; + +- for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { ++ for (int i = lightenginethreaded.getMinLightSection(); false && i < lightenginethreaded.getMaxLightSection(); ++i) { // Paper - don't run loop, we don't need to - light data is per chunk now + DataLayer blockArray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i)); + DataLayer skyArray = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i)); + +@@ -478,6 +511,12 @@ public class ChunkSerializer { + } + public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) { + // Paper end ++ // Paper start - rewrite light impl ++ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); ++ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); ++ // Paper end - rewrite light impl + ChunkPos chunkcoordintpair = chunk.getPos(); + CompoundTag nbttagcompound = new CompoundTag(); + +@@ -528,20 +567,14 @@ public class ChunkSerializer { + for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { + int j = chunk.getSectionIndexFromSectionY(i); + boolean flag1 = j >= 0 && j < achunksection.length; +- // Paper start - async chunk save for unload +- DataLayer nibblearray; // block light +- DataLayer nibblearray1; // sky light +- if (asyncsavedata == null) { +- nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) +- nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) +- } else { +- nibblearray = asyncsavedata.blockLight[i - lightenginethreaded.getMinLightSection()]; +- nibblearray1 = asyncsavedata.skyLight[i - lightenginethreaded.getMinLightSection()]; +- } +- // Paper end ++ // Paper - replace light engine + +- if (flag1 || nibblearray != null || nibblearray1 != null) { +- CompoundTag nbttagcompound1 = new CompoundTag(); ++ // Paper start - replace light engine ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); ++ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); ++ if (flag1 || blockNibble != null || skyNibble != null) { ++ // Paper end - replace light engine ++ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper + + if (flag1) { + LevelChunkSection chunksection = achunksection[j]; +@@ -556,13 +589,27 @@ public class ChunkSerializer { + nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error)); + } + +- if (nibblearray != null && !nibblearray.isEmpty()) { +- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData()); ++ // Paper start ++ // we store under the same key so mod programs editing nbt ++ // can still read the data, hopefully. ++ // however, for compatibility we store chunks as unlit so vanilla ++ // is forced to re-light them if it encounters our data. It's too much of a burden ++ // to try and maintain compatibility with a broken and inferior skylight management system. ++ ++ if (blockNibble != null) { ++ if (blockNibble.data != null) { ++ section.putByteArray("BlockLight", blockNibble.data); ++ } ++ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); + } + +- if (nibblearray1 != null && !nibblearray1.isEmpty()) { +- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData()); ++ if (skyNibble != null) { ++ if (skyNibble.data != null) { ++ section.putByteArray("SkyLight", skyNibble.data); ++ } ++ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); + } ++ // Paper end + + if (!nbttagcompound1.isEmpty()) { + nbttagcompound1.putByte("Y", (byte) i); +@@ -573,7 +620,8 @@ public class ChunkSerializer { + + nbttagcompound.put("sections", nbttaglist); + if (flag) { +- nbttagcompound.putBoolean("isLightOn", true); ++ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper ++ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) + } + + // Paper start diff --git a/patches/server/0789-Always-parse-protochunk-light-sources-unless-it-is-m.patch b/patches/server/0789-Always-parse-protochunk-light-sources-unless-it-is-m.patch new file mode 100644 index 0000000000..6723907d85 --- /dev/null +++ b/patches/server/0789-Always-parse-protochunk-light-sources-unless-it-is-m.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 23 Aug 2021 04:50:05 -0700 +Subject: [PATCH] Always parse protochunk light sources unless it is marked as + non-lit + +Chunks not marked as lit will always go through the light engine, +so they should always have their block sources parsed. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +index 4df5853781a2ac89dd391374d34d9096643a2ab8..3367c75b1c132b42465d4c355a6b5fd00c3efe23 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +@@ -331,16 +331,33 @@ public class ChunkSerializer { + BelowZeroRetrogen belowzeroretrogen = protochunk.getBelowZeroRetrogen(); + boolean flag5 = chunkstatus.isOrAfter(ChunkStatus.LIGHT) || belowzeroretrogen != null && belowzeroretrogen.targetStatus().isOrAfter(ChunkStatus.LIGHT); + +- if (!flag && flag5) { +- Iterator iterator = BlockPos.betweenClosed(chunkPos.getMinBlockX(), world.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), world.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ()).iterator(); ++ if (!flag) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit ++ // Paper start - let's make sure the implementation isn't as slow as possible ++ int offX = chunkPos.x << 4; ++ int offZ = chunkPos.z << 4; ++ ++ int minChunkSection = io.papermc.paper.util.WorldUtil.getMinSection(world); ++ int maxChunkSection = io.papermc.paper.util.WorldUtil.getMaxSection(world); ++ ++ LevelChunkSection[] sections = achunksection; ++ for (int sectionY = minChunkSection; sectionY <= maxChunkSection; ++sectionY) { ++ LevelChunkSection section = sections[sectionY - minChunkSection]; ++ if (section == null || section.hasOnlyAir()) { ++ // no sources in empty sections ++ continue; ++ } ++ int offY = sectionY << 4; + +- while (iterator.hasNext()) { +- BlockPos blockposition = (BlockPos) iterator.next(); ++ for (int index = 0; index < (16 * 16 * 16); ++index) { ++ if (section.states.get(index).getLightEmission() <= 0) { ++ continue; ++ } + +- if (((ChunkAccess) object1).getBlockState(blockposition).getLightEmission() != 0) { +- protochunk.addLight(blockposition); ++ // index = x | (z << 4) | (y << 8) ++ protochunk.addLight(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); + } + } ++ // Paper end + } + } + diff --git a/patches/server/0789-Rewrite-the-light-engine.patch b/patches/server/0789-Rewrite-the-light-engine.patch deleted file mode 100644 index 7784f1b578..0000000000 --- a/patches/server/0789-Rewrite-the-light-engine.patch +++ /dev/null @@ -1,5290 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 28 Oct 2020 16:51:55 -0700 -Subject: [PATCH] Rewrite the light engine - -The standard vanilla light engine is plagued by -awful performance. Paper's changes to the light engine -help a bit, however they appear to cause some lighting -errors - most easily noticed in coral generation. - -The vanilla light engine's is too abstract to be modified - -so an entirely new implementation is required to fix the -performance and lighting errors. - -The new implementation is designed primarily to optimise -light level propagations (increase and decrease). Unlike -the vanilla light engine, this implementation tracks more -information per queued value when performing a -breadth first search. Vanilla just tracks coordinate, which -means every time they handle a queued value, they must -also determine the coordinate's target light level -from its neighbours - very wasteful, especially considering -these checks read neighbour block data. -The new light engine tracks both position and target level, -as well as whether the target block needs to be read at all -(for checking sided propagation). So, the work done per coordinate -is significantly reduced because no work is done for calculating -the target level. -In my testing, the block get calls were reduced by approximately -an order of magnitude. However, the light read checks were only -reduced by approximately 2x - but this is fine, light read checks -are extremely cheap compared to block gets. - -Generation testing showed that the new light engine improved -total generation (not lighting itself, but the whole generation process) -by 2x. According to cpu time, the light engine itself spent 10x less time -lighting chunks for generation. - -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4a04eb6449d33d3f15c354b2ac98198f4ac12758 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/BlockStarLightEngine.java -@@ -0,0 +1,288 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.ImposterProtoChunk; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public final class BlockStarLightEngine extends StarLightEngine { -+ -+ public BlockStarLightEngine(final Level world) { -+ super(false, world); -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setBlockEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getBlockNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setBlockNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically -+ // because a block was removed - which can decrease light. with sky data, block breaking can only result -+ // in increases, and thus the existing sky block check will actually correctly propagate light through -+ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove -+ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running -+ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence -+ // of vanilla data management we "hide" them. -+ nibble.setHidden(); -+ } -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); -+ } -+ } else { -+ nibble.setNonNull(); -+ } -+ } -+ -+ @Override -+ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change emitted light -+ // blocks can change direction of propagation -+ -+ final int encodeOffset = this.coordinateOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -+ final int emittedLevel = blockState.getLightEmission() & emittedMask; -+ -+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); -+ // this accounts for change in emitted light that would cause an increase -+ if (emittedLevel != 0) { -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ } -+ // this also accounts for a change in emitted light that would cause a decrease -+ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) -+ // as it checks all neighbours (even if current level is 0) -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ // always keep sided transparent false here, new block might be conditionally transparent which would -+ // prevent us from decreasing sources in the directions where the new block is opaque -+ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always -+ // catch that and fix it. -+ ); -+ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int level = centerState.getLightEmission() & 0xF; -+ -+ if (level >= (15 - 1) || level > expect) { -+ return level; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState conditionallyOpaqueState; -+ int opacity = centerState.getOpacityIfCached(); -+ -+ if (opacity == -1) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else if (opacity >= 15) { -+ return level; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ opacity = Math.max(1, opacity); -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ // passed transparency, -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected Iterator getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { -+ if (chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk) { -+ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is -+ // skipping empty sections, and the far more optimised reading of types. -+ List sources = new ArrayList<>(); -+ -+ int offX = chunk.getPos().x << 4; -+ int offZ = chunk.getPos().z << 4; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final LevelChunkSection section = sections[sectionY - this.minSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // no sources in empty sections -+ continue; -+ } -+ final PalettedContainer states = section.states; -+ final int offY = sectionY << 4; -+ -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ final BlockState state = states.get(index); -+ if (state.getLightEmission() <= 0) { -+ continue; -+ } -+ -+ // index = x | (z << 4) | (y << 8) -+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); -+ } -+ } -+ -+ return sources.iterator(); -+ } else { -+ // world gen and lighting run in parallel, and if lighting keeps up it can be lighting chunks that are -+ // being generated. In the nether, lava will add a lot of sources. This resulted in quite a few CME crashes. -+ // So all we do spinloop until we can collect a list of sources, and even if it is out of date we will pick up -+ // the missing sources from checkBlock. -+ for (;;) { -+ try { -+ return chunk.getLights().collect(Collectors.toList()).iterator(); -+ } catch (final Exception cme) { -+ continue; -+ } -+ } -+ } -+ } -+ -+ @Override -+ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ // setup sources -+ final int emittedMask = this.emittedLightMask; -+ for (final Iterator positions = this.getSources(lightAccess, chunk); positions.hasNext();) { -+ final BlockPos pos = positions.next(); -+ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ -+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { -+ // some other source is brighter -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLight & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ -+ -+ // propagation wont set this for us -+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); -+ } -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ // verify neighbour edges -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ } else { -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ffb4ffe01c4628d52742c5c0bbd35220eea6294 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SWMRNibbleArray.java -@@ -0,0 +1,440 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import net.minecraft.world.level.chunk.DataLayer; -+import java.util.ArrayDeque; -+import java.util.Arrays; -+ -+// SWMR -> Single Writer Multi Reader Nibble Array -+public final class SWMRNibbleArray { -+ -+ /* -+ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null -+ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised -+ * nibbles can be written to. -+ * -+ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. -+ * -+ * Initialised nibble - Has light data. -+ */ -+ -+ protected static final int INIT_STATE_NULL = 0; // null -+ protected static final int INIT_STATE_UNINIT = 1; // uninitialised -+ protected static final int INIT_STATE_INIT = 2; // initialised -+ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL -+ -+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block -+ // this allows us to maintain only 1 byte array when we're not updating -+ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); -+ -+ private static byte[] allocateBytes() { -+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); -+ if (inPool != null) { -+ return inPool; -+ } -+ -+ return new byte[ARRAY_SIZE]; -+ } -+ -+ private static void freeBytes(final byte[] bytes) { -+ WORKING_BYTES_POOL.get().addFirst(bytes); -+ } -+ -+ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { -+ if (nibble == null) { -+ return new SWMRNibbleArray(null, true); -+ } else if (nibble.isEmpty()) { -+ return new SWMRNibbleArray(); -+ } else { -+ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later -+ } -+ } -+ -+ protected int stateUpdating; -+ protected volatile int stateVisible; -+ -+ protected byte[] storageUpdating; -+ protected boolean updatingDirty; // only returns whether storageUpdating is dirty -+ protected volatile byte[] storageVisible; -+ -+ public SWMRNibbleArray() { -+ this(null, false); // lazy init -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes) { -+ this(bytes, false); -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final int state) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { -+ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); -+ } -+ this.stateUpdating = this.stateVisible = state; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ @Override -+ public String toString() { -+ StringBuilder stringBuilder = new StringBuilder(); -+ stringBuilder.append("State: "); -+ switch (this.stateVisible) { -+ case INIT_STATE_NULL: -+ stringBuilder.append("null"); -+ break; -+ case INIT_STATE_UNINIT: -+ stringBuilder.append("uninitialised"); -+ break; -+ case INIT_STATE_INIT: -+ stringBuilder.append("initialised"); -+ break; -+ case INIT_STATE_HIDDEN: -+ stringBuilder.append("hidden"); -+ break; -+ default: -+ stringBuilder.append("unknown"); -+ break; -+ } -+ stringBuilder.append("\nData:\n"); -+ -+ final byte[] data = this.storageVisible; -+ if (data != null) { -+ for (int i = 0; i < 4096; ++i) { -+ // Copied from NibbleArray#toString -+ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); -+ -+ stringBuilder.append(Integer.toHexString(level)); -+ if ((i & 15) == 15) { -+ stringBuilder.append("\n"); -+ } -+ -+ if ((i & 255) == 255) { -+ stringBuilder.append("\n"); -+ } -+ } -+ } else { -+ stringBuilder.append("null"); -+ } -+ -+ return stringBuilder.toString(); -+ } -+ -+ public SaveState getSaveState() { -+ synchronized (this) { -+ final int state = this.stateVisible; -+ final byte[] data = this.storageVisible; -+ if (state == INIT_STATE_NULL) { -+ return null; -+ } -+ if (state == INIT_STATE_UNINIT) { -+ return new SaveState(null, state); -+ } -+ final boolean zero = isAllZero(data); -+ if (zero) { -+ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; -+ } else { -+ return new SaveState(data.clone(), state); -+ } -+ } -+ } -+ -+ protected static boolean isAllZero(final byte[] data) { -+ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { -+ byte whole = data[i << 4]; -+ -+ for (int k = 1; k < (1 << 4); ++k) { -+ whole |= data[(i << 4) | k]; -+ } -+ -+ if (whole != 0) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ // operation type: updating on src, updating on other -+ public void extrudeLower(final SWMRNibbleArray other) { -+ if (other.stateUpdating == INIT_STATE_NULL) { -+ throw new IllegalArgumentException(); -+ } -+ -+ if (other.storageUpdating == null) { -+ this.setUninitialised(); -+ return; -+ } -+ -+ final byte[] src = other.storageUpdating; -+ final byte[] into; -+ -+ if (!this.updatingDirty) { -+ if (this.storageUpdating != null) { -+ into = this.storageUpdating = allocateBytes(); -+ } else { -+ this.storageUpdating = into = allocateBytes(); -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } else { -+ into = this.storageUpdating; -+ } -+ -+ final int start = 0; -+ final int end = (15 | (15 << 4)) >>> 1; -+ -+ /* x | (z << 4) | (y << 8) */ -+ for (int y = 0; y <= 15; ++y) { -+ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); -+ } -+ } -+ -+ // operation type: updating -+ public void setFull() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setZero() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setNonNull() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_NULL) { -+ return; -+ } -+ this.stateUpdating = INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public void setNull() { -+ this.stateUpdating = INIT_STATE_NULL; -+ if (this.updatingDirty && this.storageUpdating != null) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setUninitialised() { -+ this.stateUpdating = INIT_STATE_UNINIT; -+ if (this.storageUpdating != null && this.updatingDirty) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setHidden() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_INIT) { -+ this.setNull(); -+ } else { -+ this.stateUpdating = INIT_STATE_HIDDEN; -+ } -+ } -+ -+ // operation type: updating -+ public boolean isDirty() { -+ return this.stateUpdating != this.stateVisible || this.updatingDirty; -+ } -+ -+ // operation type: updating -+ public boolean isNullNibbleUpdating() { -+ return this.stateUpdating == INIT_STATE_NULL; -+ } -+ -+ // operation type: visible -+ public boolean isNullNibbleVisible() { -+ return this.stateVisible == INIT_STATE_NULL; -+ } -+ -+ // opeartion type: updating -+ public boolean isUninitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: visible -+ public boolean isUninitialisedVisible() { -+ return this.stateVisible == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public boolean isInitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_INIT; -+ } -+ -+ // operation type: visible -+ public boolean isInitialisedVisible() { -+ return this.stateVisible == INIT_STATE_INIT; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenUpdating() { -+ return this.stateUpdating == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenVisible() { -+ return this.stateVisible == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ protected void swapUpdatingAndMarkDirty() { -+ if (this.updatingDirty) { -+ return; -+ } -+ -+ if (this.storageUpdating == null) { -+ this.storageUpdating = allocateBytes(); -+ Arrays.fill(this.storageUpdating, (byte)0); -+ } else { -+ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); -+ } -+ -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public boolean updateVisible() { -+ if (!this.isDirty()) { -+ return false; -+ } -+ -+ synchronized (this) { -+ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { -+ this.storageVisible = null; -+ } else { -+ if (this.storageVisible == null) { -+ this.storageVisible = this.storageUpdating.clone(); -+ } else { -+ if (this.storageUpdating != this.storageVisible) { -+ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); -+ } -+ } -+ -+ if (this.storageUpdating != this.storageVisible) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = this.storageVisible; -+ } -+ this.updatingDirty = false; -+ this.stateVisible = this.stateUpdating; -+ } -+ -+ return true; -+ } -+ -+ // operation type: visible -+ public DataLayer toVanillaNibble() { -+ synchronized (this) { -+ switch (this.stateVisible) { -+ case INIT_STATE_HIDDEN: -+ case INIT_STATE_NULL: -+ return null; -+ case INIT_STATE_UNINIT: -+ return new DataLayer(); -+ case INIT_STATE_INIT: -+ return new DataLayer(this.storageVisible.clone()); -+ default: -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ /* x | (z << 4) | (y << 8) */ -+ -+ // operation type: updating -+ public int getUpdating(final int x, final int y, final int z) { -+ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: updating -+ public int getUpdating(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] bytes = this.storageUpdating; -+ if (bytes == null) { -+ return 0; -+ } -+ final byte value = bytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int x, final int y, final int z) { -+ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] visibleBytes = this.storageVisible; -+ if (visibleBytes == null) { -+ return 0; -+ } -+ final byte value = visibleBytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: updating -+ public void set(final int x, final int y, final int z, final int value) { -+ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); -+ } -+ -+ // operation type: updating -+ public void set(final int index, final int value) { -+ if (!this.updatingDirty) { -+ this.swapUpdatingAndMarkDirty(); -+ } -+ final int shift = (index & 1) << 2; -+ final int i = index >>> 1; -+ -+ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); -+ } -+ -+ public static final class SaveState { -+ -+ public final byte[] data; -+ public final int state; -+ -+ public SaveState(final byte[] data, final int state) { -+ this.data = data; -+ this.state = state; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5f771962afb44175d446f138c8e7453230f48c6c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/SkyStarLightEngine.java -@@ -0,0 +1,709 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.Set; -+ -+public final class SkyStarLightEngine extends StarLightEngine { -+ -+ /* -+ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: -+ -+ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. -+ -+ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. -+ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees -+ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise -+ our own) - we need a radius of 2 to de-initialise neighbour nibbles. -+ How do we solve this? -+ -+ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. -+ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the -+ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last -+ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data -+ to see if any of its nibbles need to be de-initialised. -+ -+ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, -+ and if it doesn't have data then we know it will correctly de-initialise once it fills up. -+ -+ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking -+ around those. -+ */ -+ -+ protected final int[] heightMapBlockChange = new int[16 * 16]; -+ { -+ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap -+ } -+ -+ protected final boolean[] nullPropagationCheckCache; -+ -+ public SkyStarLightEngine(final Level world) { -+ super(true, world); -+ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); -+ } -+ } -+ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ nibble.setNull(); -+ } -+ } -+ -+ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { -+ if (!currNibble.isNullNibbleUpdating()) { -+ // already initialised -+ return; -+ } -+ -+ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = this.minLightSection - 1; -+ for (int currY = this.maxSection; currY >= this.minSection; --currY) { -+ if (emptinessMap == null) { -+ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. -+ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); -+ if (current == null || current.hasOnlyAir()) { -+ continue; -+ } -+ } else { -+ if (emptinessMap[currY - this.minSection]) { -+ continue; -+ } -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (chunkY > lowestY) { -+ // we need to set this one to full -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ nibble.setNonNull(); -+ nibble.setFull(); -+ return; -+ } -+ -+ if (extrude) { -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ currNibble.setNonNull(); -+ currNibble.extrudeLower(nibble); -+ break; -+ } -+ } -+ } else { -+ currNibble.setNonNull(); -+ } -+ } -+ -+ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (nibble != null && nibble.isNullNibbleUpdating()) { -+ // stop propagation in these areas -+ this.nibbleCache[index] = null; -+ nibble.updateVisible(); -+ } -+ } -+ } -+ -+ // rets whether neighbours were init'd -+ -+ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, -+ final boolean extrudeInitialised) { -+ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are -+ // non-null. Propagation to these neighbours is necessary. -+ // What makes this easy is we know none of these neighbours are non-empty (otherwise -+ // this nibble would be initialised). So, we don't have to initialise -+ // the neighbours in the full 1 radius, because there's no worry that any "paths" -+ // to the neighbours on this horizontal plane are blocked. -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { -+ return false; -+ } -+ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; -+ -+ // check horizontal neighbours -+ boolean needInitNeighbours = false; -+ neighbour_search: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ needInitNeighbours = true; -+ break neighbour_search; -+ } -+ } -+ } -+ -+ if (needInitNeighbours) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); -+ } -+ } -+ } -+ -+ return needInitNeighbours; -+ } -+ -+ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { -+ final int chunkX = worldX >> 4; -+ int chunkY = worldY >> 4; -+ final int chunkZ = worldZ >> 4; -+ -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, worldY, worldZ); -+ } -+ -+ for (;;) { -+ if (++chunkY > this.maxLightSection) { -+ return 15; -+ } -+ -+ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, 0, worldZ); -+ } -+ } -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setSkyEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getSkyNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setSkyNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ // can only use chunks for sky stuff if their sections have been init'd -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, -+ final int toSection) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (int y = toSection; y >= fromSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ final int y = (int)iterator.nextShort(); -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, sections); -+ } -+ -+ @Override -+ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change direction of propagation -+ -+ // same logic applies from BlockStarLightEngine#checkBlock -+ -+ final int encodeOffset = this.coordinateOffset; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ -+ if (currentLevel == 15) { -+ // must re-propagate clobbered source -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent -+ ); -+ } else { -+ this.setLightLevel(worldX, worldY, worldZ, 0); -+ } -+ -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ ); -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ if (expect == 15) { -+ return expect; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int opacity = centerState.getOpacityIfCached(); -+ -+ final BlockState conditionallyOpaqueState; -+ if (opacity < 0) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else { -+ conditionallyOpaqueState = null; -+ opacity = Math.max(1, opacity); -+ } -+ -+ int level = 0; -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ this.rewriteNibbleCacheForSkylight(atChunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final int chunkX = atChunk.getPos().x; -+ final int chunkZ = atChunk.getPos().z; -+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); -+ -+ // setup heightmap for changes -+ for (final BlockPos pos : positions) { -+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; -+ final int curr = this.heightMapBlockChange[index]; -+ if (pos.getY() > curr) { -+ this.heightMapBlockChange[index] = pos.getY(); -+ } -+ } -+ -+ // note: light sets are delayed while processing skylight source changes due to how -+ // nibbles are initialised, as we want to avoid clobbering nibble values so what when -+ // below nibbles are initialised they aren't reading from partially modified nibbles -+ -+ // now we can recalculate the sources for the changed columns -+ for (int index = 0; index < (16 * 16); ++index) { -+ final int maxY = this.heightMapBlockChange[index]; -+ if (maxY == Integer.MIN_VALUE) { -+ // not changed -+ continue; -+ } -+ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller -+ -+ final int columnX = (index & 15) | (chunkX << 4); -+ final int columnZ = (index >>> 4) | (chunkZ << 4); -+ -+ // try and propagate from the above y -+ // delay light set until after processing all sources to setup -+ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); -+ -+ // maxPropagationY is now the highest block that could not be propagated to -+ -+ // remove all sources below that are 15 -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; -+ final int encodeOffset = this.coordinateOffset; -+ -+ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); -+ -+ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { -+ if ((currY & 15) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); -+ } -+ -+ // ensure section below is always checked -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); -+ if (nibble == null) { -+ // advance currY to the the top of the section below -+ currY = (currY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ continue; -+ } -+ -+ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { -+ break; -+ } -+ -+ // delay light set until after processing all sources to setup -+ this.appendToDecreaseQueue( -+ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // do not set transparent blocks for the same reason we don't in the checkBlock method -+ ); -+ } -+ } -+ } -+ -+ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads -+ // immediate light value -+ this.processDelayedIncreases(); -+ this.processDelayedDecreases(); -+ -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected final int[] heightMapGen = new int[32 * 32]; -+ -+ @Override -+ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ this.rewriteNibbleCacheForSkylight(chunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ int highestNonEmptySection = this.maxSection; -+ while (highestNonEmptySection == (this.minSection - 1) || -+ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].hasOnlyAir()) { -+ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); -+ // try propagate FULL to neighbours -+ -+ // check neighbours to see if we need to propagate into them -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourX = chunkX + direction.x; -+ final int neighbourZ = chunkZ + direction.z; -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); -+ if (neighbourNibble == null) { -+ // unloaded neighbour -+ // most of the time we fall here -+ continue; -+ } -+ -+ // it looks like we need to propagate into the neighbour -+ -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (direction.x != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (direction.z < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction -+ -+ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) -+ ); -+ } -+ } -+ } -+ -+ if (highestNonEmptySection-- == (this.minSection - 1)) { -+ break; -+ } -+ } -+ -+ if (highestNonEmptySection >= this.minSection) { -+ // fill out our other sources -+ final int minX = chunkPos.x << 4; -+ final int maxX = chunkPos.x << 4 | 15; -+ final int minZ = chunkPos.z << 4; -+ final int maxZ = chunkPos.z << 4 | 15; -+ final int startY = highestNonEmptySection << 4 | 15; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); -+ } -+ } -+ } // else: apparently the chunk is empty -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ // no need to rewrite the nibble cache again -+ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ } else { -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+ -+ protected final void processDelayedIncreases() { -+ // copied from performLightIncrease -+ final long[] queue = this.increaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ } -+ -+ protected final void processDelayedDecreases() { -+ // copied from performLightDecrease -+ final long[] queue = this.decreaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ -+ this.setLightLevel(posX, posY, posZ, 0); -+ } -+ } -+ -+ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays -+ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so -+ // clobbering the light values will result in broken propagation) -+ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, -+ final boolean extrudeInitialised, final boolean delayLightSet) { -+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. -+ -+ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { -+ return startY; -+ } -+ -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ -+ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); -+ -+ for (;startY >= (this.minLightSection << 4); --startY) { -+ if ((startY & 15) == 15) { -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ } -+ final BlockState current = this.getBlockState(worldX, startY, worldZ); -+ -+ final VoxelShape fromShape; -+ if (above.isConditionallyFullOpaque()) { -+ this.mutablePos2.set(worldX, startY + 1, worldZ); -+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); -+ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ // above wont let us propagate -+ break; -+ } -+ } else { -+ fromShape = Shapes.empty(); -+ } -+ -+ final int opacityIfCached = current.getOpacityIfCached(); -+ // does light propagate from the top down? -+ if (opacityIfCached != -1) { -+ if (opacityIfCached != 0) { -+ // we cannot propagate 15 through this -+ break; -+ } -+ // most of the time it falls here. -+ // add to propagate -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ ); -+ } else { -+ mutablePos.set(worldX, startY, worldZ); -+ long flags = 0L; -+ if (current.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. -+ break; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = current.getLightBlock(world, mutablePos); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; -+ } -+ -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); -+ } -+ -+ above = current; -+ -+ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { -+ // we skip empty sections here, as this is just an easy way of making sure the above block -+ // can propagate through air. -+ -+ // nothing can propagate in null sections, remove the queue entry for it -+ --this.increaseQueueInitialLength; -+ -+ // advance currY to the the top of the section below -+ startY = (startY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ -+ // make sure this is marked as AIR -+ above = AIR_BLOCK_STATE; -+ } else if (!delayLightSet) { -+ this.setLightLevel(worldX, startY, worldZ, 15); -+ } -+ } -+ -+ return startY; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b0d92c68407cdb09ed8aac271b625d92db87017 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightEngine.java -@@ -0,0 +1,1572 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.IntegerUtil; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.SectionPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelHeightAccessor; -+import net.minecraft.world.level.LightLayer; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public abstract class StarLightEngine { -+ -+ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); -+ -+ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); -+ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; -+ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { -+ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, -+ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z -+ }; -+ -+ protected static enum AxisDirection { -+ -+ // Declaration order is important and relied upon. Do not change without modifying propagation code. -+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); -+ -+ static { -+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; -+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; -+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; -+ } -+ -+ protected AxisDirection opposite; -+ -+ public final int x; -+ public final int y; -+ public final int z; -+ public final Direction nms; -+ public final long everythingButThisDirection; -+ public final long everythingButTheOppositeDirection; -+ -+ AxisDirection(final int x, final int y, final int z) { -+ this.x = x; -+ this.y = y; -+ this.z = z; -+ this.nms = Direction.fromNormal(x, y, z); -+ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); -+ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. -+ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); -+ } -+ -+ public AxisDirection getOpposite() { -+ return this.opposite; -+ } -+ } -+ -+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 -+ // for explaining how light propagates via breadth-first search -+ -+ // While the above is a good start to understanding the general idea of what the general principles are, it's not -+ // exactly how the vanilla light engine should behave for minecraft. -+ -+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ // null index indicates the chunk section doesn't exist (empty or out of bounds) -+ protected final LevelChunkSection[] sectionCache; -+ -+ // the exact same as above, except for storing fast access to SWMRNibbleArray -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final SWMRNibbleArray[] nibbleCache; -+ -+ // the exact same as above, except for storing fast access to nibbles to call change callbacks for -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final boolean[] notifyUpdateCache; -+ -+ // always initialsed during start of lighting. -+ // index = x + (z * 5) -+ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; -+ -+ // index = x + (z * 5) -+ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; -+ -+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); -+ -+ protected int encodeOffsetX; -+ protected int encodeOffsetY; -+ protected int encodeOffsetZ; -+ -+ protected int coordinateOffset; -+ -+ protected int chunkOffsetX; -+ protected int chunkOffsetY; -+ protected int chunkOffsetZ; -+ -+ protected int chunkIndexOffset; -+ protected int chunkSectionIndexOffset; -+ -+ protected final boolean skylightPropagator; -+ protected final int emittedLightMask; -+ protected final boolean isClientSide; -+ -+ protected final Level world; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ protected final int minSection; -+ protected final int maxSection; -+ -+ protected StarLightEngine(final boolean skylightPropagator, final Level world) { -+ this.skylightPropagator = skylightPropagator; -+ this.emittedLightMask = skylightPropagator ? 0 : 0xF; -+ this.isClientSide = world.isClientSide; -+ this.world = world; -+ this.minLightSection = WorldUtil.getMinLightSection(world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(world); -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ -+ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ } -+ -+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { -+ // 31 = center + encodeOffset -+ this.encodeOffsetX = 31 - centerX; -+ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value -+ this.encodeOffsetZ = 31 - centerZ; -+ -+ // coordinateIndex = x | (z << 6) | (y << 12) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); -+ -+ // 2 = (centerX >> 4) + chunkOffset -+ this.chunkOffsetX = 2 - (centerX >> 4); -+ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 -+ this.chunkOffsetZ = 2 - (centerZ >> 4); -+ -+ // chunk index = x + (5 * z) -+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); -+ -+ // chunk section index = x + (5 * z) + ((5*5) * y) -+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); -+ } -+ -+ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, -+ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { -+ final int centerChunkX = centerX >> 4; -+ final int centerChunkY = centerY >> 4; -+ final int centerChunkZ = centerZ >> 4; -+ -+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); -+ -+ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; -+ -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final int cx = centerChunkX + dx; -+ final int cz = centerChunkZ + dz; -+ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; -+ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); -+ -+ if (chunk == null) { -+ if (relaxed | isTwoRadius) { -+ continue; -+ } -+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); -+ } -+ -+ if (!this.canUseChunk(chunk)) { -+ continue; -+ } -+ -+ this.setChunkInCache(cx, cz, chunk); -+ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); -+ if (!isTwoRadius) { -+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); -+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); -+ } -+ } -+ } -+ } -+ -+ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { -+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { -+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; -+ } -+ -+ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { -+ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; -+ } -+ -+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setChunkSectionInCache(chunkX, cy, chunkZ, -+ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? sections[cy - this.minSection] : null)); -+ } -+ } -+ -+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; -+ -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; -+ } -+ -+ return ret; -+ } -+ -+ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { -+ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; -+ } -+ -+ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); -+ } -+ } -+ -+ protected final void updateVisible(final LightChunkGetter lightAccess) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { -+ continue; -+ } -+ -+ final int chunkX = (index % 5) - this.chunkOffsetX; -+ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; -+ final int ySections = (this.maxSection - this.minSection) + 1; -+ final int chunkY = ((index / (5*5)) % (ySections + 2 + 2)) - this.chunkOffsetY; -+ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); -+ } -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sectionCache, null); -+ Arrays.fill(this.nibbleCache, null); -+ Arrays.fill(this.chunkCache, null); -+ Arrays.fill(this.emptinessMapCache, null); -+ if (this.isClientSide) { -+ Arrays.fill(this.notifyUpdateCache, false); -+ } -+ } -+ -+ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { -+ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { -+ final LevelChunkSection section = this.sectionCache[sectionIndex]; -+ -+ if (section != null) { -+ return section.hasOnlyAir() ? AIR_BLOCK_STATE : section.states.get(localIndex); -+ } -+ -+ return AIR_BLOCK_STATE; -+ } -+ -+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { -+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); -+ } -+ -+ protected final int getLightLevel(final int sectionIndex, final int localIndex) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ return nibble == null ? 0 : nibble.getUpdating(localIndex); -+ } -+ -+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { -+ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set(localIndex, level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { -+ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { -+ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; -+ } -+ -+ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { -+ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); -+ } -+ -+ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; -+ -+ for (int i = 0, len = ret.length; i < len; ++i) { -+ ret[i] = new SWMRNibbleArray(null, true); -+ } -+ -+ return ret; -+ } -+ -+ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); -+ -+ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); -+ -+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); -+ -+ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); -+ -+ protected abstract boolean canUseChunk(final ChunkAccess chunk); -+ -+ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Set positions, final Boolean[] changedSections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ if (changedSections != null) { -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ } -+ if (!positions.isEmpty()) { -+ this.propagateBlockChanges(lightAccess, chunk, positions); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); -+ -+ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); -+ -+ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) -+ // if ret == expect, then expect is the correct light value for pos -+ // if ret < expect, then ret is the real light value -+ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect); -+ -+ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; -+ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; -+ -+ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (currNibble == null) { -+ return; -+ } -+ -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ chunkY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null) { -+ continue; -+ } -+ -+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { -+ // both are zero, nothing to check. -+ continue; -+ } -+ -+ // this chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ int centerDelayedChecks = 0; -+ int neighbourDelayedChecks = 0; -+ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int neighbourX = currX + neighbourOffX; -+ final int neighbourZ = currZ + neighbourOffZ; -+ -+ final int currentIndex = (currX & 15) | -+ ((currZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int currentLevel = currNibble.getUpdating(currentIndex); -+ -+ final int neighbourIndex = -+ (neighbourX & 15) | -+ ((neighbourZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); -+ -+ // the checks are delayed because the checkBlock method clobbers light values - which then -+ // affect later calculate light value operations. While they don't affect it in a behaviourly significant -+ // way, they do have a negative performance impact due to simply queueing more values -+ -+ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { -+ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; -+ } -+ -+ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { -+ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; -+ } -+ } -+ } -+ -+ final int currentChunkOffX = chunkX << 4; -+ final int currentChunkOffZ = chunkZ << 4; -+ final int neighbourChunkOffX = (chunkX + direction.x) << 4; -+ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; -+ final int chunkOffY = chunkY << 4; -+ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { -+ // try to queue neighbouring data together -+ // index = x | (z << 4) | (y << 8) -+ if (i < centerDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesCenter[i]; -+ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ currentChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ if (i < neighbourDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; -+ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ neighbourChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ } -+ } -+ } -+ -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours -+ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). -+ // This does not resolve skylight source problems. -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. -+ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); -+ if (currNibble == null) { -+ continue; -+ } -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ currSectionY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { -+ // can't pull from 0 -+ continue; -+ } -+ -+ // neighbour chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = (chunkX << 4) - 1; -+ } else { -+ startX = (chunkX << 4) + 16; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = (chunkZ << 4) - 1; -+ } else { -+ startZ = (chunkZ << 4) + 16; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk -+ final int encodeOffset = this.coordinateOffset; -+ -+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int level = neighbourNibble.getUpdating( -+ (currX & 15) -+ | ((currZ & 15) << 4) -+ | ((currY & 15) << 8) -+ ); -+ -+ if (level <= 1) { -+ // nothing to propagate -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((level & 0xFL) << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. -+ ); -+ } -+ } -+ } -+ } -+ } -+ -+ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { -+ final LevelChunkSection[] sections = chunk.getSections(); -+ final Boolean[] ret = new Boolean[sections.length]; -+ -+ for (int i = 0; i < sections.length; ++i) { -+ if (sections[i] == null || sections[i].hasOnlyAir()) { -+ ret[i] = Boolean.TRUE; -+ } else { -+ ret[i] = Boolean.FALSE; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Boolean[] emptinessChanges) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); -+ -+ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // subclasses are guaranteed that this is always called before a changed block set -+ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks -+ // rets non-null when the emptiness map changed and needs to be updated -+ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final Boolean[] emptinessChanges, final boolean unlit) { -+ final Level world = (Level)lightAccess.getLevel(); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ -+ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ boolean[] ret = null; -+ final boolean needsInit = unlit || chunkEmptinessMap == null; -+ if (needsInit) { -+ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); -+ } -+ -+ // update emptiness map -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ if (valueBoxed == null) { -+ if (!needsInit) { -+ continue; -+ } -+ final LevelChunkSection section = this.getChunkSection(chunkX, sectionIndex + this.minSection, chunkZ); -+ emptinessChanges[sectionIndex] = valueBoxed = section == null || section.hasOnlyAir() ? Boolean.TRUE : Boolean.FALSE; -+ } -+ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); -+ } -+ -+ // now init neighbour nibbles -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ final Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ final int sectionY = sectionIndex + this.minSection; -+ if (valueBoxed == null) { -+ continue; -+ } -+ -+ final boolean empty = valueBoxed.booleanValue(); -+ -+ if (empty) { -+ continue; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // if we're not empty, we also need to initialise nibbles -+ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ for (int dy = 1; dy >= -1; --dy) { -+ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ // check for de-init and lazy-init -+ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running -+ // init checks. -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // does this neighbour have 1 radius loaded? -+ boolean neighboursLoaded = true; -+ neighbour_loaded_search: -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { -+ neighboursLoaded = false; -+ break neighbour_loaded_search; -+ } -+ } -+ } -+ -+ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { -+ // check neighbours to see if we need to de-init this one -+ boolean allEmpty = true; -+ neighbour_search: -+ for (int dy2 = -1; dy2 <= 1; ++dy2) { -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int y = sectionY + dy2; -+ if (y < this.minSection || y > this.maxSection) { -+ // empty -+ continue; -+ } -+ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); -+ if (emptinessMap != null) { -+ if (!emptinessMap[y - this.minSection]) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } else { -+ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); -+ if (section != null && !section.hasOnlyAir()) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } -+ } -+ } -+ } -+ -+ if (allEmpty & neighboursLoaded) { -+ // can only de-init when neighbours are loaded -+ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting -+ // to be correct -+ -+ // all were empty, so de-init -+ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); -+ } else if (!allEmpty) { -+ // must init -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, sections); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current -+ // chunks light values with respect to neighbours -+ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function -+ // does not need to detect empty chunks itself (and it should do no handling for them either!) -+ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); -+ -+ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ -+ try { -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.lightChunk(lightAccess, chunk, true); -+ this.setNibbles(chunk, nibbles); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, -+ final Consumer chunkLightCallback, final IntConsumer onComplete) { -+ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of -+ // the region of chunks to relight -+ // it's required that tickets are added for each chunk to keep them loaded -+ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); -+ -+ final int[] neighbourLightOrder = new int[] { -+ // d = 0 -+ 0, 0, -+ // d = 1 -+ -1, 0, -+ 0, -1, -+ 1, 0, -+ 0, 1, -+ // d = 2 -+ -1, 1, -+ 1, 1, -+ -1, -1, -+ 1, -1, -+ }; -+ -+ int lightCalls = 0; -+ -+ for (final ChunkPos chunkPos : chunks) { -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); -+ if (chunk == null || !this.canUseChunk(chunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { -+ final int dx = neighbourLightOrder[i]; -+ final int dz = neighbourLightOrder[i + 1]; -+ final int neighbourX = dx + chunkX; -+ final int neighbourZ = dz + chunkZ; -+ -+ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); -+ if (neighbour == null || !this.canUseChunk(neighbour)) { -+ continue; -+ } -+ -+ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { -+ // lit already called for neighbour, no need to light it now -+ continue; -+ } -+ -+ // light neighbour chunk -+ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); -+ try { -+ // insert all neighbouring chunks for this neighbour that we have data for -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int neighbourX2 = neighbourX + dx2; -+ final int neighbourZ2 = neighbourZ + dz2; -+ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); -+ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); -+ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); -+ if (nibbles == null) { -+ // we haven't lit this chunk -+ continue; -+ } -+ -+ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); -+ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); -+ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); -+ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); -+ } -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ // now insert the neighbour chunk and light it -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); -+ nibblesByChunk.put(key, nibbles); -+ -+ this.setChunkInCache(neighbourX, neighbourZ, neighbour); -+ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); -+ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); -+ -+ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); -+ emptinessMapByChunk.put(key, neighbourEmptiness); -+ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { -+ this.setEmptinessMap(neighbour, neighbourEmptiness); -+ } -+ -+ this.lightChunk(lightAccess, neighbour, false); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // done lighting all neighbours, so the chunk is now fully lit -+ -+ // make sure nibbles are fully updated before calling back -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ for (final SWMRNibbleArray nibble : nibbles) { -+ nibble.updateVisible(); -+ } -+ -+ this.setNibbles(chunk, nibbles); -+ -+ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); -+ } -+ -+ // now do callback -+ if (chunkLightCallback != null) { -+ chunkLightCallback.accept(chunkPos); -+ } -+ ++lightCalls; -+ } -+ -+ if (onComplete != null) { -+ onComplete.accept(lightCalls); -+ } -+ } -+ -+ // contains: -+ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) -+ // next 4 bits: propagated light level (0, 15] -+ // next 6 bits: propagation direction bitset -+ // next 24 bits: unused -+ // last 3 bits: state flags -+ // state flags: -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading light -+ // updates for block sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 2; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to consider if its block is conditionally transparent -+ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; -+ -+ protected long[] increaseQueue = new long[16 * 16 * 16]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[16 * 16 * 16]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; -+ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; -+ static { -+ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { -+ final List directions = new ArrayList<>(); -+ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { -+ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); -+ } -+ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); -+ } -+ } -+ -+ protected final void performLightIncrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore block sources after a propagation decrease -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want or unloaded -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void performLightDecrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final VoxelShape fromShape = (fromBlock.isConditionallyFullOpaque()) ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? (FLAG_WRITE_LEVEL | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) : FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (flags | FLAG_WRITE_LEVEL); -+ } -+ -+ currentNibble.set(localIndex, 0); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performLightIncrease(lightAccess); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef8dcbb6bbc0769e9ccfdadb05e6a46c070eda98 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/light/StarLightInterface.java -@@ -0,0 +1,665 @@ -+package ca.spottedleaf.starlight.common.light; -+ -+import ca.spottedleaf.starlight.common.util.CoordinateUtils; -+import ca.spottedleaf.starlight.common.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.DataLayer; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import net.minecraft.world.level.lighting.LevelLightEngine; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public final class StarLightInterface { -+ -+ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); -+ -+ /** -+ * Can be {@code null}, indicating the light is all empty. -+ */ -+ protected final Level world; -+ protected final LightChunkGetter lightAccess; -+ -+ protected final ArrayDeque cachedSkyPropagators; -+ protected final ArrayDeque cachedBlockPropagators; -+ -+ protected final LightQueue lightQueue = new LightQueue(this); -+ -+ protected final LayerLightEventListener skyReader; -+ protected final LayerLightEventListener blockReader; -+ protected final boolean isClientSide; -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ -+ public final LevelLightEngine lightEngine; -+ -+ private final boolean hasBlockLight; -+ private final boolean hasSkyLight; -+ -+ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { -+ this.lightAccess = lightAccess; -+ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); -+ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.isClientSide = !(this.world instanceof ServerLevel); -+ if (this.world == null) { -+ this.minSection = -4; -+ this.maxSection = 19; -+ this.minLightSection = -5; -+ this.maxLightSection = 20; -+ } else { -+ this.minSection = WorldUtil.getMinSection(this.world); -+ this.maxSection = WorldUtil.getMaxSection(this.world); -+ this.minLightSection = WorldUtil.getMinLightSection(this.world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); -+ } -+ this.lightEngine = lightEngine; -+ this.hasBlockLight = hasBlockLight; -+ this.hasSkyLight = hasSkyLight; -+ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { -+ // skylight doesn't care -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runUpdates(final int i, final boolean bl, final boolean bl2) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return null; -+ } -+ -+ final int sectionY = pos.getY(); -+ -+ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { -+ return null; -+ } -+ -+ if (chunk.getSkyEmptinessMap() == null) { -+ return null; -+ } -+ -+ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getSkyLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { -+ this.checkBlock(blockPos); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runUpdates(final int i, final boolean bl, final boolean bl2) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ -+ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { -+ return null; -+ } -+ -+ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ return StarLightInterface.this.getBlockLightValue(blockPos, StarLightInterface.this.getAnyChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4)); -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ } -+ -+ protected int getSkyLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasSkyLight) { -+ return 0; -+ } -+ final int x = blockPos.getX(); -+ int y = blockPos.getY(); -+ final int z = blockPos.getZ(); -+ -+ final int minSection = this.minSection; -+ final int maxSection = this.maxSection; -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (chunk == null || (!this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return 15; -+ } -+ -+ int sectionY = y >> 4; -+ -+ if (sectionY > maxLightSection) { -+ return 15; -+ } -+ -+ if (sectionY < minLightSection) { -+ sectionY = minLightSection; -+ y = sectionY << 4; -+ } -+ -+ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); -+ final SWMRNibbleArray immediate = nibbles[sectionY - minLightSection]; -+ -+ if (!immediate.isNullNibbleVisible()) { -+ return immediate.getVisible(x, y, z); -+ } -+ -+ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); -+ -+ if (emptinessMap == null) { -+ return 15; -+ } -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = minLightSection - 1; -+ for (int currY = maxSection; currY >= minSection; --currY) { -+ if (emptinessMap[currY - minSection]) { -+ continue; -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (sectionY > lowestY) { -+ return 15; -+ } -+ -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = sectionY + 1; currY <= maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = nibbles[currY - minLightSection]; -+ if (!nibble.isNullNibbleVisible()) { -+ return nibble.getVisible(x, 0, z); -+ } -+ } -+ -+ // should never reach here -+ return 15; -+ } -+ -+ protected int getBlockLightValue(final BlockPos blockPos, final ChunkAccess chunk) { -+ if (!this.hasBlockLight) { -+ return 0; -+ } -+ final int y = blockPos.getY(); -+ final int cy = y >> 4; -+ -+ final int minLightSection = this.minLightSection; -+ final int maxLightSection = this.maxLightSection; -+ -+ if (cy < minLightSection || cy > maxLightSection) { -+ return 0; -+ } -+ -+ if (chunk == null) { -+ return 0; -+ } -+ -+ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - minLightSection]; -+ return nibble.getVisible(blockPos.getX(), y, blockPos.getZ()); -+ } -+ -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ final ChunkAccess chunk = this.getAnyChunkNow(pos.getX() >> 4, pos.getZ() >> 4); -+ -+ final int sky = this.getSkyLightValue(pos, chunk) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.getBlockLightValue(pos, chunk); -+ return Math.max(sky, block); -+ } -+ -+ public LayerLightEventListener getSkyReader() { -+ return this.skyReader; -+ } -+ -+ public LayerLightEventListener getBlockReader() { -+ return this.blockReader; -+ } -+ -+ public boolean isClientSide() { -+ return this.isClientSide; -+ } -+ -+ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { -+ if (this.world == null) { -+ // empty world -+ return null; -+ } -+ -+ final ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource(); -+ final LevelChunk fullLoaded = chunkProvider.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (fullLoaded != null) { -+ return fullLoaded; -+ } -+ -+ return chunkProvider.getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ public boolean hasUpdates() { -+ return !this.lightQueue.isEmpty(); -+ } -+ -+ public Level getWorld() { -+ return this.world; -+ } -+ -+ public LightChunkGetter getLightAccess() { -+ return this.lightAccess; -+ } -+ -+ protected final SkyStarLightEngine getSkyLightEngine() { -+ if (this.cachedSkyPropagators == null) { -+ return null; -+ } -+ final SkyStarLightEngine ret; -+ synchronized (this.cachedSkyPropagators) { -+ ret = this.cachedSkyPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new SkyStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ if (this.cachedSkyPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedSkyPropagators) { -+ this.cachedSkyPropagators.addFirst(engine); -+ } -+ } -+ -+ protected final BlockStarLightEngine getBlockLightEngine() { -+ if (this.cachedBlockPropagators == null) { -+ return null; -+ } -+ final BlockStarLightEngine ret; -+ synchronized (this.cachedBlockPropagators) { -+ ret = this.cachedBlockPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new BlockStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ if (this.cachedBlockPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedBlockPropagators) { -+ this.cachedBlockPropagators.addFirst(engine); -+ } -+ } -+ -+ public CompletableFuture blockChange(final BlockPos pos) { -+ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueBlockChange(pos); -+ } -+ -+ public CompletableFuture sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ if (this.world == null) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueSectionChange(pos, newEmptyValue); -+ } -+ -+ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, -+ final IntConsumer onComplete) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, -+ blockEngine == null ? onComplete : null); -+ } -+ if (blockEngine != null) { -+ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkChunkEdges(final int chunkX, final int chunkZ) { -+ this.checkSkyEdges(chunkX, chunkZ); -+ this.checkBlockEdges(chunkX, chunkZ); -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -+ this.lightQueue.queueChunkLighting(pos, run); -+ } -+ -+ public void removeChunkTasks(final ChunkPos pos) { -+ this.lightQueue.removeChunk(pos); -+ } -+ -+ public void propagateChanges() { -+ if (this.lightQueue.isEmpty()) { -+ return; -+ } -+ -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ LightQueue.ChunkTasks task; -+ while ((task = this.lightQueue.removeFirstTask()) != null) { -+ if (task.lightTasks != null) { -+ for (final Runnable run : task.lightTasks) { -+ run.run(); -+ } -+ } -+ -+ final long coordinate = task.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = task.changedPositions; -+ final Boolean[] sectionChanges = task.changedSectionSet; -+ -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && task.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -+ } -+ -+ task.onComplete.complete(null); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ protected static final class LightQueue { -+ -+ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ } -+ -+ public synchronized boolean isEmpty() { -+ return this.chunkTasks.isEmpty(); -+ } -+ -+ public synchronized CompletableFuture queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ tasks.changedPositions.add(pos.immutable()); -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks.onComplete; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public synchronized ChunkTasks removeFirstTask() { -+ if (this.chunkTasks.isEmpty()) { -+ return null; -+ } -+ return this.chunkTasks.removeFirst(); -+ } -+ -+ protected static final class ChunkTasks { -+ -+ public final Set changedPositions = new HashSet<>(); -+ public Boolean[] changedSectionSet; -+ public ShortOpenHashSet queuedEdgeChecksSky; -+ public ShortOpenHashSet queuedEdgeChecksBlock; -+ public List lightTasks; -+ -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ -+ public final long chunkCoordinate; -+ -+ public ChunkTasks(final long chunkCoordinate) { -+ this.chunkCoordinate = chunkCoordinate; -+ } -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..16a4a14e7ccf9e4d7fdf1166674fe8f529c06d39 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/CoordinateUtils.java -@@ -0,0 +1,128 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+ -+public final class CoordinateUtils { -+ -+ // dx, dz are relative to the target chunk -+ // dx, dz in [-radius, radius] -+ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { -+ return (dx + radius) + (2 * radius + 1)*(dz + radius); -+ } -+ -+ // the chunk keys are compatible with vanilla -+ -+ public static long getChunkKey(final BlockPos pos) { -+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final Entity entity) { -+ return ((long)(Mth.floor(entity.getZ()) >> 4) << 32) | ((Mth.floor(entity.getX()) >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final ChunkPos pos) { -+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final SectionPos pos) { -+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int getChunkX(final long chunkKey) { -+ return (int)chunkKey; -+ } -+ -+ public static int getChunkZ(final long chunkKey) { -+ return (int)(chunkKey >>> 32); -+ } -+ -+ public static int getChunkCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate) >> 4; -+ } -+ -+ // the section keys are compatible with vanilla's -+ -+ static final int SECTION_X_BITS = 22; -+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; -+ static final int SECTION_Y_BITS = 20; -+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; -+ static final int SECTION_Z_BITS = 22; -+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; -+ // format is y,z,x (in order of LSB to MSB) -+ static final int SECTION_Y_SHIFT = 0; -+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; -+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; -+ static final int SECTION_TO_BLOCK_SHIFT = 4; -+ -+ public static long getChunkSectionKey(final int x, final int y, final int z) { -+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final SectionPos pos) { -+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final ChunkPos pos, final int y) { -+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final BlockPos pos) { -+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static long getChunkSectionKey(final Entity entity) { -+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static int getChunkSectionX(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); -+ } -+ -+ public static int getChunkSectionY(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); -+ } -+ -+ public static int getChunkSectionZ(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); -+ } -+ -+ // the block coordinates are not necessarily compatible with vanilla's -+ -+ public static int getBlockCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate); -+ } -+ -+ public static long getBlockKey(final int x, final int y, final int z) { -+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); -+ } -+ -+ public static long getBlockKey(final BlockPos pos) { -+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); -+ } -+ -+ public static long getBlockKey(final Entity entity) { -+ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); -+ } -+ -+ private CoordinateUtils() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..177d0a969f3d72a34e773e8309c3719a235ee06d ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/IntegerUtil.java -@@ -0,0 +1,226 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+public final class IntegerUtil { -+ -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ // copied from hacker's delight (signed division magic value) -+ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt -+ public static long getDivisorNumbers(final int d) { -+ final int ad = 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/ca/spottedleaf/starlight/common/util/SaveUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6df9e01731d7fcbe279736b8fc18396595b95574 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/SaveUtil.java -@@ -0,0 +1,192 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import ca.spottedleaf.starlight.common.light.SWMRNibbleArray; -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import com.mojang.logging.LogUtils; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import org.slf4j.Logger; -+ -+public final class SaveUtil { -+ -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ private static final int STARLIGHT_LIGHT_VERSION = 8; -+ -+ public static int getLightVersion() { -+ return STARLIGHT_LIGHT_VERSION; -+ } -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ -+ public static void saveLightHook(final Level world, final ChunkAccess chunk, final CompoundTag nbt) { -+ try { -+ saveLightHookReal(world, chunk, nbt); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, it will have correctly set lit to false -+ // for Vanilla to relight on load and it will not set our lit tag so we will relight on load -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to inject light data into save data for chunk " + chunk.getPos() + ", chunk light will be recalculated on its next load", ex); -+ } -+ } -+ -+ private static void saveLightHookReal(final Level world, final ChunkAccess chunk, final CompoundTag tag) { -+ if (tag == null) { -+ return; -+ } -+ -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ -+ boolean lit = chunk.isLightCorrect() || !(world instanceof ServerLevel); -+ // diff start - store our tag for whether light data is init'd -+ if (lit) { -+ tag.putBoolean("isLightOn", false); -+ } -+ // diff end - store our tag for whether light data is init'd -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ -+ CompoundTag[] sections = new CompoundTag[maxSection - minSection + 1]; -+ -+ ListTag sectionsStored = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sectionsStored.size(); ++i) { -+ CompoundTag sectionStored = sectionsStored.getCompound(i); -+ int k = sectionStored.getByte("Y"); -+ -+ // strip light data -+ sectionStored.remove("BlockLight"); -+ sectionStored.remove("SkyLight"); -+ -+ if (!sectionStored.isEmpty()) { -+ sections[k - minSection] = sectionStored; -+ } -+ } -+ -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { -+ for (int i = minSection; i <= maxSection; ++i) { -+ SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (blockNibble != null || skyNibble != null) { -+ CompoundTag section = sections[i - minSection]; -+ if (section == null) { -+ section = new CompoundTag(); -+ section.putByte("Y", (byte)i); -+ sections[i - minSection] = section; -+ } -+ -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); -+ } -+ -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); -+ } -+ } -+ } -+ } -+ -+ // rewrite section list -+ sectionsStored.clear(); -+ for (CompoundTag section : sections) { -+ if (section != null) { -+ sectionsStored.add(section); -+ } -+ } -+ tag.put("sections", sectionsStored); -+ if (lit) { -+ tag.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // only mark as fully lit after we have successfully injected our data -+ } -+ } -+ -+ public static void loadLightHook(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ try { -+ loadLightHookReal(world, pos, tag, into); -+ } catch (final Throwable ex) { -+ // failing to inject is not fatal so we catch anything here. if it fails, then we simply relight. Not a problem, we get correct -+ // lighting in both cases. -+ if (ex instanceof ThreadDeath) { -+ throw (ThreadDeath)ex; -+ } -+ LOGGER.warn("Failed to load light for chunk " + pos + ", light will be recalculated", ex); -+ } -+ } -+ -+ private static void loadLightHookReal(final Level world, final ChunkPos pos, final CompoundTag tag, final ChunkAccess into) { -+ if (into == null) { -+ return; -+ } -+ final int minSection = WorldUtil.getMinLightSection(world); -+ final int maxSection = WorldUtil.getMaxLightSection(world); -+ -+ into.setLightCorrect(false); // mark as unlit in case we fail parsing -+ -+ SWMRNibbleArray[] blockNibbles = StarLightEngine.getFilledEmptyLight(world); -+ SWMRNibbleArray[] skyNibbles = StarLightEngine.getFilledEmptyLight(world); -+ -+ -+ // start copy from the original method -+ boolean lit = tag.get("isLightOn") != null && tag.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ ChunkStatus status = ChunkStatus.byName(tag.getString("Status")); -+ if (lit && status.isOrAfter(ChunkStatus.LIGHT)) { // diff - we add the status check here -+ ListTag sections = tag.getList("sections", 10); -+ -+ for (int i = 0; i < sections.size(); ++i) { -+ CompoundTag sectionData = sections.getCompound(i); -+ int y = sectionData.getByte("Y"); -+ -+ if (sectionData.contains("BlockLight", 7)) { -+ // this is where our diff is -+ blockNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); -+ } -+ -+ if (canReadSky) { -+ if (sectionData.contains("SkyLight", 7)) { -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ skyNibbles[y - minSection] = new SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ } -+ } -+ } -+ } -+ // end copy from vanilla -+ -+ into.setBlockNibbles(blockNibbles); -+ into.setSkyNibbles(skyNibbles); -+ into.setLightCorrect(lit); // now we set lit here, only after we've correctly parsed data -+ } -+ -+ private SaveUtil() {} -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad034ba50d2 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/common/util/WorldUtil.java -@@ -0,0 +1,47 @@ -+package ca.spottedleaf.starlight.common.util; -+ -+import net.minecraft.world.level.LevelHeightAccessor; -+ -+public final class WorldUtil { -+ -+ // min, max are inclusive -+ -+ public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSection() - 1; // getMaxSection() is exclusive -+ } -+ -+ public static int getMinSection(final LevelHeightAccessor world) { -+ return world.getMinSection(); -+ } -+ -+ public static int getMaxLightSection(final LevelHeightAccessor world) { -+ return getMaxSection(world) + 1; -+ } -+ -+ public static int getMinLightSection(final LevelHeightAccessor world) { -+ return getMinSection(world) - 1; -+ } -+ -+ -+ -+ public static int getTotalSections(final LevelHeightAccessor world) { -+ return getMaxSection(world) - getMinSection(world) + 1; -+ } -+ -+ public static int getTotalLightSections(final LevelHeightAccessor world) { -+ return getMaxLightSection(world) - getMinLightSection(world) + 1; -+ } -+ -+ public static int getMinBlockY(final LevelHeightAccessor world) { -+ return getMinSection(world) << 4; -+ } -+ -+ public static int getMaxBlockY(final LevelHeightAccessor world) { -+ return (getMaxSection(world) << 4) | 15; -+ } -+ -+ private WorldUtil() { -+ throw new RuntimeException(); -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -index 190df802cb24aa360f6cf4d291e38b4b3fe4a2ac..68645bbbab9b4225048b647252d8f462028a9c84 100644 ---- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -@@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.level.ThreadedLevelLightEngine; - import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.LevelChunk; - import org.bukkit.command.CommandSender; - import org.bukkit.craftbukkit.entity.CraftPlayer; -@@ -19,6 +20,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; - import org.checkerframework.framework.qual.DefaultQualifier; - - import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.BLUE; -+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; - import static net.kyori.adventure.text.format.NamedTextColor.GREEN; - import static net.kyori.adventure.text.format.NamedTextColor.RED; - -@@ -44,7 +47,7 @@ public final class FixLightCommand implements PaperSubcommand { - sender.sendMessage(text("Radius cannot be negative!", RED)); - return; - } -- final int maxRadius = 5; -+ final int maxRadius = 32; // Paper - MOOOOOORE - radius = Math.min(maxRadius, parsed); - if (radius != parsed) { - post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); -@@ -59,12 +62,67 @@ public final class FixLightCommand implements PaperSubcommand { - ServerPlayer handle = player.getHandle(); - ServerLevel world = (ServerLevel) handle.level; - ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); -+ // Paper start - rewrite light engine -+ if (true) { -+ this.starlightFixLight(handle, world, lightengine, radius, post); -+ return; -+ } -+ // Paper end - rewrite light engine - - net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); - Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); - updateLight(sender, world, lightengine, queue, post); - } - -+ // Paper start - rewrite light engine -+ private void starlightFixLight( -+ final ServerPlayer sender, -+ final ServerLevel world, -+ final ThreadedLevelLightEngine lightengine, -+ final int radius, -+ final @Nullable Runnable done -+ ) { -+ long start = System.nanoTime(); -+ java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos -+ -+ int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ ++pending[0]; -+ } -+ -+ int[] relitChunks = new int[1]; -+ lightengine.relight(chunks, -+ (ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit chunk ", BLUE), text(chunkPos.toString()), -+ text(", progress: ", BLUE), text((int) (Math.round(100.0 * (double) (relitChunks[0]) / (double) pending[0])) + "%") -+ )); -+ }, -+ (int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6 * (end - start)); -+ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( -+ text("Relit ", BLUE), text(totalRelit), -+ text(" chunks. Took ", BLUE), text(diff + "ms") -+ )); -+ if (done != null) { -+ done.run(); -+ } -+ }); -+ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); -+ } -+ // Paper end - rewrite light engine -+ - private void updateLight( - final CommandSender sender, - final ServerLevel world, -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 5b4c3ca92dffff876af18db106310cb14e8612b1..5482be03a667939ff009b6810d5cc90c8601e983 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -55,7 +55,7 @@ public class ChunkHolder { - private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- private CompletableFuture chunkToSave; -+ public CompletableFuture chunkToSave; // Paper - public - @Nullable - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index adce96d372e0f9b6c3813b2153a5dcbb32a4e75c..dc8d915956c04b3e9b596e3cb62f1a0498ef1787 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -133,7 +133,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final LongSet entitiesInLevel; - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; -- private final BlockableEventLoop mainThreadExecutor; -+ public final BlockableEventLoop mainThreadExecutor; // Paper - public - final java.util.concurrent.Executor mainInvokingExecutor; // Paper - public ChunkGenerator generator; - private RandomState randomState; -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 537d34a0325a985948c744929b90144a66a35ee3..06e4d3a02e0d1326b7029157856476db4ef3575e 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -545,7 +545,7 @@ public abstract class DistanceManager { - } - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve -+ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); - - while (objectiterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 5539f2a7e069cbe98997b734f3b1cd498148f09b..b57bffce30154b196b879209c1ce559d0b82456e 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -23,6 +23,17 @@ import net.minecraft.world.level.chunk.LightChunkGetter; - import net.minecraft.world.level.lighting.LevelLightEngine; - import org.slf4j.Logger; - -+// Paper start -+import ca.spottedleaf.starlight.common.light.StarLightEngine; -+import io.papermc.paper.util.CoordinateUtils; -+import java.util.function.Supplier; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import net.minecraft.world.level.chunk.ChunkStatus; -+// Paper end -+ - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - private static final Logger LOGGER = LogUtils.getLogger(); - private final ProcessorMailbox taskMailbox; -@@ -157,13 +168,168 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private volatile int taskPerBatch = 5; - private final AtomicBoolean scheduled = new AtomicBoolean(); - -+ // Paper start - replace light engine impl -+ protected final ca.spottedleaf.starlight.common.light.StarLightInterface theLightEngine; -+ public final boolean hasBlockLight; -+ public final boolean hasSkyLight; -+ // Paper end - replace light engine impl -+ - public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { -- super(chunkProvider, true, hasBlockLight); -+ super(chunkProvider, false, false); // Paper - destroy vanilla light engine state - this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper - this.sorterMailbox = executor; - this.taskMailbox = processor; -+ // Paper start - replace light engine impl -+ this.hasBlockLight = true; -+ this.hasSkyLight = hasBlockLight; // Nice variable name. -+ this.theLightEngine = new ca.spottedleaf.starlight.common.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); -+ // Paper end - replace light engine impl -+ } -+ -+// Paper start - replace light engine impl -+ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { -+ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ protected long relightCounter; -+ -+ public int relight(java.util.Set chunks_param, -+ java.util.function.Consumer chunkLightCallback, -+ java.util.function.IntConsumer onComplete) { -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ throw new IllegalStateException("Must only be called on the main thread"); -+ } -+ -+ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); -+ // add tickets -+ java.util.Map ticketIds = new java.util.HashMap<>(); -+ int totalChunks = 0; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final ChunkAccess chunk = (ChunkAccess)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ final Long id = Long.valueOf(this.relightCounter++); -+ -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); -+ ticketIds.put(chunkPos, id); -+ -+ ++totalChunks; -+ } -+ -+ this.taskMailbox.tell(() -> { -+ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { -+ chunkLightCallback.accept(chunkPos); -+ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null, true), false); -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); -+ }); -+ }, onComplete); -+ }); -+ this.tryScheduleUpdate(); -+ -+ return totalChunks; -+ } -+ -+ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); -+ -+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier> runnable) { -+ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); -+ -+ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -+ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing -+ // chunk scheduling, we could be lighting and generating a chunk at the same time -+ return; -+ } -+ -+ if (center.getStatus() != ChunkStatus.FULL) { -+ // do not keep chunk loaded, we are probably in a gen thread -+ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) -+ runnable.get(); -+ return; -+ } -+ -+ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { -+ // ticket logic is not safe to run off-main, re-schedule -+ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { -+ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); -+ }); -+ return; -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final CompletableFuture updateFuture = runnable.get(); -+ -+ if (updateFuture == null) { -+ // not scheduled -+ return; -+ } -+ -+ final int references = this.chunksBeingWorkedOn.addTo(key, 1); -+ if (references == 0) { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } -+ -+ // append future to this chunk and 1 radius neighbours chunk save futures -+ // this prevents us from saving the world without first waiting for the light engine -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ ChunkHolder neighbour = world.getChunkSource().chunkMap.getUpdatingChunkIfPresent(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ)); -+ if (neighbour != null) { -+ neighbour.chunkToSave = neighbour.chunkToSave.thenCombine(updateFuture, (final ChunkAccess curr, final Void ignore) -> { -+ return curr; -+ }); -+ } -+ } -+ } -+ -+ updateFuture.thenAcceptAsync((final Void ignore) -> { -+ final int newReferences = this.chunksBeingWorkedOn.get(key); -+ if (newReferences == 1) { -+ this.chunksBeingWorkedOn.remove(key); -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } else { -+ this.chunksBeingWorkedOn.put(key, newReferences - 1); -+ } -+ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { -+ if (thr != null) { -+ LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); -+ } -+ }); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // route to new light engine -+ return this.theLightEngine.hasUpdates() || !this.queue.isEmpty(); - } - -+ @Override -+ public LayerLightEventListener getLayerListener(final LightLayer lightType) { -+ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); -+ } -+ -+ @Override -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ // need to use new light hooks for this -+ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; -+ // Don't fetch the block light level if the skylight level is 15, since the value will never be higher. -+ if (sky == 15) return 15; -+ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); -+ return Math.max(sky, block); -+ } -+ // Paper end - replace light engine imp -+ - @Override - public void close() { - } -@@ -180,15 +346,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void checkBlock(BlockPos pos) { -- BlockPos blockPos = pos.immutable(); -- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.POST_UPDATE, Util.name(() -> { -- super.checkBlock(blockPos); -- }, () -> { -- return "checkBlock " + blockPos; -- })); -+ // Paper start - replace light engine impl -+ final BlockPos posCopy = pos.immutable(); -+ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { -+ return this.theLightEngine.blockChange(posCopy); -+ }); -+ // Paper end - replace light engine impl - } - - protected void updateChunkStatus(ChunkPos pos) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -211,17 +378,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void updateSectionStatus(SectionPos pos, boolean notReady) { -- this.addTask(pos.x(), pos.z(), () -> { -- return 0; -- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- super.updateSectionStatus(pos, notReady); -- }, () -> { -- return "updateSectionStatus " + pos + " " + notReady; -- })); -+ // Paper start - replace light engine impl -+ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { -+ return this.theLightEngine.sectionChange(pos, notReady); -+ }); -+ // Paper end - replace light engine impl - } - - @Override - public void enableLightSources(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - super.enableLightSources(pos, retainData); - }, () -> { -@@ -231,6 +397,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles, boolean nonEdge) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x(), pos.z(), () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -252,6 +419,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void retainData(ChunkPos pos, boolean retainData) { -+ if (true) return; // Paper - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -274,6 +442,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -+ // Paper start - replace light engine impl -+ if (true) { -+ boolean lit = excludeBlocks; -+ final ChunkPos chunkPos = chunk.getPos(); -+ -+ return CompletableFuture.supplyAsync(() -> { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -+ if (!lit) { -+ chunk.setLightCorrect(false); -+ this.theLightEngine.lightChunk(chunk, emptySections); -+ chunk.setLightCorrect(true); -+ } else { -+ this.theLightEngine.forceLoadInChunk(chunk, emptySections); -+ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -+ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -+ // catch what we miss here. -+ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -+ } -+ -+ this.chunkMap.releaseLightTicket(chunkPos); -+ return chunk; -+ }, (runnable) -> { -+ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -+ this.tryScheduleUpdate(); -+ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -+ if (throwable != null) { -+ LOGGER.error("Failed to light chunk " + chunkPos, throwable); -+ } -+ }); -+ } -+ // Paper end - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - // Paper start - //ichunkaccess.b(false); // Don't need to disable this -@@ -316,7 +515,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public void tryScheduleUpdate() { -- if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper -+ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Paper // Paper - rewrite light engine - this.taskMailbox.tell(() -> { - this.runUpdate(); - this.scheduled.set(false); -@@ -333,12 +532,12 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - if (queue.poll(pre, post)) { - pre.forEach(Runnable::run); - pre.clear(); -- super.runUpdates(Integer.MAX_VALUE, true, true); -+ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine - post.forEach(Runnable::run); - post.clear(); - } else { - // might have level updates to go still -- super.runUpdates(Integer.MAX_VALUE, true, true); -+ this.theLightEngine.propagateChanges(); // Paper - rewrite light engine - } - // Paper end - } -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 41ddcf6775f99c56cf4b13b284420061e5dd6bdc..ae46429264e6a7e5c88b6b6a41a6df4db7b3e70d 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -32,6 +32,7 @@ public class TicketType { - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper - public static final TicketType REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail -+ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Paper - ensure chunks stay loaded for lighting - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 178d9ad7525b6743038ed45c6f85686a860ffd26..24b820484497714eb8be87e07ca1d37829d4f2c9 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -701,6 +701,7 @@ public abstract class BlockBehaviour { - this.hasPostProcess = blockbase_info.hasPostProcess; - this.emissiveRendering = blockbase_info.emissiveRendering; - this.offsetType = (BlockBehaviour.OffsetType) blockbase_info.offsetType.apply(this.asState()); -+ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Paper - } - // 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; -@@ -721,6 +722,18 @@ public abstract class BlockBehaviour { - protected boolean isTicking; - protected FluidState fluid; - // Paper end -+ // Paper start -+ protected int opacityIfCached = -1; -+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] -+ public final int getOpacityIfCached() { -+ return this.opacityIfCached; -+ } -+ -+ protected final boolean conditionallyFullOpaque; -+ public final boolean isConditionallyFullOpaque() { -+ return this.conditionallyFullOpaque; -+ } -+ // Paper end - - public void initCache() { - this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() -@@ -729,6 +742,7 @@ public abstract class BlockBehaviour { - this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); - } - this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here -+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light - - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index a97909e77b9b28aede8c8716831c3f9a90618f09..b68625ebb32b8d1e5bc232d7cc791edbed923378 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -81,6 +81,47 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom - private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); - public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY); - // CraftBukkit end -+ // Paper start - rewrite light engine -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles; -+ -+ private volatile ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles; -+ -+ private volatile boolean[] skyEmptinessMap; -+ -+ private volatile boolean[] blockEmptinessMap; -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.blockNibbles; -+ } -+ -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.blockNibbles = nibbles; -+ } -+ -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.skyNibbles; -+ } -+ -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.skyNibbles = nibbles; -+ } -+ -+ public boolean[] getSkyEmptinessMap() { -+ return this.skyEmptinessMap; -+ } -+ -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.skyEmptinessMap = emptinessMap; -+ } -+ -+ public boolean[] getBlockEmptinessMap() { -+ return this.blockEmptinessMap; -+ } -+ -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.blockEmptinessMap = emptinessMap; -+ } -+ // Paper end - rewrite light engine - - public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biome, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable BlendingData blendingData) { - this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups -diff --git a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -index a9c65c8d36e5c7080133706df1363b3ce52e3370..d1b175f2bb1bc96e4f044a97b14721feb44d78f5 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/EmptyLevelChunk.java -@@ -21,6 +21,38 @@ public class EmptyLevelChunk extends LevelChunk { - this.biome = holder; - } - -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(this.getLevel()); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) {} -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) {} -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return null; -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) {} -+ - // Paper start - @Override - public BlockState getBlockState(int x, int y, int z) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -index 7b320357973202423c29743d922b72dc4ec11efe..8ffc206a858864d277ff94de7c66ffdb07d8f491 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -@@ -31,6 +31,48 @@ public class ImposterProtoChunk extends ProtoChunk { - private final LevelChunk wrapped; - private final boolean allowWrites; - -+ // Paper start - rewrite light engine -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getBlockNibbles() { -+ return this.wrapped.getBlockNibbles(); -+ } -+ -+ @Override -+ public void setBlockNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setBlockNibbles(nibbles); -+ } -+ -+ @Override -+ public ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] getSkyNibbles() { -+ return this.wrapped.getSkyNibbles(); -+ } -+ -+ @Override -+ public void setSkyNibbles(final ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] nibbles) { -+ this.wrapped.setSkyNibbles(nibbles); -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.wrapped.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setSkyEmptinessMap(emptinessMap); -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.wrapped.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ this.wrapped.setBlockEmptinessMap(emptinessMap); -+ } -+ // Paper end - rewrite light engine -+ - public ImposterProtoChunk(LevelChunk wrapped, boolean bl) { - super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.levelHeightAccessor, wrapped.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), wrapped.getBlendingData()); - this.wrapped = wrapped; -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index c85380c3bf3bf4448a28a91af78f41c235a583e4..d870cefbe5b7485f423817f4f639e3e2a304640c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -100,6 +100,10 @@ public class LevelChunk extends ChunkAccess { - - public LevelChunk(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks blockTickScheduler, LevelChunkTicks fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) { - super(pos, upgradeData, world, net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), inhabitedTime, sectionArrayInitializer, blendingData); // Paper - Anti-Xray - The world isn't ready yet, use server singleton for registry -+ // Paper start - rewrite light engine -+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); -+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); -+ // Paper end - rewrite light engine - this.tickersInLevel = Maps.newHashMap(); - this.clientLightReady = false; - this.level = (ServerLevel) world; // CraftBukkit - type -@@ -330,6 +334,12 @@ public class LevelChunk extends ChunkAccess { - - public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { - this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); -+ // Paper start - rewrite light engine -+ this.setBlockNibbles(protoChunk.getBlockNibbles()); -+ this.setSkyNibbles(protoChunk.getSkyNibbles()); -+ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); -+ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); -+ // Paper end - rewrite light engine - Iterator iterator = protoChunk.getBlockEntities().values().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 5ebde3a4f99b8d017d9a10a30fefc0b7dd011319..7908360dd47937b2cb702e381802b7b278a5198e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -203,7 +203,7 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - return this.get(this.strategy.getIndex(x, y, z)); - } - -- protected T get(int index) { -+ public T get(int index) { // Paper - public - PalettedContainer.Data data = this.data; - return data.palette.valueFor(data.storage.get(index)); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -index 9014331e4ceac9f77a911aead87bf452d29e3fb4..13b62e8e6569c154547bc0d5626488c5b0839f20 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -@@ -55,6 +55,12 @@ public class ProtoChunk extends ChunkAccess { - - public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoChunkTicks blockTickScheduler, ProtoChunkTicks fluidTickScheduler, LevelHeightAccessor world, Registry biomeRegistry, @Nullable BlendingData blendingData) { - super(pos, upgradeData, world, biomeRegistry, 0L, sections, blendingData); -+ // Paper start - rewrite light engine -+ if (!(this instanceof ImposterProtoChunk)) { -+ this.setBlockNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); -+ this.setSkyNibbles(ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world)); -+ } -+ // Paper end - rewrite light engine - this.blockTicks = blockTickScheduler; - this.fluidTicks = fluidTickScheduler; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index be9c15fe141ede1132dbe07ba4bfcf22036ab194..4df5853781a2ac89dd391374d34d9096643a2ab8 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -94,6 +94,14 @@ public class ChunkSerializer { - public static final String BLOCK_LIGHT_TAG = "BlockLight"; - public static final String SKY_LIGHT_TAG = "SkyLight"; - -+ // Paper start - replace light engine impl -+ private static final int STARLIGHT_LIGHT_VERSION = 8; -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ // Paper end - replace light engine impl -+ - public ChunkSerializer() {} - - // Paper start - guard against serializing mismatching coordinates -@@ -153,13 +161,20 @@ public class ChunkSerializer { - } - - UpgradeData chunkconverter = nbt.contains("UpgradeData", 10) ? new UpgradeData(nbt.getCompound("UpgradeData"), world) : UpgradeData.EMPTY; -- boolean flag = nbt.getBoolean("isLightOn"); -+ boolean flag = getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbt.get("isLightOn") != null && nbt.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Paper - ListTag nbttaglist = nbt.getList("sections", 10); - int i = world.getSectionsCount(); - LevelChunkSection[] achunksection = new LevelChunkSection[i]; - boolean flag1 = world.dimensionType().hasSkyLight(); - ServerChunkCache chunkproviderserver = world.getChunkSource(); - LevelLightEngine lightengine = chunkproviderserver.getLightEngine(); -+ // Paper start -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); // Paper - replace light impl -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(world); // Paper - replace light impl -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ // Paper end - Registry iregistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); - Codec>> codec = ChunkSerializer.makeBiomeCodecRW(iregistry); // CraftBukkit - read/write - boolean flag2 = false; -@@ -167,7 +182,7 @@ public class ChunkSerializer { - DataResult dataresult; - - for (int j = 0; j < nbttaglist.size(); ++j) { -- CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); -+ CompoundTag nbttagcompound1 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound1; // Paper - byte b0 = nbttagcompound1.getByte("Y"); - int k = world.getSectionIndexFromSectionY(b0); - -@@ -214,31 +229,45 @@ public class ChunkSerializer { - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); - boolean flag4 = flag1 && nbttagcompound1.contains("SkyLight", 7); - -- if (flag3 || flag4) { -- if (!flag2) { -+ // Paper start - rewrite the light engine -+ if (flag) { -+ try { -+ if ((flag3 || flag4) && !flag2) { -+ // Paper end - rewrite the light engine - tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main - lightengine.retainData(chunkPos, true); - }); // Paper - delay this task since we're executing off-main - flag2 = true; - } - -+ int y = sectionData.getByte("Y"); - if (flag3) { -- // Paper start - delay this task since we're executing off-main -- DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight").clone()); -- tasksToExecuteOnMain.add(() -> { -- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), blockLight, true); -- }); -- // Paper end - delay this task since we're executing off-main -+ // Paper start - rewrite the light engine -+ // this is where our diff is -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); - } -+ // Paper end - rewrite the light engine - - if (flag4) { -- // Paper start - delay this task since we're executing off-main -- DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight").clone()); -- tasksToExecuteOnMain.add(() -> { -- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), skyLight, true); -- }); -- // Paper end - delay this task since we're executing off-mai -+ // Paper start - rewrite the light engine -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else if (flag1) { -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); - } -+ // Paper end - rewrite the light engine -+ // Paper start - rewrite the light engine -+ } catch (Exception ex) { -+ LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex); -+ flag = false; -+ } -+ // Paper end - rewrite light engine - } - } - -@@ -267,6 +296,8 @@ public class ChunkSerializer { - }, chunkPos); - - object1 = new LevelChunk(world.getLevel(), chunkPos, chunkconverter, levelchunkticks, levelchunkticks1, l, achunksection, ChunkSerializer.postLoadChunk(world, nbt), blendingdata); -+ ((LevelChunk)object1).setBlockNibbles(blockNibbles); // Paper - replace light impl -+ ((LevelChunk)object1).setSkyNibbles(skyNibbles); // Paper - replace light impl - } else { - ProtoChunkTicks protochunkticklist = ProtoChunkTicks.load(nbt.getList("block_ticks", 10), (s) -> { - return Registry.BLOCK.getOptional(ResourceLocation.tryParse(s)); -@@ -275,6 +306,8 @@ public class ChunkSerializer { - return Registry.FLUID.getOptional(ResourceLocation.tryParse(s)); - }, chunkPos); - ProtoChunk protochunk = new ProtoChunk(chunkPos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, iregistry, blendingdata); -+ protochunk.setBlockNibbles(blockNibbles); // Paper - replace light impl -+ protochunk.setSkyNibbles(skyNibbles); // Paper - replace light impl - - object1 = protochunk; - protochunk.setInhabitedTime(l); -@@ -420,7 +453,7 @@ public class ChunkSerializer { - DataLayer[] blockLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; - DataLayer[] skyLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; - -- for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { -+ for (int i = lightenginethreaded.getMinLightSection(); false && i < lightenginethreaded.getMaxLightSection(); ++i) { // Paper - don't run loop, we don't need to - light data is per chunk now - DataLayer blockArray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i)); - DataLayer skyArray = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i)); - -@@ -478,6 +511,12 @@ public class ChunkSerializer { - } - public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) { - // Paper end -+ // Paper start - rewrite light impl -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = io.papermc.paper.util.WorldUtil.getMaxLightSection(world); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ // Paper end - rewrite light impl - ChunkPos chunkcoordintpair = chunk.getPos(); - CompoundTag nbttagcompound = new CompoundTag(); - -@@ -528,20 +567,14 @@ public class ChunkSerializer { - for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { - int j = chunk.getSectionIndexFromSectionY(i); - boolean flag1 = j >= 0 && j < achunksection.length; -- // Paper start - async chunk save for unload -- DataLayer nibblearray; // block light -- DataLayer nibblearray1; // sky light -- if (asyncsavedata == null) { -- nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) -- nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) -- } else { -- nibblearray = asyncsavedata.blockLight[i - lightenginethreaded.getMinLightSection()]; -- nibblearray1 = asyncsavedata.skyLight[i - lightenginethreaded.getMinLightSection()]; -- } -- // Paper end -+ // Paper - replace light engine - -- if (flag1 || nibblearray != null || nibblearray1 != null) { -- CompoundTag nbttagcompound1 = new CompoundTag(); -+ // Paper start - replace light engine -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ ca.spottedleaf.starlight.common.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (flag1 || blockNibble != null || skyNibble != null) { -+ // Paper end - replace light engine -+ CompoundTag nbttagcompound1 = new CompoundTag(); CompoundTag section = nbttagcompound1; // Paper - - if (flag1) { - LevelChunkSection chunksection = achunksection[j]; -@@ -556,13 +589,27 @@ public class ChunkSerializer { - nbttagcompound1.put("biomes", (Tag) dataresult1.getOrThrow(false, logger1::error)); - } - -- if (nibblearray != null && !nibblearray.isEmpty()) { -- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData()); -+ // Paper start -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); - } - -- if (nibblearray1 != null && !nibblearray1.isEmpty()) { -- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData()); -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); - } -+ // Paper end - - if (!nbttagcompound1.isEmpty()) { - nbttagcompound1.putByte("Y", (byte) i); -@@ -573,7 +620,8 @@ public class ChunkSerializer { - - nbttagcompound.put("sections", nbttaglist); - if (flag) { -- nbttagcompound.putBoolean("isLightOn", true); -+ nbttagcompound.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Paper -+ nbttagcompound.putBoolean("isLightOn", false); // Paper - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - - // Paper start diff --git a/patches/server/0790-Always-parse-protochunk-light-sources-unless-it-is-m.patch b/patches/server/0790-Always-parse-protochunk-light-sources-unless-it-is-m.patch deleted file mode 100644 index 6723907d85..0000000000 --- a/patches/server/0790-Always-parse-protochunk-light-sources-unless-it-is-m.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 23 Aug 2021 04:50:05 -0700 -Subject: [PATCH] Always parse protochunk light sources unless it is marked as - non-lit - -Chunks not marked as lit will always go through the light engine, -so they should always have their block sources parsed. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 4df5853781a2ac89dd391374d34d9096643a2ab8..3367c75b1c132b42465d4c355a6b5fd00c3efe23 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -331,16 +331,33 @@ public class ChunkSerializer { - BelowZeroRetrogen belowzeroretrogen = protochunk.getBelowZeroRetrogen(); - boolean flag5 = chunkstatus.isOrAfter(ChunkStatus.LIGHT) || belowzeroretrogen != null && belowzeroretrogen.targetStatus().isOrAfter(ChunkStatus.LIGHT); - -- if (!flag && flag5) { -- Iterator iterator = BlockPos.betweenClosed(chunkPos.getMinBlockX(), world.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), world.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ()).iterator(); -+ if (!flag) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit -+ // Paper start - let's make sure the implementation isn't as slow as possible -+ int offX = chunkPos.x << 4; -+ int offZ = chunkPos.z << 4; -+ -+ int minChunkSection = io.papermc.paper.util.WorldUtil.getMinSection(world); -+ int maxChunkSection = io.papermc.paper.util.WorldUtil.getMaxSection(world); -+ -+ LevelChunkSection[] sections = achunksection; -+ for (int sectionY = minChunkSection; sectionY <= maxChunkSection; ++sectionY) { -+ LevelChunkSection section = sections[sectionY - minChunkSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // no sources in empty sections -+ continue; -+ } -+ int offY = sectionY << 4; - -- while (iterator.hasNext()) { -- BlockPos blockposition = (BlockPos) iterator.next(); -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ if (section.states.get(index).getLightEmission() <= 0) { -+ continue; -+ } - -- if (((ChunkAccess) object1).getBlockState(blockposition).getLightEmission() != 0) { -- protochunk.addLight(blockposition); -+ // index = x | (z << 4) | (y << 8) -+ protochunk.addLight(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); - } - } -+ // Paper end - } - } - diff --git a/patches/server/0790-Fix-removing-recipes-from-RecipeIterator.patch b/patches/server/0790-Fix-removing-recipes-from-RecipeIterator.patch new file mode 100644 index 0000000000..5784fd4dc3 --- /dev/null +++ b/patches/server/0790-Fix-removing-recipes-from-RecipeIterator.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake +Date: Tue, 30 Nov 2021 12:01:56 -0800 +Subject: [PATCH] Fix removing recipes from RecipeIterator + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java +index 24f336c89b548c5ba2a95372db0d7c83b5c4ec47..91895c639c33a1cafd2a35bab7b5fd83e558468d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java +@@ -11,6 +11,7 @@ import org.bukkit.inventory.Recipe; + public class RecipeIterator implements Iterator { + private final Iterator, Object2ObjectLinkedOpenHashMap>>> recipes; + private Iterator> current; ++ private Recipe currentRecipe; // Paper - fix removing recipes + + public RecipeIterator() { + this.recipes = MinecraftServer.getServer().getRecipeManager().recipes.entrySet().iterator(); +@@ -34,10 +35,16 @@ public class RecipeIterator implements Iterator { + public Recipe next() { + if (this.current == null || !this.current.hasNext()) { + this.current = this.recipes.next().getValue().values().iterator(); +- return this.next(); ++ // Paper start - fix removing recipes ++ this.currentRecipe = this.next(); ++ return this.currentRecipe; ++ // Paper end + } + +- return this.current.next().toBukkitRecipe(); ++ // Paper start - fix removing recipes ++ this.currentRecipe = this.current.next().toBukkitRecipe(); ++ return this.currentRecipe; ++ // Paper end + } + + @Override +@@ -46,6 +53,11 @@ public class RecipeIterator implements Iterator { + throw new IllegalStateException("next() not yet called"); + } + ++ // Paper start - fix removing recipes ++ if (this.currentRecipe instanceof org.bukkit.Keyed keyed) { ++ MinecraftServer.getServer().getRecipeManager().byName.remove(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(keyed.getKey())); ++ } ++ // Paper end + this.current.remove(); + } + } diff --git a/patches/server/0791-Fix-removing-recipes-from-RecipeIterator.patch b/patches/server/0791-Fix-removing-recipes-from-RecipeIterator.patch deleted file mode 100644 index 5784fd4dc3..0000000000 --- a/patches/server/0791-Fix-removing-recipes-from-RecipeIterator.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake -Date: Tue, 30 Nov 2021 12:01:56 -0800 -Subject: [PATCH] Fix removing recipes from RecipeIterator - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java -index 24f336c89b548c5ba2a95372db0d7c83b5c4ec47..91895c639c33a1cafd2a35bab7b5fd83e558468d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java -@@ -11,6 +11,7 @@ import org.bukkit.inventory.Recipe; - public class RecipeIterator implements Iterator { - private final Iterator, Object2ObjectLinkedOpenHashMap>>> recipes; - private Iterator> current; -+ private Recipe currentRecipe; // Paper - fix removing recipes - - public RecipeIterator() { - this.recipes = MinecraftServer.getServer().getRecipeManager().recipes.entrySet().iterator(); -@@ -34,10 +35,16 @@ public class RecipeIterator implements Iterator { - public Recipe next() { - if (this.current == null || !this.current.hasNext()) { - this.current = this.recipes.next().getValue().values().iterator(); -- return this.next(); -+ // Paper start - fix removing recipes -+ this.currentRecipe = this.next(); -+ return this.currentRecipe; -+ // Paper end - } - -- return this.current.next().toBukkitRecipe(); -+ // Paper start - fix removing recipes -+ this.currentRecipe = this.current.next().toBukkitRecipe(); -+ return this.currentRecipe; -+ // Paper end - } - - @Override -@@ -46,6 +53,11 @@ public class RecipeIterator implements Iterator { - throw new IllegalStateException("next() not yet called"); - } - -+ // Paper start - fix removing recipes -+ if (this.currentRecipe instanceof org.bukkit.Keyed keyed) { -+ MinecraftServer.getServer().getRecipeManager().byName.remove(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(keyed.getKey())); -+ } -+ // Paper end - this.current.remove(); - } - } diff --git a/patches/server/0791-Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/0791-Prevent-sending-oversized-item-data-in-equipment-and.patch new file mode 100644 index 0000000000..63ed303a2b --- /dev/null +++ b/patches/server/0791-Prevent-sending-oversized-item-data-in-equipment-and.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 1 Dec 2021 12:36:25 +0100 +Subject: [PATCH] Prevent sending oversized item data in equipment and metadata + + +diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +index 0c79613597e9ed1fbeeb36e9cb60a70bbda17bb9..79593d42ef881aa96eab7ea1e50683fa48ff4896 100644 +--- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java ++++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +@@ -38,7 +38,7 @@ public class EntityDataSerializers { + public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { + @Override + public void write(FriendlyByteBuf buf, ItemStack value) { +- buf.writeItem(value); ++ buf.writeItem(net.minecraft.world.entity.LivingEntity.sanitizeItemStack(value, false)); // Paper - prevent oversized data + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index cc418554b655ea4111631e4a1abf69776e150e7c..319dfa82dff1fe188a52bed5aa2d39575853b793 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -309,7 +309,10 @@ public class ServerEntity { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + + if (!itemstack.isEmpty()) { +- list.add(Pair.of(enumitemslot, itemstack.copy())); ++ // Paper start - prevent oversized data ++ final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); ++ list.add(Pair.of(enumitemslot, sanitized)); ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 8ac7d25e6e1a22829d6c45522409763bbb1328a0..e7560eb298b563231407de832a81bb2c97e3c4cf 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3134,7 +3134,10 @@ public abstract class LivingEntity extends Entity { + equipmentChanges.forEach((enumitemslot, itemstack) -> { + ItemStack itemstack1 = itemstack.copy(); + +- list.add(Pair.of(enumitemslot, itemstack1)); ++ // Paper start - prevent oversized data ++ ItemStack toSend = sanitizeItemStack(itemstack1, true); ++ list.add(Pair.of(enumitemslot, toSend)); ++ // Paper end + switch (enumitemslot.getType()) { + case HAND: + this.setLastHandItem(enumitemslot, itemstack1); +@@ -3147,6 +3150,34 @@ public abstract class LivingEntity extends Entity { + ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper start - prevent oversized data ++ public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || !itemStack.hasTag()) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ final CompoundTag tag = copy.getTag(); ++ if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { ++ // Bundles change their texture based on their fullness. ++ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta(); ++ int sizeUsed = 0; ++ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { ++ int scale = 64 / item.getMaxStackSize(); ++ sizeUsed += scale * item.getAmount(); ++ } ++ // Now we add a single fake item that uses the same amount of slots as all other items. ++ ListTag items = new ListTag(); ++ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); ++ tag.put("Items", items); ++ } ++ if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) { ++ blockEntityTag.remove("Items"); ++ } ++ return copy; ++ } ++ // Paper end ++ + private ItemStack getLastArmorItem(EquipmentSlot slot) { + return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex()); + } diff --git a/patches/server/0792-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0792-Hide-unnecessary-itemmeta-from-clients.patch new file mode 100644 index 0000000000..9442632b57 --- /dev/null +++ b/patches/server/0792-Hide-unnecessary-itemmeta-from-clients.patch @@ -0,0 +1,107 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Tue, 3 Aug 2021 17:28:27 +0200 +Subject: [PATCH] Hide unnecessary itemmeta from clients + + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 319dfa82dff1fe188a52bed5aa2d39575853b793..919758363c7b703cb200582768e68c97ce5c807a 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -311,7 +311,7 @@ public class ServerEntity { + if (!itemstack.isEmpty()) { + // Paper start - prevent oversized data + final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); +- list.add(Pair.of(enumitemslot, sanitized)); ++ list.add(Pair.of(enumitemslot, ((LivingEntity) this.entity).stripMeta(sanitized, false))); // Paper - remove unnecessary item meta + // Paper end + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 678c0b1c37b16e405205933a16f0d2d29359fd12..1d024bfbada440c93b1174568feaa67544f7f0d2 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2816,8 +2816,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // Paper end + // SPIGOT-7136 - Allays +- if (entity instanceof Allay) { +- ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); ++ if (entity instanceof Allay allay) { // Paper ++ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, allay.stripMeta(allay.getItemBySlot(slot), true))).collect(Collectors.toList()))); // Paper - remove unnecessary item meta + player.containerMenu.sendAllDataToRemote(); + } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index e7560eb298b563231407de832a81bb2c97e3c4cf..d6ecdeab398d7bfde3d760ada0374245f03014b3 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3136,7 +3136,7 @@ public abstract class LivingEntity extends Entity { + + // Paper start - prevent oversized data + ItemStack toSend = sanitizeItemStack(itemstack1, true); +- list.add(Pair.of(enumitemslot, toSend)); ++ list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - hide unnecessary item meta + // Paper end + switch (enumitemslot.getType()) { + case HAND: +@@ -3150,6 +3150,59 @@ public abstract class LivingEntity extends Entity { + ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper start - hide unnecessary item meta ++ public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ if (level.paperConfig().anticheat.obfuscation.items.hideDurability) { ++ // Only show damage values for elytra's, since they show a different texture when broken. ++ if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) { ++ copy.setDamageValue(0); ++ } ++ } ++ ++ if (level.paperConfig().anticheat.obfuscation.items.hideItemmeta) { ++ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, ++ // we'll tell the client if there's one or (more than) two items. ++ copy.setCount(copy.getCount() > 1 ? 2 : 1); ++ // We can't just strip out display, leather helmets still use the display.color tag. ++ final CompoundTag tag = copy.getTag(); ++ if (tag != null) { ++ if (tag.get("display") instanceof CompoundTag displayTag) { ++ displayTag.remove("Lore"); ++ displayTag.remove("Name"); ++ } ++ ++ if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { ++ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. ++ ListTag enchantments = new ListTag(); ++ CompoundTag fakeEnchantment = new CompoundTag(); ++ // Soul speed boots generate client side particles. ++ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SOUL_SPEED, itemStack) > 0) { ++ fakeEnchantment.putString("id", org.bukkit.enchantments.Enchantment.SOUL_SPEED.getKey().asString()); ++ fakeEnchantment.putInt("lvl", 1); ++ } ++ enchantments.add(fakeEnchantment); ++ tag.put("Enchantments", enchantments); ++ } ++ tag.remove("AttributeModifiers"); ++ ++ // Books ++ tag.remove("author"); ++ tag.remove("filtered_title"); ++ tag.remove("pages"); ++ tag.remove("filtered_pages"); ++ tag.remove("title"); ++ tag.remove("generation"); ++ } ++ } ++ return copy; ++ } ++ // Paper end ++ + // Paper start - prevent oversized data + public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { + if (itemStack.isEmpty() || !itemStack.hasTag()) { diff --git a/patches/server/0792-Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/0792-Prevent-sending-oversized-item-data-in-equipment-and.patch deleted file mode 100644 index ec1d45e5e6..0000000000 --- a/patches/server/0792-Prevent-sending-oversized-item-data-in-equipment-and.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 1 Dec 2021 12:36:25 +0100 -Subject: [PATCH] Prevent sending oversized item data in equipment and metadata - - -diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -index 0c79613597e9ed1fbeeb36e9cb60a70bbda17bb9..79593d42ef881aa96eab7ea1e50683fa48ff4896 100644 ---- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -+++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -@@ -38,7 +38,7 @@ public class EntityDataSerializers { - public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { - @Override - public void write(FriendlyByteBuf buf, ItemStack value) { -- buf.writeItem(value); -+ buf.writeItem(net.minecraft.world.entity.LivingEntity.sanitizeItemStack(value, false)); // Paper - prevent oversized data - } - - @Override -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index cc418554b655ea4111631e4a1abf69776e150e7c..319dfa82dff1fe188a52bed5aa2d39575853b793 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -309,7 +309,10 @@ public class ServerEntity { - ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); - - if (!itemstack.isEmpty()) { -- list.add(Pair.of(enumitemslot, itemstack.copy())); -+ // Paper start - prevent oversized data -+ final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); -+ list.add(Pair.of(enumitemslot, sanitized)); -+ // Paper end - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 4091253c84b5a722a75fa6dc8f5f0002b29ebf8e..d8522072adb93d47fdd7cfa947132c23c2386952 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3112,7 +3112,10 @@ public abstract class LivingEntity extends Entity { - equipmentChanges.forEach((enumitemslot, itemstack) -> { - ItemStack itemstack1 = itemstack.copy(); - -- list.add(Pair.of(enumitemslot, itemstack1)); -+ // Paper start - prevent oversized data -+ ItemStack toSend = sanitizeItemStack(itemstack1, true); -+ list.add(Pair.of(enumitemslot, toSend)); -+ // Paper end - switch (enumitemslot.getType()) { - case HAND: - this.setLastHandItem(enumitemslot, itemstack1); -@@ -3125,6 +3128,34 @@ public abstract class LivingEntity extends Entity { - ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); - } - -+ // Paper start - prevent oversized data -+ public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { -+ if (itemStack.isEmpty() || !itemStack.hasTag()) { -+ return itemStack; -+ } -+ -+ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; -+ final CompoundTag tag = copy.getTag(); -+ if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { -+ // Bundles change their texture based on their fullness. -+ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta(); -+ int sizeUsed = 0; -+ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { -+ int scale = 64 / item.getMaxStackSize(); -+ sizeUsed += scale * item.getAmount(); -+ } -+ // Now we add a single fake item that uses the same amount of slots as all other items. -+ ListTag items = new ListTag(); -+ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); -+ tag.put("Items", items); -+ } -+ if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) { -+ blockEntityTag.remove("Items"); -+ } -+ return copy; -+ } -+ // Paper end -+ - private ItemStack getLastArmorItem(EquipmentSlot slot) { - return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex()); - } diff --git a/patches/server/0793-Fix-kelp-modifier-changing-growth-for-other-crops.patch b/patches/server/0793-Fix-kelp-modifier-changing-growth-for-other-crops.patch new file mode 100644 index 0000000000..60f87f5973 --- /dev/null +++ b/patches/server/0793-Fix-kelp-modifier-changing-growth-for-other-crops.patch @@ -0,0 +1,108 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 3 Dec 2021 17:09:24 -0800 +Subject: [PATCH] Fix kelp modifier changing growth for other crops + +Also add growth modifiers for twisting vines, weeping vines, cave vines, +and glow berries + +Also fix above-mentioned modifiers from having the reverse effect + +Co-authored-by: Jake Potrebic + +diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +index 7d9056f9d841fbbdeaf1e323d818f2f1267b47e8..4940e101250874111e9c55aeb5b87b28602246f0 100644 +--- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +@@ -47,9 +47,17 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl + + @Override + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { +- return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, random.nextFloat() < 0.11F); ++ // Paper start ++ return this.getGrowIntoState(state, random, null); + } + ++ @Override ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F); ++ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value); ++ } ++ // Paper end ++ + @Override + public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) { + return new ItemStack(Items.GLOW_BERRIES); +diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +index ae9052efc48dc05c7b41cb18c4330d7e62839a07..4d1e1cf4c541793492a02681087a6242e7977acd 100644 +--- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +@@ -40,16 +40,36 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { +- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < (100.0D / world.spigotConfig.kelpModifier) * this.growPerTickProbability) { // Spigot ++ // Paper start ++ final int modifier; ++ if (state.is(Blocks.TWISTING_VINES) || state.is(Blocks.TWISTING_VINES_PLANT)) { ++ modifier = world.spigotConfig.twistingVinesModifier; ++ } else if (state.is(Blocks.WEEPING_VINES) || state.is(Blocks.WEEPING_VINES_PLANT)) { ++ modifier = world.spigotConfig.weepingVinesModifier; ++ } else if (state.is(Blocks.CAVE_VINES) || state.is(Blocks.CAVE_VINES_PLANT)) { ++ modifier = world.spigotConfig.caveVinesModifier; ++ } else if (state.is(Blocks.KELP) || state.is(Blocks.KELP_PLANT)) { ++ modifier = world.spigotConfig.kelpModifier; ++ } else { ++ modifier = 100; // Above cases are exhaustive as of 1.18 ++ } ++ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < (modifier / 100.0D) * this.growPerTickProbability) { // Spigot // Paper - fix growth modifier having the reverse effect ++ // Paper end + BlockPos blockposition1 = pos.relative(this.growthDirection); + + if (this.canGrowInto(world.getBlockState(blockposition1))) { +- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random)); // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper + } + } + + } + ++ // Paper start ++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { ++ return this.getGrowIntoState(state, random); ++ } ++ // Paper end ++ + protected BlockState getGrowIntoState(BlockState state, RandomSource random) { + return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE); + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index 40984144a062230fd45cc6c707b03e5cd7d89efc..cf96f9fdc4ae561f01d44503b9851c60140e4ea7 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -103,6 +103,12 @@ public class SpigotWorldConfig + public int bambooModifier; + public int sweetBerryModifier; + public int kelpModifier; ++ // Paper start ++ public int twistingVinesModifier; ++ public int weepingVinesModifier; ++ public int caveVinesModifier; ++ public int glowBerryModifier; ++ // Paper end + private int getAndValidateGrowth(String crop) + { + int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 ); +@@ -133,6 +139,12 @@ public class SpigotWorldConfig + this.bambooModifier = this.getAndValidateGrowth( "Bamboo" ); + this.sweetBerryModifier = this.getAndValidateGrowth( "SweetBerry" ); + this.kelpModifier = this.getAndValidateGrowth( "Kelp" ); ++ // Paper start ++ this.twistingVinesModifier = this.getAndValidateGrowth("TwistingVines"); ++ this.weepingVinesModifier = this.getAndValidateGrowth("WeepingVines"); ++ this.caveVinesModifier = this.getAndValidateGrowth("CaveVines"); ++ this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); ++ // Paper end + } + + public double itemMerge; diff --git a/patches/server/0793-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0793-Hide-unnecessary-itemmeta-from-clients.patch deleted file mode 100644 index 46c41e8b92..0000000000 --- a/patches/server/0793-Hide-unnecessary-itemmeta-from-clients.patch +++ /dev/null @@ -1,107 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Tue, 3 Aug 2021 17:28:27 +0200 -Subject: [PATCH] Hide unnecessary itemmeta from clients - - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 319dfa82dff1fe188a52bed5aa2d39575853b793..919758363c7b703cb200582768e68c97ce5c807a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -311,7 +311,7 @@ public class ServerEntity { - if (!itemstack.isEmpty()) { - // Paper start - prevent oversized data - final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); -- list.add(Pair.of(enumitemslot, sanitized)); -+ list.add(Pair.of(enumitemslot, ((LivingEntity) this.entity).stripMeta(sanitized, false))); // Paper - remove unnecessary item meta - // Paper end - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 97f1b8585c0fa6f1abbec0a0172157c61b6d0614..3e8284838eecb3c71410dcaf684cf8727265d2b6 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2812,8 +2812,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // Paper end - // SPIGOT-7136 - Allays -- if (entity instanceof Allay) { -- ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); -+ if (entity instanceof Allay allay) { // Paper -+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, allay.stripMeta(allay.getItemBySlot(slot), true))).collect(Collectors.toList()))); // Paper - remove unnecessary item meta - player.containerMenu.sendAllDataToRemote(); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d8522072adb93d47fdd7cfa947132c23c2386952..8dc54061802f0253193bda79bded1d5265591519 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3114,7 +3114,7 @@ public abstract class LivingEntity extends Entity { - - // Paper start - prevent oversized data - ItemStack toSend = sanitizeItemStack(itemstack1, true); -- list.add(Pair.of(enumitemslot, toSend)); -+ list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - hide unnecessary item meta - // Paper end - switch (enumitemslot.getType()) { - case HAND: -@@ -3128,6 +3128,59 @@ public abstract class LivingEntity extends Entity { - ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); - } - -+ // Paper start - hide unnecessary item meta -+ public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { -+ if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) { -+ return itemStack; -+ } -+ -+ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; -+ if (level.paperConfig().anticheat.obfuscation.items.hideDurability) { -+ // Only show damage values for elytra's, since they show a different texture when broken. -+ if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) { -+ copy.setDamageValue(0); -+ } -+ } -+ -+ if (level.paperConfig().anticheat.obfuscation.items.hideItemmeta) { -+ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, -+ // we'll tell the client if there's one or (more than) two items. -+ copy.setCount(copy.getCount() > 1 ? 2 : 1); -+ // We can't just strip out display, leather helmets still use the display.color tag. -+ final CompoundTag tag = copy.getTag(); -+ if (tag != null) { -+ if (tag.get("display") instanceof CompoundTag displayTag) { -+ displayTag.remove("Lore"); -+ displayTag.remove("Name"); -+ } -+ -+ if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { -+ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. -+ ListTag enchantments = new ListTag(); -+ CompoundTag fakeEnchantment = new CompoundTag(); -+ // Soul speed boots generate client side particles. -+ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SOUL_SPEED, itemStack) > 0) { -+ fakeEnchantment.putString("id", org.bukkit.enchantments.Enchantment.SOUL_SPEED.getKey().asString()); -+ fakeEnchantment.putInt("lvl", 1); -+ } -+ enchantments.add(fakeEnchantment); -+ tag.put("Enchantments", enchantments); -+ } -+ tag.remove("AttributeModifiers"); -+ -+ // Books -+ tag.remove("author"); -+ tag.remove("filtered_title"); -+ tag.remove("pages"); -+ tag.remove("filtered_pages"); -+ tag.remove("title"); -+ tag.remove("generation"); -+ } -+ } -+ return copy; -+ } -+ // Paper end -+ - // Paper start - prevent oversized data - public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { - if (itemStack.isEmpty() || !itemStack.hasTag()) { diff --git a/patches/server/0794-Fix-kelp-modifier-changing-growth-for-other-crops.patch b/patches/server/0794-Fix-kelp-modifier-changing-growth-for-other-crops.patch deleted file mode 100644 index 60f87f5973..0000000000 --- a/patches/server/0794-Fix-kelp-modifier-changing-growth-for-other-crops.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Fri, 3 Dec 2021 17:09:24 -0800 -Subject: [PATCH] Fix kelp modifier changing growth for other crops - -Also add growth modifiers for twisting vines, weeping vines, cave vines, -and glow berries - -Also fix above-mentioned modifiers from having the reverse effect - -Co-authored-by: Jake Potrebic - -diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -index 7d9056f9d841fbbdeaf1e323d818f2f1267b47e8..4940e101250874111e9c55aeb5b87b28602246f0 100644 ---- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -@@ -47,9 +47,17 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl - - @Override - protected BlockState getGrowIntoState(BlockState state, RandomSource random) { -- return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, random.nextFloat() < 0.11F); -+ // Paper start -+ return this.getGrowIntoState(state, random, null); - } - -+ @Override -+ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { -+ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F); -+ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value); -+ } -+ // Paper end -+ - @Override - public ItemStack getCloneItemStack(BlockGetter world, BlockPos pos, BlockState state) { - return new ItemStack(Items.GLOW_BERRIES); -diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -index ae9052efc48dc05c7b41cb18c4330d7e62839a07..4d1e1cf4c541793492a02681087a6242e7977acd 100644 ---- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -@@ -40,16 +40,36 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < (100.0D / world.spigotConfig.kelpModifier) * this.growPerTickProbability) { // Spigot -+ // Paper start -+ final int modifier; -+ if (state.is(Blocks.TWISTING_VINES) || state.is(Blocks.TWISTING_VINES_PLANT)) { -+ modifier = world.spigotConfig.twistingVinesModifier; -+ } else if (state.is(Blocks.WEEPING_VINES) || state.is(Blocks.WEEPING_VINES_PLANT)) { -+ modifier = world.spigotConfig.weepingVinesModifier; -+ } else if (state.is(Blocks.CAVE_VINES) || state.is(Blocks.CAVE_VINES_PLANT)) { -+ modifier = world.spigotConfig.caveVinesModifier; -+ } else if (state.is(Blocks.KELP) || state.is(Blocks.KELP_PLANT)) { -+ modifier = world.spigotConfig.kelpModifier; -+ } else { -+ modifier = 100; // Above cases are exhaustive as of 1.18 -+ } -+ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < (modifier / 100.0D) * this.growPerTickProbability) { // Spigot // Paper - fix growth modifier having the reverse effect -+ // Paper end - BlockPos blockposition1 = pos.relative(this.growthDirection); - - if (this.canGrowInto(world.getBlockState(blockposition1))) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random)); // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - } - } - - } - -+ // Paper start -+ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) { -+ return this.getGrowIntoState(state, random); -+ } -+ // Paper end -+ - protected BlockState getGrowIntoState(BlockState state, RandomSource random) { - return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE); - } -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 40984144a062230fd45cc6c707b03e5cd7d89efc..cf96f9fdc4ae561f01d44503b9851c60140e4ea7 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -103,6 +103,12 @@ public class SpigotWorldConfig - public int bambooModifier; - public int sweetBerryModifier; - public int kelpModifier; -+ // Paper start -+ public int twistingVinesModifier; -+ public int weepingVinesModifier; -+ public int caveVinesModifier; -+ public int glowBerryModifier; -+ // Paper end - private int getAndValidateGrowth(String crop) - { - int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 ); -@@ -133,6 +139,12 @@ public class SpigotWorldConfig - this.bambooModifier = this.getAndValidateGrowth( "Bamboo" ); - this.sweetBerryModifier = this.getAndValidateGrowth( "SweetBerry" ); - this.kelpModifier = this.getAndValidateGrowth( "Kelp" ); -+ // Paper start -+ this.twistingVinesModifier = this.getAndValidateGrowth("TwistingVines"); -+ this.weepingVinesModifier = this.getAndValidateGrowth("WeepingVines"); -+ this.caveVinesModifier = this.getAndValidateGrowth("CaveVines"); -+ this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); -+ // Paper end - } - - public double itemMerge; diff --git a/patches/server/0794-Prevent-ContainerOpenersCounter-openCount-from-going.patch b/patches/server/0794-Prevent-ContainerOpenersCounter-openCount-from-going.patch new file mode 100644 index 0000000000..62a65ff130 --- /dev/null +++ b/patches/server/0794-Prevent-ContainerOpenersCounter-openCount-from-going.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Tue, 30 Nov 2021 05:30:35 +0000 +Subject: [PATCH] Prevent ContainerOpenersCounter openCount from going negative + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java +index ba06184bad7e8ae55cb2d55f6196e8f990d2811d..3e4b3eecc788c564f81b7929bfab7d2fdb6e307d 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java +@@ -64,6 +64,7 @@ public abstract class ContainerOpenersCounter { + + public void decrementOpeners(Player player, Level world, BlockPos pos, BlockState state) { + int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added ++ if (this.openCount == 0) return; // Paper + int i = this.openCount--; + + // CraftBukkit start - Call redstone event diff --git a/patches/server/0795-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0795-Add-PlayerItemFrameChangeEvent.patch new file mode 100644 index 0000000000..f9ce7fc78c --- /dev/null +++ b/patches/server/0795-Add-PlayerItemFrameChangeEvent.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SamB440 +Date: Mon, 15 Nov 2021 18:10:10 +0000 +Subject: [PATCH] Add PlayerItemFrameChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +index ac0fe310b01506b3b2a767d47f65cefb57f58c7d..d2a77b4ca343d19e1c70afe3f3906a9bd53d0eec 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +@@ -3,6 +3,7 @@ package net.minecraft.world.entity.decoration; + import com.mojang.logging.LogUtils; + import java.util.OptionalInt; + import javax.annotation.Nullable; ++import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper + import net.minecraft.core.BlockPos; + import net.minecraft.core.Direction; + import net.minecraft.nbt.CompoundTag; +@@ -187,6 +188,13 @@ public class ItemFrame extends HangingEntity { + return true; + } + // CraftBukkit end ++ // Paper start - call PlayerItemFrameChangeEvent ++ if (source.getEntity() instanceof Player player) { ++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE); ++ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false); ++ } ++ // Paper end + this.dropItem(source.getEntity(), false); + this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F); + } +@@ -451,13 +459,22 @@ public class ItemFrame extends HangingEntity { + return InteractionResult.FAIL; + } + } +- +- this.setItem(itemstack); ++ // Paper start - call PlayerItemFrameChangeEvent ++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE); ++ if (!event.callEvent()) return InteractionResult.FAIL; ++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack())); ++ // this.setItem(itemstack); ++ // Paper end + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + } + } else { ++ // Paper start - call PlayerItemFrameChangeEvent ++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE); ++ if (!event.callEvent()) return InteractionResult.FAIL; ++ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false); ++ // Paper end + this.playSound(this.getRotateItemSound(), 1.0F, 1.0F); + this.setRotation(this.getRotation() + 1); + } diff --git a/patches/server/0795-Prevent-ContainerOpenersCounter-openCount-from-going.patch b/patches/server/0795-Prevent-ContainerOpenersCounter-openCount-from-going.patch deleted file mode 100644 index 62a65ff130..0000000000 --- a/patches/server/0795-Prevent-ContainerOpenersCounter-openCount-from-going.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Tue, 30 Nov 2021 05:30:35 +0000 -Subject: [PATCH] Prevent ContainerOpenersCounter openCount from going negative - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java -index ba06184bad7e8ae55cb2d55f6196e8f990d2811d..3e4b3eecc788c564f81b7929bfab7d2fdb6e307d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java -@@ -64,6 +64,7 @@ public abstract class ContainerOpenersCounter { - - public void decrementOpeners(Player player, Level world, BlockPos pos, BlockState state) { - int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added -+ if (this.openCount == 0) return; // Paper - int i = this.openCount--; - - // CraftBukkit start - Call redstone event diff --git a/patches/server/0796-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0796-Add-PlayerItemFrameChangeEvent.patch deleted file mode 100644 index f9ce7fc78c..0000000000 --- a/patches/server/0796-Add-PlayerItemFrameChangeEvent.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SamB440 -Date: Mon, 15 Nov 2021 18:10:10 +0000 -Subject: [PATCH] Add PlayerItemFrameChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index ac0fe310b01506b3b2a767d47f65cefb57f58c7d..d2a77b4ca343d19e1c70afe3f3906a9bd53d0eec 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -3,6 +3,7 @@ package net.minecraft.world.entity.decoration; - import com.mojang.logging.LogUtils; - import java.util.OptionalInt; - import javax.annotation.Nullable; -+import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.nbt.CompoundTag; -@@ -187,6 +188,13 @@ public class ItemFrame extends HangingEntity { - return true; - } - // CraftBukkit end -+ // Paper start - call PlayerItemFrameChangeEvent -+ if (source.getEntity() instanceof Player player) { -+ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE); -+ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change -+ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false); -+ } -+ // Paper end - this.dropItem(source.getEntity(), false); - this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F); - } -@@ -451,13 +459,22 @@ public class ItemFrame extends HangingEntity { - return InteractionResult.FAIL; - } - } -- -- this.setItem(itemstack); -+ // Paper start - call PlayerItemFrameChangeEvent -+ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE); -+ if (!event.callEvent()) return InteractionResult.FAIL; -+ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack())); -+ // this.setItem(itemstack); -+ // Paper end - if (!player.getAbilities().instabuild) { - itemstack.shrink(1); - } - } - } else { -+ // Paper start - call PlayerItemFrameChangeEvent -+ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE); -+ if (!event.callEvent()) return InteractionResult.FAIL; -+ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false); -+ // Paper end - this.playSound(this.getRotateItemSound(), 1.0F, 1.0F); - this.setRotation(this.getRotation() + 1); - } diff --git a/patches/server/0796-Add-player-health-update-API.patch b/patches/server/0796-Add-player-health-update-API.patch new file mode 100644 index 0000000000..96480e59ca --- /dev/null +++ b/patches/server/0796-Add-player-health-update-API.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SamB440 +Date: Wed, 17 Nov 2021 12:31:42 +0000 +Subject: [PATCH] Add player health update API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 5cdb599c6e460672ed0fe15d5c2a9d60ad22c2e3..8882651847fc9a8d2e4222f10eb389b553da48ca 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2142,9 +2142,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().maxHealthCache = getMaxHealth(); + } + +- public void sendHealthUpdate() { ++ // Paper start ++ @Override ++ public void sendHealthUpdate(final double health, final int foodLevel, final float saturationLevel) { + // Paper start - cancellable death event +- ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel()); ++ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket((float) health, foodLevel, saturationLevel); + if (this.getHandle().queueHealthUpdatePacket) { + this.getHandle().queuedHealthUpdatePacket = packet; + } else { +@@ -2152,7 +2154,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + // Paper end + } +- ++ ++ @Override ++ public void sendHealthUpdate() { ++ this.sendHealthUpdate(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel()); ++ } ++ // Paper end ++ + public void injectScaledMaxHealth(Collection collection, boolean force) { + if (!this.scaledHealth && !force) { + return; diff --git a/patches/server/0797-Add-player-health-update-API.patch b/patches/server/0797-Add-player-health-update-API.patch deleted file mode 100644 index 96480e59ca..0000000000 --- a/patches/server/0797-Add-player-health-update-API.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SamB440 -Date: Wed, 17 Nov 2021 12:31:42 +0000 -Subject: [PATCH] Add player health update API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 5cdb599c6e460672ed0fe15d5c2a9d60ad22c2e3..8882651847fc9a8d2e4222f10eb389b553da48ca 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2142,9 +2142,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().maxHealthCache = getMaxHealth(); - } - -- public void sendHealthUpdate() { -+ // Paper start -+ @Override -+ public void sendHealthUpdate(final double health, final int foodLevel, final float saturationLevel) { - // Paper start - cancellable death event -- ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel()); -+ ClientboundSetHealthPacket packet = new ClientboundSetHealthPacket((float) health, foodLevel, saturationLevel); - if (this.getHandle().queueHealthUpdatePacket) { - this.getHandle().queuedHealthUpdatePacket = packet; - } else { -@@ -2152,7 +2154,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - // Paper end - } -- -+ -+ @Override -+ public void sendHealthUpdate() { -+ this.sendHealthUpdate(this.getScaledHealth(), this.getHandle().getFoodData().getFoodLevel(), this.getHandle().getFoodData().getSaturationLevel()); -+ } -+ // Paper end -+ - public void injectScaledMaxHealth(Collection collection, boolean force) { - if (!this.scaledHealth && !force) { - return; diff --git a/patches/server/0797-Optimize-HashMapPalette.patch b/patches/server/0797-Optimize-HashMapPalette.patch new file mode 100644 index 0000000000..ef1880895f --- /dev/null +++ b/patches/server/0797-Optimize-HashMapPalette.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: stonar96 +Date: Sun, 17 Jan 2021 01:11:36 +0100 +Subject: [PATCH] Optimize HashMapPalette + +HashMapPalette uses an instance of CrudeIncrementalIntIdentityHashBiMap +internally. A Palette has a preset maximum size = 1 << bits. +CrudeIncrementalIntIdentityHashBiMap has an initial size but is +automatically resized. The CrudeIncrementalIntIdentityHashBiMap is created +with the maximum size in the constructor of HashMapPalette, with the aim +that it doesn't need to be resized anymore. However, there are two things +that I think Mojang hasn't considered here: +1) The CrudeIncrementalIntIdentityHashBiMap is resized, when its initial +size is reached and not the next time, when a further object is added. +2) HashMapPalette adds objects (unnecessarily) before checking if the +initial size of CrudeIncrementalIntIdentityHashBiMap is reached. +This means to actually avoid resize operations in +CrudeIncrementalIntIdentityHashBiMap, one has to add 2 to the initial size +or add 1 and check the size before adding objects. This commit implements +the second approach. Note that this isn't only an optimization but also +makes async reads of Palettes fail-safe. An async read while the +CrudeIncrementalIntIdentityHashBiMap is resized is fatal and can even lead +to corrupted data. This is also something that Anti-Xray is currently +relying on. + +diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +index c5551c31597f5a7fe62ade325e36425e5f2df0b9..ba9b0f419b0785a0b1e3bc57f18bfe5edaa192bd 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java ++++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java +@@ -19,7 +19,7 @@ public class HashMapPalette implements Palette { + } + + public HashMapPalette(IdMap idList, int indexBits, PaletteResize listener) { +- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits)); ++ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap + } + + private HashMapPalette(IdMap idMap, int i, PaletteResize paletteResize, CrudeIncrementalIntIdentityHashBiMap crudeIncrementalIntIdentityHashBiMap) { +@@ -37,10 +37,16 @@ public class HashMapPalette implements Palette { + public int idFor(T object) { + int i = this.values.getId(object); + if (i == -1) { +- i = this.values.add(object); +- if (i >= 1 << this.bits) { ++ // Paper start - Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize ++ // We use size() instead of the result from add(K) ++ // This avoids adding another object unnecessarily ++ // Without this change, + 2 would be required in the constructor ++ if (this.values.size() >= 1 << this.bits) { + i = this.resizeHandler.onResize(this.bits + 1, object); ++ } else { ++ i = this.values.add(object); + } ++ // Paper end + } + + return i; diff --git a/patches/server/0798-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server/0798-Allow-delegation-to-vanilla-chunk-gen.patch new file mode 100644 index 0000000000..22aa1ee9b7 --- /dev/null +++ b/patches/server/0798-Allow-delegation-to-vanilla-chunk-gen.patch @@ -0,0 +1,129 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Wed, 29 Apr 2020 02:10:32 +0200 +Subject: [PATCH] Allow delegation to vanilla chunk gen + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4adacf6f849fe41918690fb8f195727a9c880b53..96ad0b334b2985c295be4f20df06e6eb73fbb22f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2329,6 +2329,90 @@ public final class CraftServer implements Server { + return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY), world); // Paper - Anti-Xray - Add parameters + } + ++ // Paper start ++ private static final List VANILLA_GEN_STATUSES = List.of( ++ net.minecraft.world.level.chunk.ChunkStatus.EMPTY, ++ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_STARTS, ++ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_REFERENCES, ++ net.minecraft.world.level.chunk.ChunkStatus.BIOMES, ++ net.minecraft.world.level.chunk.ChunkStatus.NOISE, ++ net.minecraft.world.level.chunk.ChunkStatus.SURFACE, ++ net.minecraft.world.level.chunk.ChunkStatus.CARVERS, ++ net.minecraft.world.level.chunk.ChunkStatus.LIQUID_CARVERS, ++ net.minecraft.world.level.chunk.ChunkStatus.FEATURES, ++ net.minecraft.world.level.chunk.ChunkStatus.LIGHT ++ ); ++ ++ @Override ++ @Deprecated(forRemoval = true) ++ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { ++ // do bunch of vanilla shit ++ final net.minecraft.server.level.ServerLevel serverLevel = ((CraftWorld) world).getHandle(); ++ final net.minecraft.core.Registry biomeRegistry = serverLevel.getServer().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); ++ final net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk( ++ new net.minecraft.world.level.ChunkPos(x, z), ++ net.minecraft.world.level.chunk.UpgradeData.EMPTY, ++ serverLevel, ++ biomeRegistry, ++ null ++ ); ++ ++ final net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator; ++ if (serverLevel.chunkSource.getGenerator() instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator bukkit) { ++ chunkGenerator = bukkit.delegate; ++ } else { ++ chunkGenerator = serverLevel.chunkSource.getGenerator(); ++ } ++ ++ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(x, z); ++ final net.minecraft.util.thread.ProcessorMailbox mailbox = net.minecraft.util.thread.ProcessorMailbox.create( ++ net.minecraft.Util.backgroundExecutor(), ++ "CraftServer#createVanillaChunkData(worldName='" + world.getName() + "', x='" + x + "', z='" + z + "')" ++ ); ++ for (final net.minecraft.world.level.chunk.ChunkStatus chunkStatus : VANILLA_GEN_STATUSES) { ++ final List chunks = Lists.newArrayList(); ++ final int statusRange = Math.max(1, chunkStatus.getRange()); ++ ++ for (int zz = chunkPos.z - statusRange; zz <= chunkPos.z + statusRange; ++zz) { ++ for (int xx = chunkPos.x - statusRange; xx <= chunkPos.x + statusRange; ++xx) { ++ if (xx == chunkPos.x && zz == chunkPos.z) { ++ chunks.add(protoChunk); ++ } else { ++ final net.minecraft.core.Holder biomeHolder = serverLevel.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ final net.minecraft.world.level.chunk.ChunkAccess chunk = new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, new net.minecraft.world.level.ChunkPos(xx, zz), biomeHolder); ++ chunks.add(chunk); ++ } ++ } ++ } ++ ++ chunkStatus.generate( ++ mailbox::tell, ++ serverLevel, ++ chunkGenerator, ++ serverLevel.getStructureManager(), ++ serverLevel.chunkSource.getLightEngine(), ++ chunk -> { ++ throw new UnsupportedOperationException("Not creating full chunks here"); ++ }, ++ chunks, ++ true ++ ).thenAccept(either -> { ++ if (chunkStatus == net.minecraft.world.level.chunk.ChunkStatus.NOISE) { ++ either.left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, net.minecraft.world.level.chunk.ChunkStatus.POST_FEATURES)); ++ } ++ }).join(); ++ } ++ ++ // get empty object ++ OldCraftChunkData data = (OldCraftChunkData) this.createChunkData(world); ++ // copy over generated sections ++ data.getLights().addAll(protoChunk.getLights().toList()); ++ 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/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +index 4a23d03757e1735b9ebb8c003adcc0374a7d672d..ce006e1d6c38e5b0bdb336c480fb9d291292f75c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java +@@ -23,7 +23,7 @@ import org.bukkit.material.MaterialData; + public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + private final int minHeight; + private final int maxHeight; +- private final LevelChunkSection[] sections; ++ private LevelChunkSection[] sections; // Paper + private final Registry biomes; + private Set tiles; + private final Set lights = new HashSet<>(); +@@ -194,7 +194,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { + return this.tiles; + } + +- Set getLights() { ++ public Set getLights() { // Paper + return this.lights; + } ++ ++ // Paper start ++ public void setRawChunkData(LevelChunkSection[] sections) { ++ this.sections = sections; ++ } ++ // Paper end + } diff --git a/patches/server/0798-Optimize-HashMapPalette.patch b/patches/server/0798-Optimize-HashMapPalette.patch deleted file mode 100644 index ef1880895f..0000000000 --- a/patches/server/0798-Optimize-HashMapPalette.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: stonar96 -Date: Sun, 17 Jan 2021 01:11:36 +0100 -Subject: [PATCH] Optimize HashMapPalette - -HashMapPalette uses an instance of CrudeIncrementalIntIdentityHashBiMap -internally. A Palette has a preset maximum size = 1 << bits. -CrudeIncrementalIntIdentityHashBiMap has an initial size but is -automatically resized. The CrudeIncrementalIntIdentityHashBiMap is created -with the maximum size in the constructor of HashMapPalette, with the aim -that it doesn't need to be resized anymore. However, there are two things -that I think Mojang hasn't considered here: -1) The CrudeIncrementalIntIdentityHashBiMap is resized, when its initial -size is reached and not the next time, when a further object is added. -2) HashMapPalette adds objects (unnecessarily) before checking if the -initial size of CrudeIncrementalIntIdentityHashBiMap is reached. -This means to actually avoid resize operations in -CrudeIncrementalIntIdentityHashBiMap, one has to add 2 to the initial size -or add 1 and check the size before adding objects. This commit implements -the second approach. Note that this isn't only an optimization but also -makes async reads of Palettes fail-safe. An async read while the -CrudeIncrementalIntIdentityHashBiMap is resized is fatal and can even lead -to corrupted data. This is also something that Anti-Xray is currently -relying on. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -index c5551c31597f5a7fe62ade325e36425e5f2df0b9..ba9b0f419b0785a0b1e3bc57f18bfe5edaa192bd 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -+++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java -@@ -19,7 +19,7 @@ public class HashMapPalette implements Palette { - } - - public HashMapPalette(IdMap idList, int indexBits, PaletteResize listener) { -- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits)); -+ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap - } - - private HashMapPalette(IdMap idMap, int i, PaletteResize paletteResize, CrudeIncrementalIntIdentityHashBiMap crudeIncrementalIntIdentityHashBiMap) { -@@ -37,10 +37,16 @@ public class HashMapPalette implements Palette { - public int idFor(T object) { - int i = this.values.getId(object); - if (i == -1) { -- i = this.values.add(object); -- if (i >= 1 << this.bits) { -+ // Paper start - Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize -+ // We use size() instead of the result from add(K) -+ // This avoids adding another object unnecessarily -+ // Without this change, + 2 would be required in the constructor -+ if (this.values.size() >= 1 << this.bits) { - i = this.resizeHandler.onResize(this.bits + 1, object); -+ } else { -+ i = this.values.add(object); - } -+ // Paper end - } - - return i; diff --git a/patches/server/0799-Allow-delegation-to-vanilla-chunk-gen.patch b/patches/server/0799-Allow-delegation-to-vanilla-chunk-gen.patch deleted file mode 100644 index b7371b996d..0000000000 --- a/patches/server/0799-Allow-delegation-to-vanilla-chunk-gen.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MiniDigger -Date: Wed, 29 Apr 2020 02:10:32 +0200 -Subject: [PATCH] Allow delegation to vanilla chunk gen - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7f874c53cdb1815e3337c51ab564b7dafb20c939..fe0b8b623b8853aaa4fd0a105f502cfebb763dfb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2329,6 +2329,90 @@ public final class CraftServer implements Server { - return new OldCraftChunkData(world.getMinHeight(), world.getMaxHeight(), handle.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY), world); // Paper - Anti-Xray - Add parameters - } - -+ // Paper start -+ private static final List VANILLA_GEN_STATUSES = List.of( -+ net.minecraft.world.level.chunk.ChunkStatus.EMPTY, -+ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_STARTS, -+ net.minecraft.world.level.chunk.ChunkStatus.STRUCTURE_REFERENCES, -+ net.minecraft.world.level.chunk.ChunkStatus.BIOMES, -+ net.minecraft.world.level.chunk.ChunkStatus.NOISE, -+ net.minecraft.world.level.chunk.ChunkStatus.SURFACE, -+ net.minecraft.world.level.chunk.ChunkStatus.CARVERS, -+ net.minecraft.world.level.chunk.ChunkStatus.LIQUID_CARVERS, -+ net.minecraft.world.level.chunk.ChunkStatus.FEATURES, -+ net.minecraft.world.level.chunk.ChunkStatus.LIGHT -+ ); -+ -+ @Override -+ @Deprecated(forRemoval = true) -+ public ChunkGenerator.ChunkData createVanillaChunkData(World world, int x, int z) { -+ // do bunch of vanilla shit -+ final net.minecraft.server.level.ServerLevel serverLevel = ((CraftWorld) world).getHandle(); -+ final net.minecraft.core.Registry biomeRegistry = serverLevel.getServer().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); -+ final net.minecraft.world.level.chunk.ProtoChunk protoChunk = new net.minecraft.world.level.chunk.ProtoChunk( -+ new net.minecraft.world.level.ChunkPos(x, z), -+ net.minecraft.world.level.chunk.UpgradeData.EMPTY, -+ serverLevel, -+ biomeRegistry, -+ null -+ ); -+ -+ final net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator; -+ if (serverLevel.chunkSource.getGenerator() instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator bukkit) { -+ chunkGenerator = bukkit.delegate; -+ } else { -+ chunkGenerator = serverLevel.chunkSource.getGenerator(); -+ } -+ -+ final net.minecraft.world.level.ChunkPos chunkPos = new net.minecraft.world.level.ChunkPos(x, z); -+ final net.minecraft.util.thread.ProcessorMailbox mailbox = net.minecraft.util.thread.ProcessorMailbox.create( -+ net.minecraft.Util.backgroundExecutor(), -+ "CraftServer#createVanillaChunkData(worldName='" + world.getName() + "', x='" + x + "', z='" + z + "')" -+ ); -+ for (final net.minecraft.world.level.chunk.ChunkStatus chunkStatus : VANILLA_GEN_STATUSES) { -+ final List chunks = Lists.newArrayList(); -+ final int statusRange = Math.max(1, chunkStatus.getRange()); -+ -+ for (int zz = chunkPos.z - statusRange; zz <= chunkPos.z + statusRange; ++zz) { -+ for (int xx = chunkPos.x - statusRange; xx <= chunkPos.x + statusRange; ++xx) { -+ if (xx == chunkPos.x && zz == chunkPos.z) { -+ chunks.add(protoChunk); -+ } else { -+ final net.minecraft.core.Holder biomeHolder = serverLevel.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = new net.minecraft.world.level.chunk.EmptyLevelChunk(serverLevel, new net.minecraft.world.level.ChunkPos(xx, zz), biomeHolder); -+ chunks.add(chunk); -+ } -+ } -+ } -+ -+ chunkStatus.generate( -+ mailbox::tell, -+ serverLevel, -+ chunkGenerator, -+ serverLevel.getStructureManager(), -+ serverLevel.chunkSource.getLightEngine(), -+ chunk -> { -+ throw new UnsupportedOperationException("Not creating full chunks here"); -+ }, -+ chunks, -+ true -+ ).thenAccept(either -> { -+ if (chunkStatus == net.minecraft.world.level.chunk.ChunkStatus.NOISE) { -+ either.left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, net.minecraft.world.level.chunk.ChunkStatus.POST_FEATURES)); -+ } -+ }).join(); -+ } -+ -+ // get empty object -+ OldCraftChunkData data = (OldCraftChunkData) this.createChunkData(world); -+ // copy over generated sections -+ data.getLights().addAll(protoChunk.getLights().toList()); -+ 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/OldCraftChunkData.java b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -index 4a23d03757e1735b9ebb8c003adcc0374a7d672d..ce006e1d6c38e5b0bdb336c480fb9d291292f75c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/OldCraftChunkData.java -@@ -23,7 +23,7 @@ import org.bukkit.material.MaterialData; - public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - private final int minHeight; - private final int maxHeight; -- private final LevelChunkSection[] sections; -+ private LevelChunkSection[] sections; // Paper - private final Registry biomes; - private Set tiles; - private final Set lights = new HashSet<>(); -@@ -194,7 +194,13 @@ public final class OldCraftChunkData implements ChunkGenerator.ChunkData { - return this.tiles; - } - -- Set getLights() { -+ public Set getLights() { // Paper - return this.lights; - } -+ -+ // Paper start -+ public void setRawChunkData(LevelChunkSection[] sections) { -+ this.sections = sections; -+ } -+ // Paper end - } diff --git a/patches/server/0799-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch b/patches/server/0799-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch new file mode 100644 index 0000000000..25285e3f66 --- /dev/null +++ b/patches/server/0799-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch @@ -0,0 +1,2122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 4 May 2020 10:06:24 -0700 +Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and + collisions + + +diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java +index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644 +--- a/src/main/java/io/papermc/paper/util/CachedLists.java ++++ b/src/main/java/io/papermc/paper/util/CachedLists.java +@@ -1,8 +1,57 @@ + package io.papermc.paper.util; + ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.phys.AABB; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.UnsafeList; ++import java.util.List; ++ + public final class CachedLists { + +- public static void reset() { ++ // Paper start - optimise collisions ++ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); ++ static boolean tempCollisionListInUse; ++ ++ public static UnsafeList getTempCollisionList() { ++ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempCollisionListInUse = true; ++ return TEMP_COLLISION_LIST; ++ } ++ ++ public static void returnTempCollisionList(List list) { ++ if (list != TEMP_COLLISION_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempCollisionListInUse = false; ++ } + ++ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); ++ static boolean tempGetEntitiesListInUse; ++ ++ public static UnsafeList getTempGetEntitiesList() { ++ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { ++ return new UnsafeList<>(16); ++ } ++ tempGetEntitiesListInUse = true; ++ return TEMP_GET_ENTITIES_LIST; ++ } ++ ++ public static void returnTempGetEntitiesList(List list) { ++ if (list != TEMP_GET_ENTITIES_LIST) { ++ return; ++ } ++ ((UnsafeList)list).setSize(0); ++ tempGetEntitiesListInUse = false; ++ } ++ // Paper end - optimise collisions ++ ++ public static void reset() { ++ // Paper start - optimise collisions ++ TEMP_COLLISION_LIST.completeReset(); ++ TEMP_GET_ENTITIES_LIST.completeReset(); ++ // Paper end - optimise collisions + } + } +diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a87f6380b2c387fb0cdd40d5087b5c93492e3c88 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java +@@ -0,0 +1,899 @@ ++package io.papermc.paper.util; ++ ++import io.papermc.paper.voxel.AABBVoxelShape; ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerChunkCache; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.WorldGenRegion; ++import net.minecraft.util.Mth; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.item.Item; ++import net.minecraft.world.level.CollisionGetter; ++import net.minecraft.world.level.EntityGetter; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.border.WorldBorder; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++import net.minecraft.world.level.chunk.PalettedContainer; ++import net.minecraft.world.level.material.FlowingFluid; ++import net.minecraft.world.level.material.FluidState; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.phys.shapes.ArrayVoxelShape; ++import net.minecraft.world.phys.shapes.CollisionContext; ++import net.minecraft.world.phys.shapes.EntityCollisionContext; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.List; ++import java.util.function.BiPredicate; ++import java.util.function.Predicate; ++ ++public final class CollisionUtil { ++ ++ public static final double COLLISION_EPSILON = 1.0E-7; ++ ++ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty ++ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube ++ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info ++ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions ++ ++ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { ++ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON; ++ } ++ ++ public static boolean isEmpty(final AABB aabb) { ++ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; ++ } ++ ++ public static boolean isEmpty(final double minX, final double minY, final double minZ, ++ final double maxX, final double maxY, final double maxZ) { ++ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON; ++ } ++ ++ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { ++ double x = (double)(chunkX << 4); ++ double z = (double)(chunkZ << 4); ++ // use a bounding box bigger than the chunk to prevent entities from entering it on move ++ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, ++ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false); ++ } ++ ++ /* ++ A couple of rules for VoxelShape collisions: ++ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement ++ checks. ++ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite ++ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code ++ will automatically round it to 0. ++ */ ++ ++ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1, ++ final double maxY1, final double maxZ1, final double minX2, final double minY2, ++ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) { ++ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON && ++ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON && ++ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; ++ } ++ ++ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, ++ final double maxX, final double maxY, final double maxZ) { ++ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && ++ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && ++ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; ++ } ++ ++ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { ++ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && ++ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && ++ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; ++ } ++ ++ public static double collideX(final AABB target, final AABB source, final double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public static double collideY(final AABB target, final AABB source, final double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && ++ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public static double collideZ(final AABB target, final AABB source, final double source_move) { ++ if (source_move == 0.0) { ++ return 0.0; ++ } ++ ++ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && ++ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { ++ if (source_move >= 0.0) { ++ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision ++ if (max_move < -COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.min(max_move, source_move); ++ } else { ++ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision ++ if (max_move > COLLISION_EPSILON) { ++ return source_move; ++ } ++ return Math.max(max_move, source_move); ++ } ++ } ++ return source_move; ++ } ++ ++ public static AABB offsetX(final AABB box, final double dx) { ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB offsetY(final AABB box, final double dy) { ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB offsetZ(final AABB box, final double dz) { ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 ++ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 ++ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false); ++ } ++ ++ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 ++ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); ++ } ++ ++ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 ++ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false); ++ } ++ ++ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 ++ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false); ++ } ++ ++ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 ++ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false); ++ } ++ ++ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ final AABB target = potentialCollisions.get(i); ++ value = collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ final AABB target = potentialCollisions.get(i); ++ value = collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ final AABB target = potentialCollisions.get(i); ++ value = collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performCollisionsY(axisalignedbb, y, potentialCollisions); ++ if (y != 0.0) { ++ axisalignedbb = offsetY(axisalignedbb, y); ++ } ++ } ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new Vec3(x, y, z); ++ } ++ ++ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List list) { ++ if (shape instanceof AABBVoxelShape) { ++ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; ++ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) { ++ list.add(shapeCasted.aabb); ++ return true; ++ } ++ return false; ++ } else if (shape instanceof ArrayVoxelShape) { ++ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; ++ // this can be optimised by checking an "overall shape" first, but not needed ++ ++ final double offX = shapeCasted.getOffsetX(); ++ final double offY = shapeCasted.getOffsetY(); ++ final double offZ = shapeCasted.getOffsetZ(); ++ ++ boolean ret = false; ++ ++ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { ++ final double minX, minY, minZ, maxX, maxY, maxZ; ++ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ, ++ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ) ++ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) { ++ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false)); ++ ret = true; ++ } ++ } ++ ++ return ret; ++ } else { ++ final List boxes = shape.toAabbs(); ++ ++ boolean ret = false; ++ ++ for (int i = 0, len = boxes.size(); i < len; ++i) { ++ final AABB box = boxes.get(i); ++ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) { ++ list.add(box); ++ ret = true; ++ } ++ } ++ ++ return ret; ++ } ++ } ++ ++ public static void addBoxesTo(final VoxelShape shape, final List list) { ++ if (shape instanceof AABBVoxelShape) { ++ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; ++ if (!isEmpty(shapeCasted.aabb)) { ++ list.add(shapeCasted.aabb); ++ } ++ } else if (shape instanceof ArrayVoxelShape) { ++ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; ++ ++ final double offX = shapeCasted.getOffsetX(); ++ final double offY = shapeCasted.getOffsetY(); ++ final double offZ = shapeCasted.getOffsetZ(); ++ ++ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { ++ final AABB box = boundingBox.move(offX, offY, offZ); ++ if (!isEmpty(box)) { ++ list.add(box); ++ } ++ } ++ } else { ++ final List boxes = shape.toAabbs(); ++ for (int i = 0, len = boxes.size(); i < len; ++i) { ++ final AABB box = boxes.get(i); ++ if (!isEmpty(box)) { ++ list.add(box); ++ } ++ } ++ } ++ } ++ ++ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) { ++ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, ++ final double boxMinZ, final double boxMaxZ) { ++ final double borderMinX = worldborder.getMinX(); // -X ++ final double borderMaxX = worldborder.getMaxX(); // +X ++ ++ final double borderMinZ = worldborder.getMinZ(); // -Z ++ final double borderMaxZ = worldborder.getMaxZ(); // +Z ++ ++ return ++ // Not intersecting if we're smaller ++ !voxelShapeIntersect( ++ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON, ++ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON, ++ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ ++ ) ++ && ++ ++ // Are intersecting if we're larger ++ voxelShapeIntersect( ++ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON, ++ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON, ++ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ ++ ); ++ } ++ ++ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) { ++ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, ++ final double boxMinZ, final double boxMaxZ) { ++ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X ++ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X ++ ++ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z ++ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z ++ ++ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ; ++ } ++ ++ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb, ++ final List into, final boolean loadChunks, final boolean collidesWithUnloaded, ++ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { ++ boolean ret = false; ++ ++ if (checkBorder) { ++ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); ++ ret = true; ++ } ++ } ++ } ++ ++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; ++ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; ++ ++ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; ++ ++ final int minSection = WorldUtil.getMinSection(getter); ++ final int maxSection = WorldUtil.getMaxSection(getter); ++ final int minBlock = minSection << 4; ++ final int maxBlock = (maxSection << 4) | 15; ++ ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ CollisionContext collisionShape = null; ++ ++ // special cases: ++ if (minBlockY > maxBlock || maxBlockY < minBlock) { ++ // no point in checking ++ return ret; ++ } ++ ++ final int minYIterate = Math.max(minBlock, minBlockY); ++ final int maxYIterate = Math.min(maxBlock, maxBlockY); ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkYIterate = minYIterate >> 4; ++ final int maxChunkYIterate = maxYIterate >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; ++ ++ final ServerChunkCache chunkProvider; ++ if (getter instanceof WorldGenRegion) { ++ chunkProvider = null; ++ } else if (getter instanceof ServerLevel) { ++ chunkProvider = ((ServerLevel)getter).getChunkSource(); ++ } else { ++ chunkProvider = null; ++ } ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk ++ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk ++ ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk ++ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk ++ ++ final int chunkXGlobalPos = currChunkX << 4; ++ final int chunkZGlobalPos = currChunkZ << 4; ++ final ChunkAccess chunk; ++ if (chunkProvider == null) { ++ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ); ++ } else { ++ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); ++ } ++ ++ if (chunk == null) { ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ ++ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) { ++ final LevelChunkSection section = sections[currChunkY - minSection]; ++ if (section == null || section.hasOnlyAir()) { ++ // empty ++ continue; ++ } ++ final PalettedContainer blocks = section.states; ++ ++ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk ++ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk ++ final int chunkYGlobalPos = currChunkY << 4; ++ ++ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks(); ++ ++ final int minXIterate; ++ final int maxXIterate; ++ final int minZIterate; ++ final int maxZIterate; ++ final int minYIterateLocal; ++ final int maxYIterateLocal; ++ ++ if (!sectionHasSpecial) { ++ minXIterate = currChunkX == minChunkX ? minX + 1 : minX; ++ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX; ++ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ; ++ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ; ++ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY; ++ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY; ++ if (minXIterate > maxXIterate || minZIterate > maxZIterate) { ++ continue; ++ } ++ } else { ++ minXIterate = minX; ++ maxXIterate = maxX; ++ minZIterate = minZ; ++ maxZIterate = maxZ; ++ minYIterateLocal = minY; ++ maxYIterateLocal = maxY; ++ } ++ ++ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) { ++ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15); ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ, ++ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) { ++ // From getKnownBlockInfoHorizontalRaw: ++ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1 ++ // the even localZ is the lower 32 bits, the odd is the upper 32 bits ++ // We want to use a bitset to only iterate over non-empty blocks. ++ // We need to build a bitset mask to and out the other collisions we just don't care at all about ++ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis ++ // It's important to note that the iterate values can be outside [0, 15], but if they are, ++ // then none of the x or z loops would meet their conditions. So we can assume they are never ++ // out of bounds here ++ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32 ++ long bitset = (1L << xAxisBits) - 1; ++ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd) ++ int shift = (currZ & 1) << 5; // this will be a LEFT shift ++ // Now we need to offset shift so that the bitset first position is at minXIterate ++ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ... ++ ++ // all done ++ bitset = bitset << shift; ++ if ((collisionForHorizontal & bitset) == 0L) { ++ // All empty ++ continue; ++ } ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8); ++ ++ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal); ++ ++ switch (blockInfo) { ++ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: { ++ continue; ++ } ++ case (int) CollisionUtil.KNOWN_FULL_BLOCK: { ++ double blockX = (double)(currX | chunkXGlobalPos); ++ double blockY = (double)(currY | chunkYGlobalPos); ++ double blockZ = (double)(currZ | chunkZGlobalPos); ++ final AABB blockBox = new AABB( ++ blockX, blockY, blockZ, ++ blockX + 1.0, blockY + 1.0, blockZ + 1.0, ++ true ++ ); ++ if (predicate != null) { ++ if (!voxelShapeIntersect(aabb, blockBox)) { ++ continue; ++ } ++ // fall through to get the block for the predicate ++ } else { ++ if (voxelShapeIntersect(aabb, blockBox)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(blockBox); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ } ++ // default: fall through to standard logic ++ } ++ ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY | chunkYGlobalPos; ++ int blockZ = currZ | chunkZGlobalPos; ++ ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ BlockState blockData = blocks.get(localBlockIndex); ++ ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (collisionShape == null) { ++ collisionShape = new LazyEntityCollisionContext(entity); ++ } ++ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); ++ if (voxelshape2 != Shapes.empty()) { ++ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ ++ if (checkOnly) { ++ if (voxelshape3.intersects(aabb)) { ++ return true; ++ } ++ } else { ++ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getCollisionsForBlocksOrWorldBorderReference(final CollisionGetter getter, final Entity entity, final AABB aabb, ++ final List into, final boolean loadChunks, final boolean collidesWithUnloaded, ++ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { ++ boolean ret = false; ++ ++ if (checkBorder) { ++ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); ++ ret = true; ++ } ++ } ++ } ++ ++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; ++ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; ++ ++ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; ++ ++ final int minSection = WorldUtil.getMinSection(getter); ++ final int maxSection = WorldUtil.getMaxSection(getter); ++ final int minBlock = minSection << 4; ++ final int maxBlock = (maxSection << 4) | 15; ++ ++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); ++ CollisionContext collisionShape = null; ++ ++ // special cases: ++ if (minBlockY > maxBlock || maxBlockY < minBlock) { ++ // no point in checking ++ return ret; ++ } ++ ++ final int minYIterate = Math.max(minBlock, minBlockY); ++ final int maxYIterate = Math.min(maxBlock, maxBlockY); ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkYIterate = minYIterate >> 4; ++ final int maxChunkYIterate = maxYIterate >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; ++ ++ final ServerChunkCache chunkProvider; ++ if (getter instanceof WorldGenRegion) { ++ chunkProvider = null; ++ } else if (getter instanceof ServerLevel) { ++ chunkProvider = ((ServerLevel)getter).getChunkSource(); ++ } else { ++ chunkProvider = null; ++ } ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk ++ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk ++ ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk ++ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk ++ ++ final int chunkXGlobalPos = currChunkX << 4; ++ final int chunkZGlobalPos = currChunkZ << 4; ++ final ChunkAccess chunk; ++ if (chunkProvider == null) { ++ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ); ++ } else { ++ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); ++ } ++ ++ if (chunk == null) { ++ if (collidesWithUnloaded) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ final LevelChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) { ++ final LevelChunkSection section = sections[currChunkY - minSection]; ++ if (section == null || section.hasOnlyAir()) { ++ // empty ++ continue; ++ } ++ final PalettedContainer blocks = section.states; ++ ++ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk ++ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk ++ final int chunkYGlobalPos = currChunkY << 4; ++ ++ for (int currY = minY; currY <= maxY; ++currY) { ++ for (int currZ = minZ; currZ <= maxZ; ++currZ) { ++ for (int currX = minX; currX <= maxX; ++currX) { ++ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); ++ int blockX = currX | chunkXGlobalPos; ++ int blockY = currY | chunkYGlobalPos; ++ int blockZ = currZ | chunkZGlobalPos; ++ ++ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ BlockState blockData = blocks.get(localBlockIndex); ++ if (blockData.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) { ++ continue; ++ } ++ ++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (collisionShape == null) { ++ collisionShape = new LazyEntityCollisionContext(entity); ++ } ++ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); ++ if (voxelshape2 != Shapes.empty()) { ++ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (predicate != null && !predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ ++ if (checkOnly) { ++ if (voxelshape3.intersects(aabb)) { ++ return true; ++ } ++ } else { ++ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb, ++ final List into, final boolean checkOnly, final Predicate predicate) { ++ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) { ++ return false; ++ } ++ ++ boolean ret = false; ++ ++ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with. ++ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems ++ // specifically with boat collisions. ++ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); ++ final List entities = CachedLists.getTempGetEntitiesList(); ++ try { ++ if (entity != null && entity.hardCollides()) { ++ entityGetter.getEntities(entity, aabb, predicate, entities); ++ } else { ++ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities); ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(otherEntity.getBoundingBox()); ++ ret = true; ++ } ++ } ++ } ++ } finally { ++ CachedLists.returnTempGetEntitiesList(entities); ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb, ++ final List into, final boolean loadChunks, final boolean collidesWithUnloadedChunks, ++ final boolean checkBorder, final boolean checkOnly, final BiPredicate blockPredicate, ++ final Predicate entityPredicate) { ++ if (checkOnly) { ++ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) ++ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); ++ } else { ++ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) ++ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); ++ } ++ } ++ ++ public static final class LazyEntityCollisionContext extends EntityCollisionContext { ++ ++ private CollisionContext delegate; ++ ++ public LazyEntityCollisionContext(final Entity entity) { ++ super(false, 0.0, null, null, entity); ++ } ++ ++ public CollisionContext getDelegate() { ++ final Entity entity = this.getEntity(); ++ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; ++ } ++ ++ @Override ++ public boolean isDescending() { ++ return this.getDelegate().isDescending(); ++ } ++ ++ @Override ++ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { ++ return this.getDelegate().isAbove(shape, pos, defaultValue); ++ } ++ ++ @Override ++ public boolean isHoldingItem(final Item item) { ++ return this.getDelegate().isHoldingItem(item); ++ } ++ ++ @Override ++ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { ++ return this.getDelegate().canStandOnFluid(state, fluidState); ++ } ++ } ++ ++ private CollisionUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d67a40e7be030142443680c89e1763fc9ecdfe0a +--- /dev/null ++++ b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java +@@ -0,0 +1,200 @@ ++package io.papermc.paper.voxel; ++ ++import io.papermc.paper.util.CollisionUtil; ++import it.unimi.dsi.fastutil.doubles.DoubleArrayList; ++import it.unimi.dsi.fastutil.doubles.DoubleList; ++import net.minecraft.core.Direction; ++import net.minecraft.world.phys.AABB; ++import net.minecraft.world.phys.shapes.Shapes; ++import net.minecraft.world.phys.shapes.VoxelShape; ++import java.util.ArrayList; ++import java.util.List; ++ ++public final class AABBVoxelShape extends VoxelShape { ++ ++ public final AABB aabb; ++ ++ public AABBVoxelShape(AABB aabb) { ++ super(Shapes.getFullUnoptimisedCube().shape); ++ this.aabb = aabb; ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return CollisionUtil.isEmpty(this.aabb); ++ } ++ ++ @Override ++ public double min(Direction.Axis enumdirection_enumaxis) { ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.aabb.minX; ++ case 1: ++ return this.aabb.minY; ++ case 2: ++ return this.aabb.minZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public double max(Direction.Axis enumdirection_enumaxis) { ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.aabb.maxX; ++ case 1: ++ return this.aabb.maxY; ++ case 2: ++ return this.aabb.maxZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public AABB bounds() { ++ return this.aabb; ++ } ++ ++ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis. ++ @Override ++ protected double get(Direction.Axis enumdirection_enumaxis, int i) { ++ switch (enumdirection_enumaxis.ordinal() | (i << 2)) { ++ case (0 | (0 << 2)): ++ return this.aabb.minX; ++ case (1 | (0 << 2)): ++ return this.aabb.minY; ++ case (2 | (0 << 2)): ++ return this.aabb.minZ; ++ case (0 | (1 << 2)): ++ return this.aabb.maxX; ++ case (1 | (1 << 2)): ++ return this.aabb.maxY; ++ case (2 | (1 << 2)): ++ return this.aabb.maxZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ private DoubleList cachedListX; ++ private DoubleList cachedListY; ++ private DoubleList cachedListZ; ++ ++ @Override ++ protected DoubleList getCoords(Direction.Axis enumdirection_enumaxis) { ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX; ++ case 1: ++ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY; ++ case 2: ++ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public VoxelShape move(double d0, double d1, double d2) { ++ return new AABBVoxelShape(this.aabb.move(d0, d1, d2)); ++ } ++ ++ @Override ++ public VoxelShape optimize() { ++ if (this.isEmpty()) { ++ return Shapes.empty(); ++ } else if (this == Shapes.BLOCK_OPTIMISED || this.aabb.equals(Shapes.BLOCK_OPTIMISED.aabb)) { ++ return Shapes.BLOCK_OPTIMISED; ++ } ++ return this; ++ } ++ ++ @Override ++ public void forAllBoxes(Shapes.DoubleLineConsumer voxelshapes_a) { ++ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ); ++ } ++ ++ @Override ++ public List toAabbs() { // getAABBs ++ List ret = new ArrayList<>(1); ++ ret.add(this.aabb); ++ return ret; ++ } ++ ++ @Override ++ protected int findIndex(Direction.Axis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1; ++ case 1: ++ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1; ++ case 2: ++ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1; ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ protected VoxelShape calculateFace(Direction direction) { ++ if (this.isEmpty()) { ++ return Shapes.empty(); ++ } ++ if (this == Shapes.BLOCK_OPTIMISED) { ++ return this; ++ } ++ switch (direction) { ++ case EAST: // +X ++ case WEST: { // -X ++ final double from = direction == Direction.EAST ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; ++ if (from > this.aabb.maxX || this.aabb.minX > from) { ++ return Shapes.empty(); ++ } ++ return new AABBVoxelShape(new AABB(0.0, this.aabb.minY, this.aabb.minZ, 1.0, this.aabb.maxY, this.aabb.maxZ)).optimize(); ++ } ++ case UP: // +Y ++ case DOWN: { // -Y ++ final double from = direction == Direction.UP ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; ++ if (from > this.aabb.maxY || this.aabb.minY > from) { ++ return Shapes.empty(); ++ } ++ return new AABBVoxelShape(new AABB(this.aabb.minX, 0.0, this.aabb.minZ, this.aabb.maxX, 1.0, this.aabb.maxZ)).optimize(); ++ } ++ case SOUTH: // +Z ++ case NORTH: { // -Z ++ final double from = direction == Direction.SOUTH ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; ++ if (from > this.aabb.maxZ || this.aabb.minZ > from) { ++ return Shapes.empty(); ++ } ++ return new AABBVoxelShape(new AABB(this.aabb.minX, this.aabb.minY, 0.0, this.aabb.maxX, this.aabb.maxY, 1.0)).optimize(); ++ } ++ default: { ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ } ++ ++ @Override ++ public double collide(Direction.Axis enumdirection_enumaxis, AABB axisalignedbb, double d0) { ++ if (CollisionUtil.isEmpty(this.aabb) || CollisionUtil.isEmpty(axisalignedbb)) { ++ return d0; ++ } ++ switch (enumdirection_enumaxis.ordinal()) { ++ case 0: ++ return CollisionUtil.collideX(this.aabb, axisalignedbb, d0); ++ case 1: ++ return CollisionUtil.collideY(this.aabb, axisalignedbb, d0); ++ case 2: ++ return CollisionUtil.collideZ(this.aabb, axisalignedbb, d0); ++ default: ++ throw new IllegalStateException("Unknown axis requested"); ++ } ++ } ++ ++ @Override ++ public boolean intersects(AABB axisalingedbb) { ++ return CollisionUtil.voxelShapeIntersect(this.aabb, axisalingedbb); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 97de35c614e1e9b0e825f9914173a3e1e0e53221..b35b36527294dd697d146d2ad817d7911145ae8c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -423,7 +423,7 @@ public class ServerPlayer extends Player { + + if (blockposition1 != null) { + this.moveTo(blockposition1, 0.0F, 0.0F); +- if (world.noCollision((Entity) this)) { ++ if (world.noCollision(this, this.getBoundingBox(), true)) { // Paper - make sure this loads chunks, we default to NOT loading now + break; + } + } +@@ -431,7 +431,7 @@ public class ServerPlayer extends Player { + } else { + this.moveTo(blockposition, 0.0F, 0.0F); + +- while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { ++ while (!world.noCollision(this, this.getBoundingBox(), true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now + this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); + } + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index fc14fc8017d89c27b0aeb10a5f38dafde5c15f53..70d648bc5e795355d28579cc2fda43c3c9eb255d 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -943,7 +943,7 @@ public abstract class PlayerList { + // CraftBukkit end + + worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper +- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { ++ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now + entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); + } + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 036894c89e6dcc98b4e53c859d531163ed155a32..4f19b25518d92c05a1ae49be905abe7dd2f69638 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1160,9 +1160,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + float f2 = this.getBlockSpeedFactor(); + + this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2)); +- if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata1) -> { +- return iblockdata1.is(BlockTags.FIRE) || iblockdata1.is(Blocks.LAVA); +- })) { ++ // Paper start - remove expensive streams from here ++ boolean noneMatch = true; ++ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); ++ { ++ int minX = Mth.floor(fireSearchBox.minX); ++ int minY = Mth.floor(fireSearchBox.minY); ++ int minZ = Mth.floor(fireSearchBox.minZ); ++ int maxX = Mth.floor(fireSearchBox.maxX); ++ int maxY = Mth.floor(fireSearchBox.maxY); ++ int maxZ = Mth.floor(fireSearchBox.maxZ); ++ fire_search_loop: ++ for (int fz = minZ; fz <= maxZ; ++fz) { ++ for (int fx = minX; fx <= maxX; ++fx) { ++ for (int fy = minY; fy <= maxY; ++fy) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); ++ if (chunk == null) { ++ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true ++ // even if we're in lava/fire ++ noneMatch = true; ++ break fire_search_loop; ++ } ++ if (!noneMatch) { ++ // don't do get type, we already know we're in fire - we just need to check the chunks ++ // loaded state ++ continue; ++ } ++ ++ BlockState type = chunk.getBlockStateFinal(fx, fy, fz); ++ if (type.is(BlockTags.FIRE) || type.is(Blocks.LAVA)) { ++ noneMatch = false; ++ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded ++ } ++ } ++ } ++ } ++ } ++ if (noneMatch) { ++ // Paper end - remove expensive streams from here + if (this.remainingFireTicks <= 0) { + this.setRemainingFireTicks(-this.getFireImmuneTicks()); + } +@@ -1306,32 +1341,78 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + private Vec3 collide(Vec3 movement) { +- AABB axisalignedbb = this.getBoundingBox(); +- List list = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement)); +- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level, list); +- boolean flag = movement.x != vec3d1.x; +- boolean flag1 = movement.y != vec3d1.y; +- boolean flag2 = movement.z != vec3d1.z; +- boolean flag3 = this.onGround || flag1 && movement.y < 0.0D; +- +- if (this.maxUpStep > 0.0F && flag3 && (flag || flag2)) { +- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep, movement.z), axisalignedbb, this.level, list); +- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep, 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, list); +- +- if (vec3d3.y < (double) this.maxUpStep) { +- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, list).add(vec3d3); +- +- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { +- vec3d2 = vec3d4; ++ // Paper start - optimise collisions ++ // This is a copy of vanilla's except that it uses strictly AABB math ++ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) { ++ return movement; ++ } ++ ++ final Level world = this.level; ++ final AABB currBoundingBox = this.getBoundingBox(); ++ ++ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { ++ return movement; ++ } ++ ++ final List potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); ++ try { ++ final double stepHeight = (double)this.maxUpStep; ++ final AABB collisionBox; ++ ++ if (movement.x == 0.0 && movement.z == 0.0 && movement.y != 0.0) { ++ if (movement.y > 0.0) { ++ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y); ++ } else { ++ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y); ++ } ++ } else { ++ if (stepHeight > 0.0 && (this.onGround || (movement.y < 0.0)) && (movement.x != 0.0 || movement.z != 0.0)) { ++ // don't bother getting the collisions if we don't need them. ++ if (movement.y <= 0.0) { ++ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); ++ } else { ++ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); ++ } ++ } else { ++ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); + } + } + +- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { +- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, list)); ++ io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, true, ++ false, false, null, null); ++ ++ if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) { ++ io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions); + } +- } + +- return vec3d1; ++ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisions); ++ ++ if (stepHeight > 0.0 ++ && (this.onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) ++ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { ++ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisions); ++ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisions); ++ ++ if (vec3d3.y < stepHeight) { ++ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3); ++ ++ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { ++ vec3d2 = vec3d4; ++ } ++ } ++ ++ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { ++ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions)); ++ } ++ ++ return limitedMoveVector; ++ } else { ++ return limitedMoveVector; ++ } ++ } finally { ++ io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions); ++ } ++ // Paper end - optimise collisions + } + + public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List collisions) { +@@ -2454,11 +2535,30 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + float f = this.dimensions.width * 0.8F; + AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); + +- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { +- BlockState iblockdata = this.level.getBlockState(blockposition); ++ BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); ++ int minX = Mth.floor(axisalignedbb.minX); ++ int minY = Mth.floor(axisalignedbb.minY); ++ int minZ = Mth.floor(axisalignedbb.minZ); ++ int maxX = Mth.floor(axisalignedbb.maxX); ++ int maxY = Mth.floor(axisalignedbb.maxY); ++ int maxZ = Mth.floor(axisalignedbb.maxZ); ++ for (int fz = minZ; fz <= maxZ; ++fz) { ++ for (int fx = minX; fx <= maxX; ++fx) { ++ for (int fy = minY; fy <= maxY; ++fy) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); ++ if (chunk == null) { ++ continue; ++ } + +- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND); +- }); ++ BlockState iblockdata = chunk.getBlockStateFinal(fx, fy, fz); ++ blockposition.set(fx, fy, fz); ++ if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND)) { ++ return true; ++ } ++ } ++ } ++ } ++ return false; + } + } + +diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java +index 95e22c91bc701785f4804e5d4e0a6b420b9830fd..2528291e00532c95690c4d7fb4cc0691cfb8c857 100644 +--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java ++++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java +@@ -106,7 +106,7 @@ public class BlockCollisions extends AbstractIterator { + + VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); + if (voxelShape == Shapes.block()) { +- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil + continue; + } + +diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java +index 56d94c94fb0d4dc468bb5d69be655ddd5c6b5360..d7d396ad73866a97cd9f63b34ad8c587f522e713 100644 +--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java ++++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java +@@ -35,31 +35,33 @@ public interface CollisionGetter extends BlockGetter { + return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); + } + ++ // Paper start - optimise collisions ++ default boolean noCollision(Entity entity, AABB box, boolean loadChunks) { ++ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, loadChunks, false, entity != null, true, null) ++ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); ++ } ++ // Paper end - optimise collisions ++ + default boolean noCollision(AABB box) { +- return this.noCollision((Entity)null, box); ++ // Paper start - optimise collisions ++ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, null, box, null, false, false, false, true, null) ++ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, null, box, null, true, null); ++ // Paper end - optimise collisions + } + + default boolean noCollision(Entity entity) { +- return this.noCollision(entity, entity.getBoundingBox()); ++ // Paper start - optimise collisions ++ AABB box = entity.getBoundingBox(); ++ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) ++ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); ++ // Paper end - optimise collisions + } + + default boolean noCollision(@Nullable Entity entity, AABB box) { +- try { if (entity != null) entity.collisionLoadChunks = true; // Paper +- for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { +- if (!voxelShape.isEmpty()) { +- return false; +- } +- } +- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper +- +- if (!this.getEntityCollisions(entity, box).isEmpty()) { +- return false; +- } else if (entity == null) { +- return true; +- } else { +- VoxelShape voxelShape2 = this.borderCollision(entity, box); +- return voxelShape2 == null || !Shapes.joinIsNotEmpty(voxelShape2, Shapes.create(box), BooleanOp.AND); +- } ++ // Paper start - optimise collisions ++ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) ++ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); ++ // Paper end - optimise collisions + } + + List getEntityCollisions(@Nullable Entity entity, AABB box); +diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java +index c0817ef8927f00e2fd3fbf3289f8041fcb494049..3f458ddd4dc04ed28510a212be76bb19e7f6a61e 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -49,7 +49,7 @@ public interface EntityGetter { + return true; + } else { + for(Entity entity : this.getEntities(except, shape.bounds())) { +- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) { ++ if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && shape.intersects(entity.getBoundingBox())) { // Paper + return false; + } + } +@@ -67,7 +67,7 @@ public interface EntityGetter { + return List.of(); + } else { + Predicate predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith); +- List list = this.getEntities(entity, box.inflate(1.0E-7D), predicate); ++ List list = this.getEntities(entity, box.inflate(-1.0E-7D), predicate); // Paper - needs to be negated, or else we get things we don't collide with + if (list.isEmpty()) { + return List.of(); + } else { +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 24b820484497714eb8be87e07ca1d37829d4f2c9..fd74cc9c0dab84b176f7da3fbbbdbc8fd3a7e26d 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -734,6 +734,13 @@ public abstract class BlockBehaviour { + return this.conditionallyFullOpaque; + } + // Paper end ++ // Paper start ++ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK; ++ ++ public final long getBlockCollisionBehavior() { ++ return this.blockCollisionBehavior; ++ } ++ // Paper end + + public void initCache() { + this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() +@@ -743,7 +750,35 @@ public abstract class BlockBehaviour { + } + this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here + this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light +- ++ // Paper start ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(this)) { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK; ++ } else { ++ try { ++ // There is NOTHING HACKY ABOUT THIS AT ALLLLLLLLLLLLLLL ++ VoxelShape constantShape = this.getCollisionShape(null, null, null); ++ if (constantShape == null) { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; ++ } else { ++ constantShape = constantShape.optimize(); ++ if (constantShape.isEmpty()) { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_EMPTY_BLOCK; ++ } else { ++ final List boxes = constantShape.toAabbs(); ++ if (constantShape == net.minecraft.world.phys.shapes.Shapes.getFullUnoptimisedCube() || (boxes.size() == 1 && boxes.get(0).equals(net.minecraft.world.phys.shapes.Shapes.BLOCK_OPTIMISED.aabb))) { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_FULL_BLOCK; ++ } else { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; ++ } ++ } ++ } ++ } catch (final Error error) { ++ throw error; ++ } catch (final Throwable throwable) { ++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; ++ } ++ } ++ // Paper end + } + + public Block getBlock() { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +index c3f1334b2bb97f0633f3ea43b97ee49adfd8bc0d..b0c9fce9d4e06cac139e341d218d0b6aac1f1943 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -46,6 +46,110 @@ public class LevelChunkSection { + this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes + } + ++ // Paper start ++ protected int specialCollidingBlocks; ++ // blockIndex = x | (z << 4) | (y << 8) ++ private long[] knownBlockCollisionData; ++ ++ private long[] initKnownDataField() { ++ return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE]; ++ } ++ ++ public final boolean hasSpecialCollidingBlocks() { ++ return this.specialCollidingBlocks != 0; ++ } ++ ++ public static long getKnownBlockInfo(final int blockIndex, final long value) { ++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); ++ ++ return (value >>> (valueShift << 1)) & 0b11L; ++ } ++ ++ public final long getKnownBlockInfo(final int blockIndex) { ++ if (this.knownBlockCollisionData == null) { ++ return 0L; ++ } ++ ++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) ++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); ++ ++ final long value = this.knownBlockCollisionData[arrayIndex]; ++ ++ return (value >>> (valueShift << 1)) & 0b11L; ++ } ++ ++ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1 ++ // the even localZ is the lower 32 bits, the odd is the upper 32 bits ++ public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) { ++ if (this.knownBlockCollisionData == null) { ++ return 0L; ++ } ++ ++ final int horizontalIndex = (localZ << 4) | (localY << 8); ++ return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)]; ++ } ++ ++ private void initBlockCollisionData() { ++ this.specialCollidingBlocks = 0; ++ // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw) ++ // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket. ++ // So only init if we contain non-empty blocks. ++ if (this.nonEmptyBlockCount == 0) { ++ this.knownBlockCollisionData = null; ++ return; ++ } ++ this.initKnownDataField(); ++ for (int index = 0; index < (16 * 16 * 16); ++index) { ++ final BlockState state = this.states.get(index); ++ this.setKnownBlockInfo(index, state); ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) { ++ ++this.specialCollidingBlocks; ++ } ++ } ++ } ++ ++ // only use for initBlockCollisionData ++ private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) { ++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) ++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; ++ ++ long value = this.knownBlockCollisionData[arrayIndex]; ++ ++ value &= ~(0b11L << valueShift); ++ value |= blockState.getBlockCollisionBehavior() << valueShift; ++ ++ this.knownBlockCollisionData[arrayIndex] = value; ++ } ++ ++ public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) { ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) { ++ --this.specialCollidingBlocks; ++ } ++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) { ++ ++this.specialCollidingBlocks; ++ } ++ ++ if (this.nonEmptyBlockCount == 0) { ++ this.knownBlockCollisionData = null; ++ return; ++ } ++ ++ if (this.knownBlockCollisionData == null) { ++ this.initKnownDataField(); ++ } ++ ++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) ++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; ++ ++ long value = this.knownBlockCollisionData[arrayIndex]; ++ ++ value &= ~(0b11L << valueShift); ++ value |= to.getBlockCollisionBehavior() << valueShift; ++ ++ this.knownBlockCollisionData[arrayIndex] = value; ++ } ++ // Paper end ++ + public static int getBottomBlockY(int chunkPos) { + return chunkPos << 4; + } +@@ -70,8 +174,8 @@ public class LevelChunkSection { + return this.setBlockState(x, y, z, state, true); + } + +- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { +- BlockState iblockdata1; ++ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state ++ BlockState iblockdata1; // Paper - iblockdata1 -> oldState + + if (lock) { + iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state); +@@ -110,6 +214,7 @@ public class LevelChunkSection { + ++this.tickingFluidCount; + } + ++ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper + return iblockdata1; + } + +@@ -159,6 +264,7 @@ public class LevelChunkSection { + + }); + // Paper end ++ this.initBlockCollisionData(); // Paper + } + + public PalettedContainer getStates() { +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index 120498a39b7ca7aee9763084507508d4a1c425aa..68cc6f2a78a06293a29317fda72ab3ee79b3533a 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -25,6 +25,17 @@ public class AABB { + this.maxZ = Math.max(z1, z2); + } + ++ // Paper start ++ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) { ++ this.minX = minX; ++ this.minY = minY; ++ this.minZ = minZ; ++ this.maxX = maxX; ++ this.maxY = maxY; ++ this.maxZ = maxZ; ++ } ++ // Paper end ++ + public AABB(BlockPos pos) { + this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1)); + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java +index 9d627b8e6bf3140b894d38b9a720896e2d776369..ca5f01be5d5ccfcc56780ff93cca3824409ffc0d 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java +@@ -6,6 +6,9 @@ import java.util.Arrays; + import net.minecraft.Util; + import net.minecraft.core.Direction; + ++// Paper start ++import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; ++// Paper end + public class ArrayVoxelShape extends VoxelShape { + private final DoubleList xs; + private final DoubleList ys; +@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape { + } + + ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { ++ // Paper start - optimise multi-aabb shapes ++ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0); ++ } ++ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) { ++ // Paper end - optimise multi-aabb shapes + super(shape); + int i = shape.getXSize() + 1; + int j = shape.getYSize() + 1; +@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape { + } else { + throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); + } ++ // Paper start - optimise multi-aabb shapes ++ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation; ++ this.offsetX = offsetX; ++ this.offsetY = offsetY; ++ this.offsetZ = offsetZ; ++ // Paper end - optimise multi-aabb shapes + } + + @Override +@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape { + throw new IllegalArgumentException(); + } + } ++ ++ // Paper start ++ public static final class DoubleListOffsetExposed extends AbstractDoubleList { ++ ++ public final DoubleArrayList list; ++ public final double offset; ++ ++ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) { ++ this.list = list; ++ this.offset = offset; ++ } ++ ++ @Override ++ public double getDouble(final int index) { ++ return this.list.getDouble(index) + this.offset; ++ } ++ ++ @Override ++ public int size() { ++ return this.list.size(); ++ } ++ } ++ ++ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0]; ++ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation; ++ ++ final double offsetX; ++ final double offsetY; ++ final double offsetZ; ++ ++ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() { ++ return this.boundingBoxesRepresentation; ++ } ++ ++ public final double getOffsetX() { ++ return this.offsetX; ++ } ++ ++ public final double getOffsetY() { ++ return this.offsetY; ++ } ++ ++ public final double getOffsetZ() { ++ return this.offsetZ; ++ } ++ ++ @Override ++ public java.util.List toAabbs() { ++ if (this.boundingBoxesRepresentation == null) { ++ return super.toAabbs(); ++ } ++ java.util.List ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length); ++ ++ double offX = this.offsetX; ++ double offY = this.offsetY; ++ double offZ = this.offsetZ; ++ ++ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { ++ ret.add(boundingBox.move(offX, offY, offZ)); ++ } ++ ++ return ret; ++ } ++ ++ protected static DoubleArrayList getList(DoubleList from) { ++ if (from instanceof DoubleArrayList) { ++ return (DoubleArrayList)from; ++ } else { ++ return DoubleArrayList.wrap(from.toDoubleArray()); ++ } ++ } ++ ++ @Override ++ public VoxelShape move(double x, double y, double z) { ++ if (x == 0.0 && y == 0.0 && z == 0.0) { ++ return this; ++ } ++ DoubleListOffsetExposed xPoints, yPoints, zPoints; ++ double offsetX, offsetY, offsetZ; ++ ++ if (this.xs instanceof DoubleListOffsetExposed) { ++ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x); ++ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y); ++ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z); ++ } else { ++ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x); ++ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y); ++ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z); ++ } ++ ++ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ); ++ } ++ ++ @Override ++ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) { ++ // this can be optimised by checking an "overall shape" first, but not needed ++ double offX = this.offsetX; ++ double offY = this.offsetY; ++ double offZ = this.offsetZ; ++ ++ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { ++ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ, ++ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) { ++ if (this.boundingBoxesRepresentation == null) { ++ super.forAllBoxes(doubleLineConsumer); ++ return; ++ } ++ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { ++ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, ++ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ); ++ } ++ } ++ ++ @Override ++ public VoxelShape optimize() { ++ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) { ++ return this; ++ } ++ ++ VoxelShape simplified = Shapes.empty(); ++ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { ++ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, ++ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR); ++ } ++ ++ if (!(simplified instanceof ArrayVoxelShape)) { ++ return simplified; ++ } ++ ++ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation(); ++ ++ if (boundingBoxesRepresentation.length == 1) { ++ return new io.papermc.paper.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize(); ++ } ++ ++ return simplified; ++ } ++ // Paper end ++ + } +diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +index 9176735c08a75854209f24113b0e78332249dc4d..731c7dd15f131dc124be6af8f342b122cb89491b 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -19,16 +19,17 @@ public final class Shapes { + DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); + discreteVoxelShape.fill(0, 0, 0); + return new CubeVoxelShape(discreteVoxelShape); +- }); ++ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Paper - OBFHELPER + public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D}))); ++ public static final io.papermc.paper.voxel.AABBVoxelShape BLOCK_OPTIMISED = new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Paper + + public static VoxelShape empty() { + return EMPTY; + } + + public static VoxelShape block() { +- return BLOCK; ++ return BLOCK_OPTIMISED; // Paper + } + + public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { +@@ -41,29 +42,14 @@ public final class Shapes { + + public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) { +- int i = findBits(minX, maxX); +- int j = findBits(minY, maxY); +- int k = findBits(minZ, maxZ); +- if (i >= 0 && j >= 0 && k >= 0) { +- if (i == 0 && j == 0 && k == 0) { +- return block(); +- } else { +- int l = 1 << i; +- int m = 1 << j; +- int n = 1 << k; +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n)); +- return new CubeVoxelShape(bitSetDiscreteVoxelShape); +- } +- } else { +- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ})); +- } ++ return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper + } else { + return empty(); + } + } + + public static VoxelShape create(AABB box) { +- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); ++ return new io.papermc.paper.voxel.AABBVoxelShape(box); // Paper + } + + @VisibleForTesting +@@ -125,6 +111,20 @@ public final class Shapes { + } + + public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { ++ // Paper start - optimise voxelshape ++ if (predicate == BooleanOp.AND) { ++ if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape) { ++ return io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb, ((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb); ++ } else if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) { ++ return ((ArrayVoxelShape)shape2).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb); ++ } else if (shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) { ++ return ((ArrayVoxelShape)shape1).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb); ++ } ++ } ++ return joinIsNotEmptyVanilla(shape1, shape2, predicate); ++ } ++ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { ++ // Paper end - optimise voxelshape + if (predicate.apply(false, false)) { + throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); + } else { +@@ -196,6 +196,43 @@ public final class Shapes { + } + + public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { ++ // Paper start - optimise shape creation here for lighting, as this shape is going to be used ++ // for transparency checks ++ if (shape == BLOCK || shape == BLOCK_OPTIMISED) { ++ return BLOCK_OPTIMISED; ++ } else if (shape == empty()) { ++ return empty(); ++ } ++ ++ if (shape instanceof io.papermc.paper.voxel.AABBVoxelShape) { ++ final AABB box = ((io.papermc.paper.voxel.AABBVoxelShape)shape).aabb; ++ switch (direction) { ++ case WEST: // -X ++ case EAST: { // +X ++ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : ++ !DoubleMath.fuzzyEquals(box.minX, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); ++ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize(); ++ } ++ case DOWN: // -Y ++ case UP: { // +Y ++ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : ++ !DoubleMath.fuzzyEquals(box.minY, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); ++ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize(); ++ } ++ case NORTH: // -Z ++ case SOUTH: { // +Z ++ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : ++ !DoubleMath.fuzzyEquals(box.minZ,0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); ++ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize(); ++ } ++ } ++ } ++ ++ // fall back to vanilla ++ return getFaceShapeVanilla(shape, direction); ++ } ++ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) { ++ // Paper end + if (shape == block()) { + return block(); + } else { +@@ -210,7 +247,7 @@ public final class Shapes { + i = 0; + } + +- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); ++ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Paper - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape + } + } + +@@ -235,6 +272,53 @@ public final class Shapes { + } + + public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { ++ // Paper start - try to optimise for the case where the shapes do _not_ occlude ++ // which is _most_ of the time in lighting ++ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED ++ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) { ++ return true; ++ } ++ boolean v1Empty = one == empty(); ++ boolean v2Empty = two == empty(); ++ if (v1Empty && v2Empty) { ++ return false; ++ } ++ if ((one instanceof io.papermc.paper.voxel.AABBVoxelShape || v1Empty) ++ && (two instanceof io.papermc.paper.voxel.AABBVoxelShape || v2Empty)) { ++ if (!v1Empty && !v2Empty && (one != two)) { ++ AABB boundingBox1 = ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb; ++ AABB boundingBox2 = ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb; ++ // can call it here in some cases ++ ++ // check overall bounding box ++ double minY = Math.min(boundingBox1.minY, boundingBox2.minY); ++ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY); ++ if (minY > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minX = Math.min(boundingBox1.minX, boundingBox2.minX); ++ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX); ++ if (minX > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ); ++ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ); ++ if (minZ > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { ++ return false; ++ } ++ // fall through to full merge check ++ } else { ++ AABB boundingBox = v1Empty ? ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb : ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb; ++ // check if the bounding box encloses the full cube ++ return (boundingBox.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (boundingBox.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && ++ (boundingBox.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); ++ } ++ } ++ return faceShapeOccludesVanilla(one, two); ++ } ++ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) { ++ // Paper end + if (one != block() && two != block()) { + if (one.isEmpty() && two.isEmpty()) { + return false; +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 c4ca051720f790f5b8eb860b14e268de8557454d..2182afd1b95acf14c55bddfeec17dae0a63e1f00 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; + + public abstract class VoxelShape { +- protected final DiscreteVoxelShape shape; ++ public final DiscreteVoxelShape shape; // Paper - public + @Nullable + private VoxelShape[] faces; + +- VoxelShape(DiscreteVoxelShape voxels) { ++ // Paper start ++ public boolean intersects(AABB shape) { ++ return Shapes.joinIsNotEmpty(this, new io.papermc.paper.voxel.AABBVoxelShape(shape), BooleanOp.AND); ++ } ++ // Paper end ++ ++ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected + this.shape = voxels; + } + +@@ -163,7 +169,7 @@ public abstract class VoxelShape { + } + } + +- private VoxelShape calculateFace(Direction direction) { ++ protected VoxelShape calculateFace(Direction direction) { // Paper + Direction.Axis axis = direction.getAxis(); + DoubleList doubleList = this.getCoords(axis); + if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) { diff --git a/patches/server/0800-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch b/patches/server/0800-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch deleted file mode 100644 index ded6d6290a..0000000000 --- a/patches/server/0800-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch +++ /dev/null @@ -1,2122 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 4 May 2020 10:06:24 -0700 -Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and - collisions - - -diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java -index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644 ---- a/src/main/java/io/papermc/paper/util/CachedLists.java -+++ b/src/main/java/io/papermc/paper/util/CachedLists.java -@@ -1,8 +1,57 @@ - package io.papermc.paper.util; - -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.phys.AABB; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.util.UnsafeList; -+import java.util.List; -+ - public final class CachedLists { - -- public static void reset() { -+ // Paper start - optimise collisions -+ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); -+ static boolean tempCollisionListInUse; -+ -+ public static UnsafeList getTempCollisionList() { -+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempCollisionListInUse = true; -+ return TEMP_COLLISION_LIST; -+ } -+ -+ public static void returnTempCollisionList(List list) { -+ if (list != TEMP_COLLISION_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempCollisionListInUse = false; -+ } - -+ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); -+ static boolean tempGetEntitiesListInUse; -+ -+ public static UnsafeList getTempGetEntitiesList() { -+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempGetEntitiesListInUse = true; -+ return TEMP_GET_ENTITIES_LIST; -+ } -+ -+ public static void returnTempGetEntitiesList(List list) { -+ if (list != TEMP_GET_ENTITIES_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempGetEntitiesListInUse = false; -+ } -+ // Paper end - optimise collisions -+ -+ public static void reset() { -+ // Paper start - optimise collisions -+ TEMP_COLLISION_LIST.completeReset(); -+ TEMP_GET_ENTITIES_LIST.completeReset(); -+ // Paper end - optimise collisions - } - } -diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a87f6380b2c387fb0cdd40d5087b5c93492e3c88 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java -@@ -0,0 +1,899 @@ -+package io.papermc.paper.util; -+ -+import io.papermc.paper.voxel.AABBVoxelShape; -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.WorldGenRegion; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.CollisionGetter; -+import net.minecraft.world.level.EntityGetter; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.border.WorldBorder; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.PalettedContainer; -+import net.minecraft.world.level.material.FlowingFluid; -+import net.minecraft.world.level.material.FluidState; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import net.minecraft.world.phys.shapes.ArrayVoxelShape; -+import net.minecraft.world.phys.shapes.CollisionContext; -+import net.minecraft.world.phys.shapes.EntityCollisionContext; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.List; -+import java.util.function.BiPredicate; -+import java.util.function.Predicate; -+ -+public final class CollisionUtil { -+ -+ public static final double COLLISION_EPSILON = 1.0E-7; -+ -+ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty -+ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube -+ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info -+ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions -+ -+ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { -+ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON; -+ } -+ -+ public static boolean isEmpty(final AABB aabb) { -+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; -+ } -+ -+ public static boolean isEmpty(final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON; -+ } -+ -+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { -+ double x = (double)(chunkX << 4); -+ double z = (double)(chunkZ << 4); -+ // use a bounding box bigger than the chunk to prevent entities from entering it on move -+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, -+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false); -+ } -+ -+ /* -+ A couple of rules for VoxelShape collisions: -+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement -+ checks. -+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite -+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code -+ will automatically round it to 0. -+ */ -+ -+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1, -+ final double maxY1, final double maxZ1, final double minX2, final double minY2, -+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) { -+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON && -+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON && -+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && -+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && -+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { -+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && -+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && -+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; -+ } -+ -+ public static double collideX(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static double collideY(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static double collideZ(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static AABB offsetX(final AABB box, final double dx) { -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB offsetY(final AABB box, final double dy) { -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB offsetZ(final AABB box, final double dz) { -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false); -+ } -+ -+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false); -+ } -+ -+ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideX(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideY(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideZ(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { -+ double x = moveVector.x; -+ double y = moveVector.y; -+ double z = moveVector.z; -+ -+ if (y != 0.0) { -+ y = performCollisionsY(axisalignedbb, y, potentialCollisions); -+ if (y != 0.0) { -+ axisalignedbb = offsetY(axisalignedbb, y); -+ } -+ } -+ -+ final boolean xSmaller = Math.abs(x) < Math.abs(z); -+ -+ if (xSmaller && z != 0.0) { -+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); -+ if (z != 0.0) { -+ axisalignedbb = offsetZ(axisalignedbb, z); -+ } -+ } -+ -+ if (x != 0.0) { -+ x = performCollisionsX(axisalignedbb, x, potentialCollisions); -+ if (!xSmaller && x != 0.0) { -+ axisalignedbb = offsetX(axisalignedbb, x); -+ } -+ } -+ -+ if (!xSmaller && z != 0.0) { -+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); -+ } -+ -+ return new Vec3(x, y, z); -+ } -+ -+ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List list) { -+ if (shape instanceof AABBVoxelShape) { -+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; -+ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) { -+ list.add(shapeCasted.aabb); -+ return true; -+ } -+ return false; -+ } else if (shape instanceof ArrayVoxelShape) { -+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; -+ // this can be optimised by checking an "overall shape" first, but not needed -+ -+ final double offX = shapeCasted.getOffsetX(); -+ final double offY = shapeCasted.getOffsetY(); -+ final double offZ = shapeCasted.getOffsetZ(); -+ -+ boolean ret = false; -+ -+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { -+ final double minX, minY, minZ, maxX, maxY, maxZ; -+ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ, -+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ) -+ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) { -+ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false)); -+ ret = true; -+ } -+ } -+ -+ return ret; -+ } else { -+ final List boxes = shape.toAabbs(); -+ -+ boolean ret = false; -+ -+ for (int i = 0, len = boxes.size(); i < len; ++i) { -+ final AABB box = boxes.get(i); -+ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) { -+ list.add(box); -+ ret = true; -+ } -+ } -+ -+ return ret; -+ } -+ } -+ -+ public static void addBoxesTo(final VoxelShape shape, final List list) { -+ if (shape instanceof AABBVoxelShape) { -+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; -+ if (!isEmpty(shapeCasted.aabb)) { -+ list.add(shapeCasted.aabb); -+ } -+ } else if (shape instanceof ArrayVoxelShape) { -+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; -+ -+ final double offX = shapeCasted.getOffsetX(); -+ final double offY = shapeCasted.getOffsetY(); -+ final double offZ = shapeCasted.getOffsetZ(); -+ -+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { -+ final AABB box = boundingBox.move(offX, offY, offZ); -+ if (!isEmpty(box)) { -+ list.add(box); -+ } -+ } -+ } else { -+ final List boxes = shape.toAabbs(); -+ for (int i = 0, len = boxes.size(); i < len; ++i) { -+ final AABB box = boxes.get(i); -+ if (!isEmpty(box)) { -+ list.add(box); -+ } -+ } -+ } -+ } -+ -+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) { -+ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); -+ } -+ -+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, -+ final double boxMinZ, final double boxMaxZ) { -+ final double borderMinX = worldborder.getMinX(); // -X -+ final double borderMaxX = worldborder.getMaxX(); // +X -+ -+ final double borderMinZ = worldborder.getMinZ(); // -Z -+ final double borderMaxZ = worldborder.getMaxZ(); // +Z -+ -+ return -+ // Not intersecting if we're smaller -+ !voxelShapeIntersect( -+ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON, -+ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON, -+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ -+ ) -+ && -+ -+ // Are intersecting if we're larger -+ voxelShapeIntersect( -+ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON, -+ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON, -+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ -+ ); -+ } -+ -+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) { -+ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); -+ } -+ -+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, -+ final double boxMinZ, final double boxMaxZ) { -+ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X -+ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X -+ -+ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z -+ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z -+ -+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ; -+ } -+ -+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb, -+ final List into, final boolean loadChunks, final boolean collidesWithUnloaded, -+ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { -+ boolean ret = false; -+ -+ if (checkBorder) { -+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); -+ ret = true; -+ } -+ } -+ } -+ -+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; -+ -+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; -+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; -+ -+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; -+ -+ final int minSection = WorldUtil.getMinSection(getter); -+ final int maxSection = WorldUtil.getMaxSection(getter); -+ final int minBlock = minSection << 4; -+ final int maxBlock = (maxSection << 4) | 15; -+ -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ CollisionContext collisionShape = null; -+ -+ // special cases: -+ if (minBlockY > maxBlock || maxBlockY < minBlock) { -+ // no point in checking -+ return ret; -+ } -+ -+ final int minYIterate = Math.max(minBlock, minBlockY); -+ final int maxYIterate = Math.min(maxBlock, maxBlockY); -+ -+ final int minChunkX = minBlockX >> 4; -+ final int maxChunkX = maxBlockX >> 4; -+ -+ final int minChunkY = minBlockY >> 4; -+ final int maxChunkY = maxBlockY >> 4; -+ -+ final int minChunkYIterate = minYIterate >> 4; -+ final int maxChunkYIterate = maxYIterate >> 4; -+ -+ final int minChunkZ = minBlockZ >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; -+ -+ final ServerChunkCache chunkProvider; -+ if (getter instanceof WorldGenRegion) { -+ chunkProvider = null; -+ } else if (getter instanceof ServerLevel) { -+ chunkProvider = ((ServerLevel)getter).getChunkSource(); -+ } else { -+ chunkProvider = null; -+ } -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk -+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk -+ -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk -+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk -+ -+ final int chunkXGlobalPos = currChunkX << 4; -+ final int chunkZGlobalPos = currChunkZ << 4; -+ final ChunkAccess chunk; -+ if (chunkProvider == null) { -+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ); -+ } else { -+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ } -+ -+ if (chunk == null) { -+ if (collidesWithUnloaded) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(getBoxForChunk(currChunkX, currChunkZ)); -+ ret = true; -+ } -+ } -+ continue; -+ } -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ -+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) { -+ final LevelChunkSection section = sections[currChunkY - minSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // empty -+ continue; -+ } -+ final PalettedContainer blocks = section.states; -+ -+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk -+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk -+ final int chunkYGlobalPos = currChunkY << 4; -+ -+ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks(); -+ -+ final int minXIterate; -+ final int maxXIterate; -+ final int minZIterate; -+ final int maxZIterate; -+ final int minYIterateLocal; -+ final int maxYIterateLocal; -+ -+ if (!sectionHasSpecial) { -+ minXIterate = currChunkX == minChunkX ? minX + 1 : minX; -+ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX; -+ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ; -+ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ; -+ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY; -+ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY; -+ if (minXIterate > maxXIterate || minZIterate > maxZIterate) { -+ continue; -+ } -+ } else { -+ minXIterate = minX; -+ maxXIterate = maxX; -+ minZIterate = minZ; -+ maxZIterate = maxZ; -+ minYIterateLocal = minY; -+ maxYIterateLocal = maxY; -+ } -+ -+ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) { -+ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15); -+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ, -+ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) { -+ // From getKnownBlockInfoHorizontalRaw: -+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1 -+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits -+ // We want to use a bitset to only iterate over non-empty blocks. -+ // We need to build a bitset mask to and out the other collisions we just don't care at all about -+ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis -+ // It's important to note that the iterate values can be outside [0, 15], but if they are, -+ // then none of the x or z loops would meet their conditions. So we can assume they are never -+ // out of bounds here -+ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32 -+ long bitset = (1L << xAxisBits) - 1; -+ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd) -+ int shift = (currZ & 1) << 5; // this will be a LEFT shift -+ // Now we need to offset shift so that the bitset first position is at minXIterate -+ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ... -+ -+ // all done -+ bitset = bitset << shift; -+ if ((collisionForHorizontal & bitset) == 0L) { -+ // All empty -+ continue; -+ } -+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { -+ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8); -+ -+ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal); -+ -+ switch (blockInfo) { -+ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: { -+ continue; -+ } -+ case (int) CollisionUtil.KNOWN_FULL_BLOCK: { -+ double blockX = (double)(currX | chunkXGlobalPos); -+ double blockY = (double)(currY | chunkYGlobalPos); -+ double blockZ = (double)(currZ | chunkZGlobalPos); -+ final AABB blockBox = new AABB( -+ blockX, blockY, blockZ, -+ blockX + 1.0, blockY + 1.0, blockZ + 1.0, -+ true -+ ); -+ if (predicate != null) { -+ if (!voxelShapeIntersect(aabb, blockBox)) { -+ continue; -+ } -+ // fall through to get the block for the predicate -+ } else { -+ if (voxelShapeIntersect(aabb, blockBox)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(blockBox); -+ ret = true; -+ } -+ } -+ continue; -+ } -+ } -+ // default: fall through to standard logic -+ } -+ -+ int blockX = currX | chunkXGlobalPos; -+ int blockY = currY | chunkYGlobalPos; -+ int blockZ = currZ | chunkZGlobalPos; -+ -+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + -+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + -+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ BlockState blockData = blocks.get(localBlockIndex); -+ -+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.set(blockX, blockY, blockZ); -+ if (collisionShape == null) { -+ collisionShape = new LazyEntityCollisionContext(entity); -+ } -+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); -+ if (voxelshape2 != Shapes.empty()) { -+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ); -+ -+ if (predicate != null && !predicate.test(blockData, mutablePos)) { -+ continue; -+ } -+ -+ if (checkOnly) { -+ if (voxelshape3.intersects(aabb)) { -+ return true; -+ } -+ } else { -+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getCollisionsForBlocksOrWorldBorderReference(final CollisionGetter getter, final Entity entity, final AABB aabb, -+ final List into, final boolean loadChunks, final boolean collidesWithUnloaded, -+ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { -+ boolean ret = false; -+ -+ if (checkBorder) { -+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); -+ ret = true; -+ } -+ } -+ } -+ -+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; -+ -+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; -+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; -+ -+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; -+ -+ final int minSection = WorldUtil.getMinSection(getter); -+ final int maxSection = WorldUtil.getMaxSection(getter); -+ final int minBlock = minSection << 4; -+ final int maxBlock = (maxSection << 4) | 15; -+ -+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ CollisionContext collisionShape = null; -+ -+ // special cases: -+ if (minBlockY > maxBlock || maxBlockY < minBlock) { -+ // no point in checking -+ return ret; -+ } -+ -+ final int minYIterate = Math.max(minBlock, minBlockY); -+ final int maxYIterate = Math.min(maxBlock, maxBlockY); -+ -+ final int minChunkX = minBlockX >> 4; -+ final int maxChunkX = maxBlockX >> 4; -+ -+ final int minChunkY = minBlockY >> 4; -+ final int maxChunkY = maxBlockY >> 4; -+ -+ final int minChunkYIterate = minYIterate >> 4; -+ final int maxChunkYIterate = maxYIterate >> 4; -+ -+ final int minChunkZ = minBlockZ >> 4; -+ final int maxChunkZ = maxBlockZ >> 4; -+ -+ final ServerChunkCache chunkProvider; -+ if (getter instanceof WorldGenRegion) { -+ chunkProvider = null; -+ } else if (getter instanceof ServerLevel) { -+ chunkProvider = ((ServerLevel)getter).getChunkSource(); -+ } else { -+ chunkProvider = null; -+ } -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk -+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk -+ -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk -+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk -+ -+ final int chunkXGlobalPos = currChunkX << 4; -+ final int chunkZGlobalPos = currChunkZ << 4; -+ final ChunkAccess chunk; -+ if (chunkProvider == null) { -+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ); -+ } else { -+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ } -+ -+ if (chunk == null) { -+ if (collidesWithUnloaded) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(getBoxForChunk(currChunkX, currChunkZ)); -+ ret = true; -+ } -+ } -+ continue; -+ } -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) { -+ final LevelChunkSection section = sections[currChunkY - minSection]; -+ if (section == null || section.hasOnlyAir()) { -+ // empty -+ continue; -+ } -+ final PalettedContainer blocks = section.states; -+ -+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk -+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk -+ final int chunkYGlobalPos = currChunkY << 4; -+ -+ for (int currY = minY; currY <= maxY; ++currY) { -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); -+ int blockX = currX | chunkXGlobalPos; -+ int blockY = currY | chunkYGlobalPos; -+ int blockZ = currZ | chunkZGlobalPos; -+ -+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + -+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + -+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ BlockState blockData = blocks.get(localBlockIndex); -+ if (blockData.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) { -+ continue; -+ } -+ -+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.set(blockX, blockY, blockZ); -+ if (collisionShape == null) { -+ collisionShape = new LazyEntityCollisionContext(entity); -+ } -+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); -+ if (voxelshape2 != Shapes.empty()) { -+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ); -+ -+ if (predicate != null && !predicate.test(blockData, mutablePos)) { -+ continue; -+ } -+ -+ if (checkOnly) { -+ if (voxelshape3.intersects(aabb)) { -+ return true; -+ } -+ } else { -+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb, -+ final List into, final boolean checkOnly, final Predicate predicate) { -+ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with. -+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems -+ // specifically with boat collisions. -+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); -+ final List entities = CachedLists.getTempGetEntitiesList(); -+ try { -+ if (entity != null && entity.hardCollides()) { -+ entityGetter.getEntities(entity, aabb, predicate, entities); -+ } else { -+ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities); -+ } -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(otherEntity.getBoundingBox()); -+ ret = true; -+ } -+ } -+ } -+ } finally { -+ CachedLists.returnTempGetEntitiesList(entities); -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb, -+ final List into, final boolean loadChunks, final boolean collidesWithUnloadedChunks, -+ final boolean checkBorder, final boolean checkOnly, final BiPredicate blockPredicate, -+ final Predicate entityPredicate) { -+ if (checkOnly) { -+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) -+ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); -+ } else { -+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) -+ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); -+ } -+ } -+ -+ public static final class LazyEntityCollisionContext extends EntityCollisionContext { -+ -+ private CollisionContext delegate; -+ -+ public LazyEntityCollisionContext(final Entity entity) { -+ super(false, 0.0, null, null, entity); -+ } -+ -+ public CollisionContext getDelegate() { -+ final Entity entity = this.getEntity(); -+ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate; -+ } -+ -+ @Override -+ public boolean isDescending() { -+ return this.getDelegate().isDescending(); -+ } -+ -+ @Override -+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) { -+ return this.getDelegate().isAbove(shape, pos, defaultValue); -+ } -+ -+ @Override -+ public boolean isHoldingItem(final Item item) { -+ return this.getDelegate().isHoldingItem(item); -+ } -+ -+ @Override -+ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) { -+ return this.getDelegate().canStandOnFluid(state, fluidState); -+ } -+ } -+ -+ private CollisionUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d67a40e7be030142443680c89e1763fc9ecdfe0a ---- /dev/null -+++ b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java -@@ -0,0 +1,200 @@ -+package io.papermc.paper.voxel; -+ -+import io.papermc.paper.util.CollisionUtil; -+import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -+import it.unimi.dsi.fastutil.doubles.DoubleList; -+import net.minecraft.core.Direction; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.List; -+ -+public final class AABBVoxelShape extends VoxelShape { -+ -+ public final AABB aabb; -+ -+ public AABBVoxelShape(AABB aabb) { -+ super(Shapes.getFullUnoptimisedCube().shape); -+ this.aabb = aabb; -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return CollisionUtil.isEmpty(this.aabb); -+ } -+ -+ @Override -+ public double min(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.aabb.minX; -+ case 1: -+ return this.aabb.minY; -+ case 2: -+ return this.aabb.minZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public double max(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.aabb.maxX; -+ case 1: -+ return this.aabb.maxY; -+ case 2: -+ return this.aabb.maxZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public AABB bounds() { -+ return this.aabb; -+ } -+ -+ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis. -+ @Override -+ protected double get(Direction.Axis enumdirection_enumaxis, int i) { -+ switch (enumdirection_enumaxis.ordinal() | (i << 2)) { -+ case (0 | (0 << 2)): -+ return this.aabb.minX; -+ case (1 | (0 << 2)): -+ return this.aabb.minY; -+ case (2 | (0 << 2)): -+ return this.aabb.minZ; -+ case (0 | (1 << 2)): -+ return this.aabb.maxX; -+ case (1 | (1 << 2)): -+ return this.aabb.maxY; -+ case (2 | (1 << 2)): -+ return this.aabb.maxZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ private DoubleList cachedListX; -+ private DoubleList cachedListY; -+ private DoubleList cachedListZ; -+ -+ @Override -+ protected DoubleList getCoords(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX; -+ case 1: -+ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY; -+ case 2: -+ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public VoxelShape move(double d0, double d1, double d2) { -+ return new AABBVoxelShape(this.aabb.move(d0, d1, d2)); -+ } -+ -+ @Override -+ public VoxelShape optimize() { -+ if (this.isEmpty()) { -+ return Shapes.empty(); -+ } else if (this == Shapes.BLOCK_OPTIMISED || this.aabb.equals(Shapes.BLOCK_OPTIMISED.aabb)) { -+ return Shapes.BLOCK_OPTIMISED; -+ } -+ return this; -+ } -+ -+ @Override -+ public void forAllBoxes(Shapes.DoubleLineConsumer voxelshapes_a) { -+ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ); -+ } -+ -+ @Override -+ public List toAabbs() { // getAABBs -+ List ret = new ArrayList<>(1); -+ ret.add(this.aabb); -+ return ret; -+ } -+ -+ @Override -+ protected int findIndex(Direction.Axis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1; -+ case 1: -+ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1; -+ case 2: -+ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ protected VoxelShape calculateFace(Direction direction) { -+ if (this.isEmpty()) { -+ return Shapes.empty(); -+ } -+ if (this == Shapes.BLOCK_OPTIMISED) { -+ return this; -+ } -+ switch (direction) { -+ case EAST: // +X -+ case WEST: { // -X -+ final double from = direction == Direction.EAST ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxX || this.aabb.minX > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(0.0, this.aabb.minY, this.aabb.minZ, 1.0, this.aabb.maxY, this.aabb.maxZ)).optimize(); -+ } -+ case UP: // +Y -+ case DOWN: { // -Y -+ final double from = direction == Direction.UP ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxY || this.aabb.minY > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(this.aabb.minX, 0.0, this.aabb.minZ, this.aabb.maxX, 1.0, this.aabb.maxZ)).optimize(); -+ } -+ case SOUTH: // +Z -+ case NORTH: { // -Z -+ final double from = direction == Direction.SOUTH ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxZ || this.aabb.minZ > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(this.aabb.minX, this.aabb.minY, 0.0, this.aabb.maxX, this.aabb.maxY, 1.0)).optimize(); -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ } -+ -+ @Override -+ public double collide(Direction.Axis enumdirection_enumaxis, AABB axisalignedbb, double d0) { -+ if (CollisionUtil.isEmpty(this.aabb) || CollisionUtil.isEmpty(axisalignedbb)) { -+ return d0; -+ } -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return CollisionUtil.collideX(this.aabb, axisalignedbb, d0); -+ case 1: -+ return CollisionUtil.collideY(this.aabb, axisalignedbb, d0); -+ case 2: -+ return CollisionUtil.collideZ(this.aabb, axisalignedbb, d0); -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public boolean intersects(AABB axisalingedbb) { -+ return CollisionUtil.voxelShapeIntersect(this.aabb, axisalingedbb); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 97de35c614e1e9b0e825f9914173a3e1e0e53221..b35b36527294dd697d146d2ad817d7911145ae8c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -423,7 +423,7 @@ public class ServerPlayer extends Player { - - if (blockposition1 != null) { - this.moveTo(blockposition1, 0.0F, 0.0F); -- if (world.noCollision((Entity) this)) { -+ if (world.noCollision(this, this.getBoundingBox(), true)) { // Paper - make sure this loads chunks, we default to NOT loading now - break; - } - } -@@ -431,7 +431,7 @@ public class ServerPlayer extends Player { - } else { - this.moveTo(blockposition, 0.0F, 0.0F); - -- while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { -+ while (!world.noCollision(this, this.getBoundingBox(), true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now - this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); - } - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index fc14fc8017d89c27b0aeb10a5f38dafde5c15f53..70d648bc5e795355d28579cc2fda43c3c9eb255d 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -943,7 +943,7 @@ public abstract class PlayerList { - // CraftBukkit end - - worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper -- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { -+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index bf039f3e2d1a228f0a169b42031d9708f3193a92..983b3b73ed071b1958e96b5be33e1b1097b95659 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1160,9 +1160,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - float f2 = this.getBlockSpeedFactor(); - - this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2)); -- if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata1) -> { -- return iblockdata1.is(BlockTags.FIRE) || iblockdata1.is(Blocks.LAVA); -- })) { -+ // Paper start - remove expensive streams from here -+ boolean noneMatch = true; -+ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); -+ { -+ int minX = Mth.floor(fireSearchBox.minX); -+ int minY = Mth.floor(fireSearchBox.minY); -+ int minZ = Mth.floor(fireSearchBox.minZ); -+ int maxX = Mth.floor(fireSearchBox.maxX); -+ int maxY = Mth.floor(fireSearchBox.maxY); -+ int maxZ = Mth.floor(fireSearchBox.maxZ); -+ fire_search_loop: -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ for (int fx = minX; fx <= maxX; ++fx) { -+ for (int fy = minY; fy <= maxY; ++fy) { -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); -+ if (chunk == null) { -+ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true -+ // even if we're in lava/fire -+ noneMatch = true; -+ break fire_search_loop; -+ } -+ if (!noneMatch) { -+ // don't do get type, we already know we're in fire - we just need to check the chunks -+ // loaded state -+ continue; -+ } -+ -+ BlockState type = chunk.getBlockStateFinal(fx, fy, fz); -+ if (type.is(BlockTags.FIRE) || type.is(Blocks.LAVA)) { -+ noneMatch = false; -+ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded -+ } -+ } -+ } -+ } -+ } -+ if (noneMatch) { -+ // Paper end - remove expensive streams from here - if (this.remainingFireTicks <= 0) { - this.setRemainingFireTicks(-this.getFireImmuneTicks()); - } -@@ -1306,32 +1341,78 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - private Vec3 collide(Vec3 movement) { -- AABB axisalignedbb = this.getBoundingBox(); -- List list = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement)); -- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level, list); -- boolean flag = movement.x != vec3d1.x; -- boolean flag1 = movement.y != vec3d1.y; -- boolean flag2 = movement.z != vec3d1.z; -- boolean flag3 = this.onGround || flag1 && movement.y < 0.0D; -- -- if (this.maxUpStep > 0.0F && flag3 && (flag || flag2)) { -- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep, movement.z), axisalignedbb, this.level, list); -- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep, 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, list); -- -- if (vec3d3.y < (double) this.maxUpStep) { -- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, list).add(vec3d3); -- -- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -- vec3d2 = vec3d4; -+ // Paper start - optimise collisions -+ // This is a copy of vanilla's except that it uses strictly AABB math -+ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) { -+ return movement; -+ } -+ -+ final Level world = this.level; -+ final AABB currBoundingBox = this.getBoundingBox(); -+ -+ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { -+ return movement; -+ } -+ -+ final List potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); -+ try { -+ final double stepHeight = (double)this.maxUpStep; -+ final AABB collisionBox; -+ -+ if (movement.x == 0.0 && movement.z == 0.0 && movement.y != 0.0) { -+ if (movement.y > 0.0) { -+ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y); -+ } else { -+ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y); -+ } -+ } else { -+ if (stepHeight > 0.0 && (this.onGround || (movement.y < 0.0)) && (movement.x != 0.0 || movement.z != 0.0)) { -+ // don't bother getting the collisions if we don't need them. -+ if (movement.y <= 0.0) { -+ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight); -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z); -+ } -+ } else { -+ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z); - } - } - -- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { -- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, list)); -+ io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, true, -+ false, false, null, null); -+ -+ if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) { -+ io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions); - } -- } - -- return vec3d1; -+ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisions); -+ -+ if (stepHeight > 0.0 -+ && (this.onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) -+ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { -+ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisions); -+ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisions); -+ -+ if (vec3d3.y < stepHeight) { -+ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3); -+ -+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -+ vec3d2 = vec3d4; -+ } -+ } -+ -+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { -+ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions)); -+ } -+ -+ return limitedMoveVector; -+ } else { -+ return limitedMoveVector; -+ } -+ } finally { -+ io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions); -+ } -+ // Paper end - optimise collisions - } - - public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List collisions) { -@@ -2440,11 +2521,30 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - float f = this.dimensions.width * 0.8F; - AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - -- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> { -- BlockState iblockdata = this.level.getBlockState(blockposition); -+ BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos(); -+ int minX = Mth.floor(axisalignedbb.minX); -+ int minY = Mth.floor(axisalignedbb.minY); -+ int minZ = Mth.floor(axisalignedbb.minZ); -+ int maxX = Mth.floor(axisalignedbb.maxX); -+ int maxY = Mth.floor(axisalignedbb.maxY); -+ int maxZ = Mth.floor(axisalignedbb.maxZ); -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ for (int fx = minX; fx <= maxX; ++fx) { -+ for (int fy = minY; fy <= maxY; ++fy) { -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); -+ if (chunk == null) { -+ continue; -+ } - -- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND); -- }); -+ BlockState iblockdata = chunk.getBlockStateFinal(fx, fy, fz); -+ blockposition.set(fx, fy, fz); -+ if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND)) { -+ return true; -+ } -+ } -+ } -+ } -+ return false; - } - } - -diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java -index 95e22c91bc701785f4804e5d4e0a6b420b9830fd..2528291e00532c95690c4d7fb4cc0691cfb8c857 100644 ---- a/src/main/java/net/minecraft/world/level/BlockCollisions.java -+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java -@@ -106,7 +106,7 @@ public class BlockCollisions extends AbstractIterator { - - VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); - if (voxelShape == Shapes.block()) { -- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil - continue; - } - -diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java -index 56d94c94fb0d4dc468bb5d69be655ddd5c6b5360..d7d396ad73866a97cd9f63b34ad8c587f522e713 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java -+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java -@@ -35,31 +35,33 @@ public interface CollisionGetter extends BlockGetter { - return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); - } - -+ // Paper start - optimise collisions -+ default boolean noCollision(Entity entity, AABB box, boolean loadChunks) { -+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, loadChunks, false, entity != null, true, null) -+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); -+ } -+ // Paper end - optimise collisions -+ - default boolean noCollision(AABB box) { -- return this.noCollision((Entity)null, box); -+ // Paper start - optimise collisions -+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, null, box, null, false, false, false, true, null) -+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, null, box, null, true, null); -+ // Paper end - optimise collisions - } - - default boolean noCollision(Entity entity) { -- return this.noCollision(entity, entity.getBoundingBox()); -+ // Paper start - optimise collisions -+ AABB box = entity.getBoundingBox(); -+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) -+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); -+ // Paper end - optimise collisions - } - - default boolean noCollision(@Nullable Entity entity, AABB box) { -- try { if (entity != null) entity.collisionLoadChunks = true; // Paper -- for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) { -- if (!voxelShape.isEmpty()) { -- return false; -- } -- } -- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper -- -- if (!this.getEntityCollisions(entity, box).isEmpty()) { -- return false; -- } else if (entity == null) { -- return true; -- } else { -- VoxelShape voxelShape2 = this.borderCollision(entity, box); -- return voxelShape2 == null || !Shapes.joinIsNotEmpty(voxelShape2, Shapes.create(box), BooleanOp.AND); -- } -+ // Paper start - optimise collisions -+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) -+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); -+ // Paper end - optimise collisions - } - - List getEntityCollisions(@Nullable Entity entity, AABB box); -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index c0817ef8927f00e2fd3fbf3289f8041fcb494049..3f458ddd4dc04ed28510a212be76bb19e7f6a61e 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -49,7 +49,7 @@ public interface EntityGetter { - return true; - } else { - for(Entity entity : this.getEntities(except, shape.bounds())) { -- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) { -+ if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && shape.intersects(entity.getBoundingBox())) { // Paper - return false; - } - } -@@ -67,7 +67,7 @@ public interface EntityGetter { - return List.of(); - } else { - Predicate predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith); -- List list = this.getEntities(entity, box.inflate(1.0E-7D), predicate); -+ List list = this.getEntities(entity, box.inflate(-1.0E-7D), predicate); // Paper - needs to be negated, or else we get things we don't collide with - if (list.isEmpty()) { - return List.of(); - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 24b820484497714eb8be87e07ca1d37829d4f2c9..fd74cc9c0dab84b176f7da3fbbbdbc8fd3a7e26d 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -734,6 +734,13 @@ public abstract class BlockBehaviour { - return this.conditionallyFullOpaque; - } - // Paper end -+ // Paper start -+ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK; -+ -+ public final long getBlockCollisionBehavior() { -+ return this.blockCollisionBehavior; -+ } -+ // Paper end - - public void initCache() { - this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() -@@ -743,7 +750,35 @@ public abstract class BlockBehaviour { - } - this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here - this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light -- -+ // Paper start -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(this)) { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK; -+ } else { -+ try { -+ // There is NOTHING HACKY ABOUT THIS AT ALLLLLLLLLLLLLLL -+ VoxelShape constantShape = this.getCollisionShape(null, null, null); -+ if (constantShape == null) { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; -+ } else { -+ constantShape = constantShape.optimize(); -+ if (constantShape.isEmpty()) { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_EMPTY_BLOCK; -+ } else { -+ final List boxes = constantShape.toAabbs(); -+ if (constantShape == net.minecraft.world.phys.shapes.Shapes.getFullUnoptimisedCube() || (boxes.size() == 1 && boxes.get(0).equals(net.minecraft.world.phys.shapes.Shapes.BLOCK_OPTIMISED.aabb))) { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_FULL_BLOCK; -+ } else { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; -+ } -+ } -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable throwable) { -+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK; -+ } -+ } -+ // Paper end - } - - public Block getBlock() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index c3f1334b2bb97f0633f3ea43b97ee49adfd8bc0d..b0c9fce9d4e06cac139e341d218d0b6aac1f1943 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -46,6 +46,110 @@ public class LevelChunkSection { - this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes - } - -+ // Paper start -+ protected int specialCollidingBlocks; -+ // blockIndex = x | (z << 4) | (y << 8) -+ private long[] knownBlockCollisionData; -+ -+ private long[] initKnownDataField() { -+ return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE]; -+ } -+ -+ public final boolean hasSpecialCollidingBlocks() { -+ return this.specialCollidingBlocks != 0; -+ } -+ -+ public static long getKnownBlockInfo(final int blockIndex, final long value) { -+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); -+ -+ return (value >>> (valueShift << 1)) & 0b11L; -+ } -+ -+ public final long getKnownBlockInfo(final int blockIndex) { -+ if (this.knownBlockCollisionData == null) { -+ return 0L; -+ } -+ -+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) -+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)); -+ -+ final long value = this.knownBlockCollisionData[arrayIndex]; -+ -+ return (value >>> (valueShift << 1)) & 0b11L; -+ } -+ -+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1 -+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits -+ public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) { -+ if (this.knownBlockCollisionData == null) { -+ return 0L; -+ } -+ -+ final int horizontalIndex = (localZ << 4) | (localY << 8); -+ return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)]; -+ } -+ -+ private void initBlockCollisionData() { -+ this.specialCollidingBlocks = 0; -+ // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw) -+ // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket. -+ // So only init if we contain non-empty blocks. -+ if (this.nonEmptyBlockCount == 0) { -+ this.knownBlockCollisionData = null; -+ return; -+ } -+ this.initKnownDataField(); -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ final BlockState state = this.states.get(index); -+ this.setKnownBlockInfo(index, state); -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) { -+ ++this.specialCollidingBlocks; -+ } -+ } -+ } -+ -+ // only use for initBlockCollisionData -+ private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) { -+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) -+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; -+ -+ long value = this.knownBlockCollisionData[arrayIndex]; -+ -+ value &= ~(0b11L << valueShift); -+ value |= blockState.getBlockCollisionBehavior() << valueShift; -+ -+ this.knownBlockCollisionData[arrayIndex] = value; -+ } -+ -+ public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) { -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) { -+ --this.specialCollidingBlocks; -+ } -+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) { -+ ++this.specialCollidingBlocks; -+ } -+ -+ if (this.nonEmptyBlockCount == 0) { -+ this.knownBlockCollisionData = null; -+ return; -+ } -+ -+ if (this.knownBlockCollisionData == null) { -+ this.initKnownDataField(); -+ } -+ -+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2) -+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1; -+ -+ long value = this.knownBlockCollisionData[arrayIndex]; -+ -+ value &= ~(0b11L << valueShift); -+ value |= to.getBlockCollisionBehavior() << valueShift; -+ -+ this.knownBlockCollisionData[arrayIndex] = value; -+ } -+ // Paper end -+ - public static int getBottomBlockY(int chunkPos) { - return chunkPos << 4; - } -@@ -70,8 +174,8 @@ public class LevelChunkSection { - return this.setBlockState(x, y, z, state, true); - } - -- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { -- BlockState iblockdata1; -+ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state -+ BlockState iblockdata1; // Paper - iblockdata1 -> oldState - - if (lock) { - iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state); -@@ -110,6 +214,7 @@ public class LevelChunkSection { - ++this.tickingFluidCount; - } - -+ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper - return iblockdata1; - } - -@@ -159,6 +264,7 @@ public class LevelChunkSection { - - }); - // Paper end -+ this.initBlockCollisionData(); // Paper - } - - public PalettedContainer getStates() { -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index 120498a39b7ca7aee9763084507508d4a1c425aa..68cc6f2a78a06293a29317fda72ab3ee79b3533a 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -25,6 +25,17 @@ public class AABB { - this.maxZ = Math.max(z1, z2); - } - -+ // Paper start -+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) { -+ this.minX = minX; -+ this.minY = minY; -+ this.minZ = minZ; -+ this.maxX = maxX; -+ this.maxY = maxY; -+ this.maxZ = maxZ; -+ } -+ // Paper end -+ - public AABB(BlockPos pos) { - this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1)); - } -diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -index 9d627b8e6bf3140b894d38b9a720896e2d776369..ca5f01be5d5ccfcc56780ff93cca3824409ffc0d 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -@@ -6,6 +6,9 @@ import java.util.Arrays; - import net.minecraft.Util; - import net.minecraft.core.Direction; - -+// Paper start -+import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; -+// Paper end - public class ArrayVoxelShape extends VoxelShape { - private final DoubleList xs; - private final DoubleList ys; -@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape { - } - - ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { -+ // Paper start - optimise multi-aabb shapes -+ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0); -+ } -+ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) { -+ // Paper end - optimise multi-aabb shapes - super(shape); - int i = shape.getXSize() + 1; - int j = shape.getYSize() + 1; -@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape { - } else { - throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); - } -+ // Paper start - optimise multi-aabb shapes -+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation; -+ this.offsetX = offsetX; -+ this.offsetY = offsetY; -+ this.offsetZ = offsetZ; -+ // Paper end - optimise multi-aabb shapes - } - - @Override -@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape { - throw new IllegalArgumentException(); - } - } -+ -+ // Paper start -+ public static final class DoubleListOffsetExposed extends AbstractDoubleList { -+ -+ public final DoubleArrayList list; -+ public final double offset; -+ -+ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) { -+ this.list = list; -+ this.offset = offset; -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ return this.list.getDouble(index) + this.offset; -+ } -+ -+ @Override -+ public int size() { -+ return this.list.size(); -+ } -+ } -+ -+ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0]; -+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation; -+ -+ final double offsetX; -+ final double offsetY; -+ final double offsetZ; -+ -+ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() { -+ return this.boundingBoxesRepresentation; -+ } -+ -+ public final double getOffsetX() { -+ return this.offsetX; -+ } -+ -+ public final double getOffsetY() { -+ return this.offsetY; -+ } -+ -+ public final double getOffsetZ() { -+ return this.offsetZ; -+ } -+ -+ @Override -+ public java.util.List toAabbs() { -+ if (this.boundingBoxesRepresentation == null) { -+ return super.toAabbs(); -+ } -+ java.util.List ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length); -+ -+ double offX = this.offsetX; -+ double offY = this.offsetY; -+ double offZ = this.offsetZ; -+ -+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ ret.add(boundingBox.move(offX, offY, offZ)); -+ } -+ -+ return ret; -+ } -+ -+ protected static DoubleArrayList getList(DoubleList from) { -+ if (from instanceof DoubleArrayList) { -+ return (DoubleArrayList)from; -+ } else { -+ return DoubleArrayList.wrap(from.toDoubleArray()); -+ } -+ } -+ -+ @Override -+ public VoxelShape move(double x, double y, double z) { -+ if (x == 0.0 && y == 0.0 && z == 0.0) { -+ return this; -+ } -+ DoubleListOffsetExposed xPoints, yPoints, zPoints; -+ double offsetX, offsetY, offsetZ; -+ -+ if (this.xs instanceof DoubleListOffsetExposed) { -+ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x); -+ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y); -+ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z); -+ } else { -+ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x); -+ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y); -+ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z); -+ } -+ -+ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ); -+ } -+ -+ @Override -+ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) { -+ // this can be optimised by checking an "overall shape" first, but not needed -+ double offX = this.offsetX; -+ double offY = this.offsetY; -+ double offZ = this.offsetZ; -+ -+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ, -+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) { -+ if (this.boundingBoxesRepresentation == null) { -+ super.forAllBoxes(doubleLineConsumer); -+ return; -+ } -+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, -+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ); -+ } -+ } -+ -+ @Override -+ public VoxelShape optimize() { -+ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) { -+ return this; -+ } -+ -+ VoxelShape simplified = Shapes.empty(); -+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, -+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR); -+ } -+ -+ if (!(simplified instanceof ArrayVoxelShape)) { -+ return simplified; -+ } -+ -+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation(); -+ -+ if (boundingBoxesRepresentation.length == 1) { -+ return new io.papermc.paper.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize(); -+ } -+ -+ return simplified; -+ } -+ // Paper end -+ - } -diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 9176735c08a75854209f24113b0e78332249dc4d..731c7dd15f131dc124be6af8f342b122cb89491b 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -@@ -19,16 +19,17 @@ public final class Shapes { - DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); - discreteVoxelShape.fill(0, 0, 0); - return new CubeVoxelShape(discreteVoxelShape); -- }); -+ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Paper - OBFHELPER - public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D}))); -+ public static final io.papermc.paper.voxel.AABBVoxelShape BLOCK_OPTIMISED = new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Paper - - public static VoxelShape empty() { - return EMPTY; - } - - public static VoxelShape block() { -- return BLOCK; -+ return BLOCK_OPTIMISED; // Paper - } - - public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { -@@ -41,29 +42,14 @@ public final class Shapes { - - public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { - if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) { -- int i = findBits(minX, maxX); -- int j = findBits(minY, maxY); -- int k = findBits(minZ, maxZ); -- if (i >= 0 && j >= 0 && k >= 0) { -- if (i == 0 && j == 0 && k == 0) { -- return block(); -- } else { -- int l = 1 << i; -- int m = 1 << j; -- int n = 1 << k; -- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n)); -- return new CubeVoxelShape(bitSetDiscreteVoxelShape); -- } -- } else { -- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ})); -- } -+ return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper - } else { - return empty(); - } - } - - public static VoxelShape create(AABB box) { -- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); -+ return new io.papermc.paper.voxel.AABBVoxelShape(box); // Paper - } - - @VisibleForTesting -@@ -125,6 +111,20 @@ public final class Shapes { - } - - public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { -+ // Paper start - optimise voxelshape -+ if (predicate == BooleanOp.AND) { -+ if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape) { -+ return io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb, ((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb); -+ } else if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) { -+ return ((ArrayVoxelShape)shape2).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb); -+ } else if (shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) { -+ return ((ArrayVoxelShape)shape1).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb); -+ } -+ } -+ return joinIsNotEmptyVanilla(shape1, shape2, predicate); -+ } -+ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { -+ // Paper end - optimise voxelshape - if (predicate.apply(false, false)) { - throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); - } else { -@@ -196,6 +196,43 @@ public final class Shapes { - } - - public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { -+ // Paper start - optimise shape creation here for lighting, as this shape is going to be used -+ // for transparency checks -+ if (shape == BLOCK || shape == BLOCK_OPTIMISED) { -+ return BLOCK_OPTIMISED; -+ } else if (shape == empty()) { -+ return empty(); -+ } -+ -+ if (shape instanceof io.papermc.paper.voxel.AABBVoxelShape) { -+ final AABB box = ((io.papermc.paper.voxel.AABBVoxelShape)shape).aabb; -+ switch (direction) { -+ case WEST: // -X -+ case EAST: { // +X -+ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minX, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize(); -+ } -+ case DOWN: // -Y -+ case UP: { // +Y -+ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minY, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize(); -+ } -+ case NORTH: // -Z -+ case SOUTH: { // +Z -+ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minZ,0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize(); -+ } -+ } -+ } -+ -+ // fall back to vanilla -+ return getFaceShapeVanilla(shape, direction); -+ } -+ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) { -+ // Paper end - if (shape == block()) { - return block(); - } else { -@@ -210,7 +247,7 @@ public final class Shapes { - i = 0; - } - -- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); -+ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Paper - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape - } - } - -@@ -235,6 +272,53 @@ public final class Shapes { - } - - public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { -+ // Paper start - try to optimise for the case where the shapes do _not_ occlude -+ // which is _most_ of the time in lighting -+ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED -+ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) { -+ return true; -+ } -+ boolean v1Empty = one == empty(); -+ boolean v2Empty = two == empty(); -+ if (v1Empty && v2Empty) { -+ return false; -+ } -+ if ((one instanceof io.papermc.paper.voxel.AABBVoxelShape || v1Empty) -+ && (two instanceof io.papermc.paper.voxel.AABBVoxelShape || v2Empty)) { -+ if (!v1Empty && !v2Empty && (one != two)) { -+ AABB boundingBox1 = ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb; -+ AABB boundingBox2 = ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb; -+ // can call it here in some cases -+ -+ // check overall bounding box -+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY); -+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY); -+ if (minY > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX); -+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX); -+ if (minX > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ); -+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ); -+ if (minZ > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ // fall through to full merge check -+ } else { -+ AABB boundingBox = v1Empty ? ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb : ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb; -+ // check if the bounding box encloses the full cube -+ return (boundingBox.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (boundingBox.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) && -+ (boundingBox.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)); -+ } -+ } -+ return faceShapeOccludesVanilla(one, two); -+ } -+ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) { -+ // Paper end - if (one != block() && two != block()) { - if (one.isEmpty() && two.isEmpty()) { - return false; -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 c4ca051720f790f5b8eb860b14e268de8557454d..2182afd1b95acf14c55bddfeec17dae0a63e1f00 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.Vec3; - - public abstract class VoxelShape { -- protected final DiscreteVoxelShape shape; -+ public final DiscreteVoxelShape shape; // Paper - public - @Nullable - private VoxelShape[] faces; - -- VoxelShape(DiscreteVoxelShape voxels) { -+ // Paper start -+ public boolean intersects(AABB shape) { -+ return Shapes.joinIsNotEmpty(this, new io.papermc.paper.voxel.AABBVoxelShape(shape), BooleanOp.AND); -+ } -+ // Paper end -+ -+ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected - this.shape = voxels; - } - -@@ -163,7 +169,7 @@ public abstract class VoxelShape { - } - } - -- private VoxelShape calculateFace(Direction direction) { -+ protected VoxelShape calculateFace(Direction direction) { // Paper - Direction.Axis axis = direction.getAxis(); - DoubleList doubleList = this.getCoords(axis); - if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) { diff --git a/patches/server/0800-Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/server/0800-Optimise-collision-checking-in-player-move-packet-ha.patch new file mode 100644 index 0000000000..a55596dbed --- /dev/null +++ b/patches/server/0800-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 2 Jul 2020 12:02:43 -0700 +Subject: [PATCH] Optimise collision checking in player move packet handling + +Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1d024bfbada440c93b1174568feaa67544f7f0d2..f5a2299e43e4e7863aff9f0fb5ed1773b1330c7a 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -655,7 +655,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + return; + } + +- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); ++ AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet + + d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above + d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above +@@ -663,6 +663,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + boolean flag1 = entity.verticalCollisionBelow; + + entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); ++ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + double d11 = d7; + + d6 = d3 - entity.getX(); +@@ -676,16 +677,24 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + boolean flag2 = false; + + if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot +- flag2 = true; ++ flag2 = true; // Paper - diff on change, this should be moved wrongly + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); + } + Location curPos = this.getCraftPlayer().getLocation(); // Spigot + + entity.absMoveTo(d3, d4, d5, f, f1); + this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit +- boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + +- if (flag && (flag2 || !flag3)) { ++ // Paper start - optimise out extra getCubes ++ boolean teleportBack = flag2; // violating this is always a fail ++ if (!teleportBack) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ AABB newBox = entity.getBoundingBox(); ++ if (didCollide || !oldBox.equals(newBox)) { ++ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ if (teleportBack) { // Paper end - optimise out extra getCubes + entity.absMoveTo(d0, d1, d2, f, f1); + this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.connection.send(new ClientboundMoveVehiclePacket(entity)); +@@ -771,7 +780,32 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + private boolean noBlocksAround(Entity entity) { +- return entity.level.getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir); ++ // Paper start - stop using streams, this is already a known fixed problem in Entity#move ++ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D); ++ int minX = Mth.floor(box.minX); ++ int minY = Mth.floor(box.minY); ++ int minZ = Mth.floor(box.minZ); ++ int maxX = Mth.floor(box.maxX); ++ int maxY = Mth.floor(box.maxY); ++ int maxZ = Mth.floor(box.maxZ); ++ ++ Level world = entity.level; ++ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ ++ for (int y = minY; y <= maxY; ++y) { ++ for (int z = minZ; z <= maxZ; ++z) { ++ for (int x = minX; x <= maxX; ++x) { ++ pos.set(x, y, z); ++ BlockState type = world.getBlockStateIfLoaded(pos); ++ if (type != null && !type.isAir()) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ // Paper end - stop using streams, this is already a known fixed problem in Entity#move + } + + @Override +@@ -1351,7 +1385,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + if (this.awaitingPositionFromClient != null) { +- if (this.tickCount - this.awaitingTeleportTime > 20) { ++ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT + this.awaitingTeleportTime = this.tickCount; + this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + } +@@ -1445,7 +1479,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + } + +- AABB axisalignedbb = this.player.getBoundingBox(); ++ AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB + + d7 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above + d8 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above +@@ -1486,6 +1520,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + boolean flag1 = this.player.verticalCollisionBelow; + + this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); ++ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move + // Paper start - prevent position desync + if (this.awaitingPositionFromClient != null) { +@@ -1505,12 +1540,23 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + boolean flag2 = false; + + if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot +- flag2 = true; ++ flag2 = true; // Paper - diff on change, this should be moved wrongly + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + } + + this.player.absMoveTo(d0, d1, d2, f, f1); +- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb))) { ++ // Paper start - optimise out extra getCubes ++ // Original for reference: ++ // boolean teleportBack = flag2 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)); ++ boolean teleportBack = flag2; // violating this is always a fail ++ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) { ++ AABB newBox = this.player.getBoundingBox(); ++ if (didCollide || !axisalignedbb.equals(newBox)) { ++ // note: only call after setLocation, or else getBoundingBox is wrong ++ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); ++ } // else: no collision at all detected, why do we care? ++ } ++ if (!this.player.noPhysics && !this.player.isSleeping() && teleportBack) { // Paper end - optimise out extra getCubes + this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet(), false); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. + } else { + // CraftBukkit start - fire PlayerMoveEvent +@@ -1596,6 +1642,27 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + } + ++ // Paper start - optimise out extra getCubes ++ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { ++ final List collisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); ++ try { ++ io.papermc.paper.util.CollisionUtil.getCollisions(world, entity, newBox, collisions, false, true, ++ true, false, null, null); ++ ++ for (int i = 0, len = collisions.size(); i < len; ++i) { ++ final AABB box = collisions.get(i); ++ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } finally { ++ io.papermc.paper.util.CachedLists.returnTempCollisionList(collisions); ++ } ++ } ++ // Paper end - optimise out extra getCubes ++ + private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) { + Iterable iterable = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D)); + VoxelShape voxelshape = Shapes.create(box.deflate(9.999999747378752E-6D)); diff --git a/patches/server/0801-Actually-unload-POI-data.patch b/patches/server/0801-Actually-unload-POI-data.patch new file mode 100644 index 0000000000..009a863557 --- /dev/null +++ b/patches/server/0801-Actually-unload-POI-data.patch @@ -0,0 +1,321 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 31 Aug 2020 11:08:17 -0700 +Subject: [PATCH] Actually unload POI data + +While it's not likely for a poi data leak to be meaningful, +sometimes it is. + +This patch also prevents the saving/unloading of POI data when +world saving is disabled. + +diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java +index 2a099fe0d514f181bf2b452d5333bc29b0d29e43..81ea64443a843736f9ada97900d173c302e39ba0 100644 +--- a/src/main/java/net/minecraft/server/ChunkSystem.java ++++ b/src/main/java/net/minecraft/server/ChunkSystem.java +@@ -270,6 +270,7 @@ public final class ChunkSystem { + for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { + chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z); + } ++ chunkMap.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data + } + + public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { +@@ -277,6 +278,7 @@ public final class ChunkSystem { + for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { + chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); + } ++ chunkMap.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data + } + + public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index fe10c770b511fa8a38ece2bf9679492a85b28eff..a5e74d30045a171f5ed66a115fbd429e9ab412af 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -1016,7 +1016,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + private void processUnloads(BooleanSupplier shouldKeepTicking) { + LongIterator longiterator = this.toDrop.iterator(); +- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { ++ for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { // Paper - diff on change + long j = longiterator.nextLong(); + ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy + +@@ -1164,6 +1164,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + this.poiManager.loadInData(pos, chunkHolder.poiData); + chunkHolder.tasks.forEach(Runnable::run); ++ this.getPoiManager().dequeueUnload(pos.longKey); // Paper + + if (chunkHolder.protoChunk != null) { + ProtoChunk protochunk = chunkHolder.protoChunk; +diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index 497a81e49d54380713c18523ae8f09f94c453721..210b0cdd4831421c8f43c3d823ac8e962b56bbbc 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity.ai.village.poi; + ++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper + import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; + import it.unimi.dsi.fastutil.longs.Long2ByteMap; +@@ -38,16 +39,145 @@ import net.minecraft.world.level.chunk.storage.SectionStorage; + public class PoiManager extends SectionStorage { + public static final int MAX_VILLAGE_DISTANCE = 6; + public static final int VILLAGE_SECTION_SIZE = 1; +- private final PoiManager.DistanceTracker distanceTracker; ++ // Paper start - unload poi data ++ // the vanilla tracker needs to be replaced because it does not support level removes ++ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D(); ++ static final int POI_DATA_SOURCE = 7; ++ public static int convertBetweenLevels(final int level) { ++ return POI_DATA_SOURCE - level; ++ } ++ ++ protected void updateDistanceTracking(long section) { ++ if (this.isVillageCenter(section)) { ++ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); ++ } else { ++ this.villageDistanceTracker.removeSource(section); ++ } ++ } ++ // Paper end - unload poi data + private final LongSet loadedChunks = new LongOpenHashSet(); + public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public + + public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { + super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); ++ if (world == null) { throw new IllegalStateException("world must be non-null"); } // Paper - require non-null + this.world = (net.minecraft.server.level.ServerLevel)world; // Paper +- this.distanceTracker = new PoiManager.DistanceTracker(); + } + ++ // Paper start - actually unload POI data ++ private final java.util.TreeSet queuedUnloads = new java.util.TreeSet<>(); ++ private final Long2ObjectOpenHashMap queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>(); ++ ++ static final class QueuedUnload implements Comparable { ++ ++ private final long unloadTick; ++ private final long coordinate; ++ ++ public QueuedUnload(long unloadTick, long coordinate) { ++ this.unloadTick = unloadTick; ++ this.coordinate = coordinate; ++ } ++ ++ @Override ++ public int compareTo(QueuedUnload other) { ++ if (other.unloadTick == this.unloadTick) { ++ return Long.compare(this.coordinate, other.coordinate); ++ } else { ++ return Long.compare(this.unloadTick, other.unloadTick); ++ } ++ } ++ ++ @Override ++ public int hashCode() { ++ int hash = 1; ++ hash = hash * 31 + Long.hashCode(this.unloadTick); ++ hash = hash * 31 + Long.hashCode(this.coordinate); ++ return hash; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (obj == null || obj.getClass() != QueuedUnload.class) { ++ return false; ++ } ++ QueuedUnload other = (QueuedUnload)obj; ++ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate; ++ } ++ } ++ ++ long determineDelay(long coordinate) { ++ if (this.isEmpty(coordinate)) { ++ return 5 * 60 * 20; ++ } else { ++ return 60 * 20; ++ } ++ } ++ ++ public void queueUnload(long coordinate, long minTarget) { ++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload queue"); ++ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate); ++ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload); ++ if (existing != null) { ++ this.queuedUnloads.remove(existing); ++ } ++ this.queuedUnloads.add(unload); ++ } ++ ++ public void dequeueUnload(long coordinate) { ++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload dequeue"); ++ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate); ++ if (unload != null) { ++ this.queuedUnloads.remove(unload); ++ } ++ } ++ ++ public void pollUnloads(BooleanSupplier canSleepForTick) { ++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload"); ++ long currentTick = net.minecraft.server.MinecraftServer.currentTickLong; ++ net.minecraft.server.level.ServerChunkCache chunkProvider = this.world.getChunkSource(); ++ net.minecraft.server.level.ChunkMap playerChunkMap = chunkProvider.chunkMap; ++ // copied target determination from PlayerChunkMap ++ ++ java.util.Iterator iterator = this.queuedUnloads.iterator(); ++ for (int i = 0; iterator.hasNext() && (i < 200 || this.queuedUnloads.size() > 2000 || canSleepForTick.getAsBoolean()); i++) { ++ QueuedUnload unload = iterator.next(); ++ if (unload.unloadTick > currentTick) { ++ break; ++ } ++ ++ long coordinate = unload.coordinate; ++ ++ iterator.remove(); ++ this.queuedUnloadsByCoordinate.remove(coordinate); ++ ++ if (playerChunkMap.getUnloadingChunkHolder(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null ++ || playerChunkMap.getUpdatingChunkIfPresent(coordinate) != null) { ++ continue; ++ } ++ ++ this.unloadData(coordinate); ++ } ++ } ++ ++ @Override ++ public void unloadData(long coordinate) { ++ io.papermc.paper.util.TickThread.softEnsureTickThread("async unloading poi data"); ++ super.unloadData(coordinate); ++ } ++ ++ @Override ++ protected void onUnload(long coordinate) { ++ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload callback"); ++ this.loadedChunks.remove(coordinate); ++ int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate); ++ int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate); ++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { ++ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); ++ this.updateDistanceTracking(sectionPos); ++ } ++ } ++ // Paper end - actually unload POI data ++ + public void add(BlockPos pos, Holder type) { + this.getOrCreate(SectionPos.asLong(pos)).add(pos, type); + } +@@ -201,8 +331,8 @@ public class PoiManager extends SectionStorage { + } + + public int sectionsToVillage(SectionPos pos) { +- this.distanceTracker.runAllUpdates(); +- return this.distanceTracker.getLevel(pos.asLong()); ++ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util ++ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util + } + + boolean isVillageCenter(long pos) { +@@ -217,7 +347,7 @@ public class PoiManager extends SectionStorage { + @Override + public void tick(BooleanSupplier shouldKeepTicking) { + // Paper start - async chunk io +- while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) { ++ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean() && !this.world.noSave()) { // Paper - unload POI data - don't write to disk if saving is disabled + ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk(); + + net.minecraft.nbt.CompoundTag data; +@@ -227,19 +357,24 @@ public class PoiManager extends SectionStorage { + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, + chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); + } ++ // Paper start - unload POI data ++ if (!this.world.noSave()) { // don't write to disk if saving is disabled ++ this.pollUnloads(shouldKeepTicking); ++ } ++ // Paper end - unload POI data + // Paper end +- this.distanceTracker.runAllUpdates(); ++ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking until + } + + @Override + protected void setDirty(long pos) { + super.setDirty(pos); +- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); ++ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util + } + + @Override + protected void onSectionLoad(long pos) { +- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); ++ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util + } + + public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) { +@@ -297,7 +432,7 @@ public class PoiManager extends SectionStorage { + + @Override + protected int getLevelFromSource(long id) { +- return PoiManager.this.isVillageCenter(id) ? 0 : 7; ++ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - unload poi data - diff on change, this specifies the source level to use for distance tracking + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 47cda78efcce597d3d7ba8fc93a2865e10cdc237..38287fad39d553a86370bbdc755c0a006615e0cf 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -58,6 +58,40 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl + // Paper - remove mojang I/O thread + } + ++ // Paper start - actually unload POI data ++ public void unloadData(long coordinate) { ++ ChunkPos chunkPos = new ChunkPos(coordinate); ++ this.flush(chunkPos); ++ ++ Long2ObjectMap> data = this.storage; ++ int before = data.size(); ++ ++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { ++ data.remove(SectionPos.asLong(chunkPos.x, section, chunkPos.z)); ++ } ++ ++ if (before != data.size()) { ++ this.onUnload(coordinate); ++ } ++ } ++ ++ protected void onUnload(long coordinate) {} ++ ++ public boolean isEmpty(long coordinate) { ++ Long2ObjectMap> data = this.storage; ++ int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate); ++ int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate); ++ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { ++ Optional optional = data.get(SectionPos.asLong(x, section, z)); ++ if (optional != null && optional.orElse(null) != null) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++ // Paper end - actually unload POI data ++ + protected void tick(BooleanSupplier shouldKeepTicking) { + while(this.hasWork() && shouldKeepTicking.getAsBoolean()) { + ChunkPos chunkPos = SectionPos.of(this.dirty.firstLong()).chunk(); +@@ -175,6 +209,7 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl + }); + } + } ++ if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data + + } + diff --git a/patches/server/0801-Optimise-collision-checking-in-player-move-packet-ha.patch b/patches/server/0801-Optimise-collision-checking-in-player-move-packet-ha.patch deleted file mode 100644 index 357ae5a705..0000000000 --- a/patches/server/0801-Optimise-collision-checking-in-player-move-packet-ha.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 2 Jul 2020 12:02:43 -0700 -Subject: [PATCH] Optimise collision checking in player move packet handling - -Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 583f61adf2122ff94be79814a04616a42a827f75..5c9d7320536be70100f7e1d843e8c4e0c0802a19 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -655,7 +655,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - return; - } - -- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); -+ AABB oldBox = entity.getBoundingBox(); // Paper - copy from player movement packet - - d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above - d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above -@@ -663,6 +663,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - boolean flag1 = entity.verticalCollisionBelow; - - entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); -+ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - double d11 = d7; - - d6 = d3 - entity.getX(); -@@ -676,16 +677,24 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - boolean flag2 = false; - - if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot -- flag2 = true; -+ flag2 = true; // Paper - diff on change, this should be moved wrongly - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); - } - Location curPos = this.getCraftPlayer().getLocation(); // Spigot - - entity.absMoveTo(d3, d4, d5, f, f1); - this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit -- boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- if (flag && (flag2 || !flag3)) { -+ // Paper start - optimise out extra getCubes -+ boolean teleportBack = flag2; // violating this is always a fail -+ if (!teleportBack) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ AABB newBox = entity.getBoundingBox(); -+ if (didCollide || !oldBox.equals(newBox)) { -+ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ if (teleportBack) { // Paper end - optimise out extra getCubes - entity.absMoveTo(d0, d1, d2, f, f1); - this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit - this.connection.send(new ClientboundMoveVehiclePacket(entity)); -@@ -771,7 +780,32 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - private boolean noBlocksAround(Entity entity) { -- return entity.level.getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir); -+ // Paper start - stop using streams, this is already a known fixed problem in Entity#move -+ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D); -+ int minX = Mth.floor(box.minX); -+ int minY = Mth.floor(box.minY); -+ int minZ = Mth.floor(box.minZ); -+ int maxX = Mth.floor(box.maxX); -+ int maxY = Mth.floor(box.maxY); -+ int maxZ = Mth.floor(box.maxZ); -+ -+ Level world = entity.level; -+ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ -+ for (int y = minY; y <= maxY; ++y) { -+ for (int z = minZ; z <= maxZ; ++z) { -+ for (int x = minX; x <= maxX; ++x) { -+ pos.set(x, y, z); -+ BlockState type = world.getBlockStateIfLoaded(pos); -+ if (type != null && !type.isAir()) { -+ return false; -+ } -+ } -+ } -+ } -+ -+ return true; -+ // Paper end - stop using streams, this is already a known fixed problem in Entity#move - } - - @Override -@@ -1351,7 +1385,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - if (this.awaitingPositionFromClient != null) { -- if (this.tickCount - this.awaitingTeleportTime > 20) { -+ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Paper - this will greatly screw with clients with > 1000ms RTT - this.awaitingTeleportTime = this.tickCount; - this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); - } -@@ -1445,7 +1479,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - } - -- AABB axisalignedbb = this.player.getBoundingBox(); -+ AABB axisalignedbb = this.player.getBoundingBox(); // Paper - diff on change, should be old AABB - - d7 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above - d8 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -@@ -1486,6 +1520,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - boolean flag1 = this.player.verticalCollisionBelow; - - this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); -+ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Paper - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move - // Paper start - prevent position desync - if (this.awaitingPositionFromClient != null) { -@@ -1505,12 +1540,23 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - boolean flag2 = false; - - if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot -- flag2 = true; -+ flag2 = true; // Paper - diff on change, this should be moved wrongly - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); - } - - this.player.absMoveTo(d0, d1, d2, f, f1); -- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb))) { -+ // Paper start - optimise out extra getCubes -+ // Original for reference: -+ // boolean teleportBack = flag2 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)); -+ boolean teleportBack = flag2; // violating this is always a fail -+ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) { -+ AABB newBox = this.player.getBoundingBox(); -+ if (didCollide || !axisalignedbb.equals(newBox)) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ if (!this.player.noPhysics && !this.player.isSleeping() && teleportBack) { // Paper end - optimise out extra getCubes - this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet(), false); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. - } else { - // CraftBukkit start - fire PlayerMoveEvent -@@ -1596,6 +1642,27 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - } - -+ // Paper start - optimise out extra getCubes -+ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { -+ final List collisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); -+ try { -+ io.papermc.paper.util.CollisionUtil.getCollisions(world, entity, newBox, collisions, false, true, -+ true, false, null, null); -+ -+ for (int i = 0, len = collisions.size(); i < len; ++i) { -+ final AABB box = collisions.get(i); -+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } finally { -+ io.papermc.paper.util.CachedLists.returnTempCollisionList(collisions); -+ } -+ } -+ // Paper end - optimise out extra getCubes -+ - private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) { - Iterable iterable = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D)); - VoxelShape voxelshape = Shapes.create(box.deflate(9.999999747378752E-6D)); diff --git a/patches/server/0802-Actually-unload-POI-data.patch b/patches/server/0802-Actually-unload-POI-data.patch deleted file mode 100644 index 009a863557..0000000000 --- a/patches/server/0802-Actually-unload-POI-data.patch +++ /dev/null @@ -1,321 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 31 Aug 2020 11:08:17 -0700 -Subject: [PATCH] Actually unload POI data - -While it's not likely for a poi data leak to be meaningful, -sometimes it is. - -This patch also prevents the saving/unloading of POI data when -world saving is disabled. - -diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java -index 2a099fe0d514f181bf2b452d5333bc29b0d29e43..81ea64443a843736f9ada97900d173c302e39ba0 100644 ---- a/src/main/java/net/minecraft/server/ChunkSystem.java -+++ b/src/main/java/net/minecraft/server/ChunkSystem.java -@@ -270,6 +270,7 @@ public final class ChunkSystem { - for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { - chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z); - } -+ chunkMap.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data - } - - public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { -@@ -277,6 +278,7 @@ public final class ChunkSystem { - for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { - chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); - } -+ chunkMap.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data - } - - public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index fe10c770b511fa8a38ece2bf9679492a85b28eff..a5e74d30045a171f5ed66a115fbd429e9ab412af 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1016,7 +1016,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - private void processUnloads(BooleanSupplier shouldKeepTicking) { - LongIterator longiterator = this.toDrop.iterator(); -- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { -+ for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { // Paper - diff on change - long j = longiterator.nextLong(); - ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy - -@@ -1164,6 +1164,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - this.poiManager.loadInData(pos, chunkHolder.poiData); - chunkHolder.tasks.forEach(Runnable::run); -+ this.getPoiManager().dequeueUnload(pos.longKey); // Paper - - if (chunkHolder.protoChunk != null) { - ProtoChunk protochunk = chunkHolder.protoChunk; -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 497a81e49d54380713c18523ae8f09f94c453721..210b0cdd4831421c8f43c3d823ac8e962b56bbbc 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -1,5 +1,6 @@ - package net.minecraft.world.entity.ai.village.poi; - -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper - import com.mojang.datafixers.DataFixer; - import com.mojang.datafixers.util.Pair; - import it.unimi.dsi.fastutil.longs.Long2ByteMap; -@@ -38,16 +39,145 @@ import net.minecraft.world.level.chunk.storage.SectionStorage; - public class PoiManager extends SectionStorage { - public static final int MAX_VILLAGE_DISTANCE = 6; - public static final int VILLAGE_SECTION_SIZE = 1; -- private final PoiManager.DistanceTracker distanceTracker; -+ // Paper start - unload poi data -+ // the vanilla tracker needs to be replaced because it does not support level removes -+ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D(); -+ static final int POI_DATA_SOURCE = 7; -+ public static int convertBetweenLevels(final int level) { -+ return POI_DATA_SOURCE - level; -+ } -+ -+ protected void updateDistanceTracking(long section) { -+ if (this.isVillageCenter(section)) { -+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE); -+ } else { -+ this.villageDistanceTracker.removeSource(section); -+ } -+ } -+ // Paper end - unload poi data - private final LongSet loadedChunks = new LongOpenHashSet(); - public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public - - public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { - super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); -+ if (world == null) { throw new IllegalStateException("world must be non-null"); } // Paper - require non-null - this.world = (net.minecraft.server.level.ServerLevel)world; // Paper -- this.distanceTracker = new PoiManager.DistanceTracker(); - } - -+ // Paper start - actually unload POI data -+ private final java.util.TreeSet queuedUnloads = new java.util.TreeSet<>(); -+ private final Long2ObjectOpenHashMap queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>(); -+ -+ static final class QueuedUnload implements Comparable { -+ -+ private final long unloadTick; -+ private final long coordinate; -+ -+ public QueuedUnload(long unloadTick, long coordinate) { -+ this.unloadTick = unloadTick; -+ this.coordinate = coordinate; -+ } -+ -+ @Override -+ public int compareTo(QueuedUnload other) { -+ if (other.unloadTick == this.unloadTick) { -+ return Long.compare(this.coordinate, other.coordinate); -+ } else { -+ return Long.compare(this.unloadTick, other.unloadTick); -+ } -+ } -+ -+ @Override -+ public int hashCode() { -+ int hash = 1; -+ hash = hash * 31 + Long.hashCode(this.unloadTick); -+ hash = hash * 31 + Long.hashCode(this.coordinate); -+ return hash; -+ } -+ -+ @Override -+ public boolean equals(Object obj) { -+ if (obj == null || obj.getClass() != QueuedUnload.class) { -+ return false; -+ } -+ QueuedUnload other = (QueuedUnload)obj; -+ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate; -+ } -+ } -+ -+ long determineDelay(long coordinate) { -+ if (this.isEmpty(coordinate)) { -+ return 5 * 60 * 20; -+ } else { -+ return 60 * 20; -+ } -+ } -+ -+ public void queueUnload(long coordinate, long minTarget) { -+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload queue"); -+ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate); -+ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload); -+ if (existing != null) { -+ this.queuedUnloads.remove(existing); -+ } -+ this.queuedUnloads.add(unload); -+ } -+ -+ public void dequeueUnload(long coordinate) { -+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload dequeue"); -+ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate); -+ if (unload != null) { -+ this.queuedUnloads.remove(unload); -+ } -+ } -+ -+ public void pollUnloads(BooleanSupplier canSleepForTick) { -+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload"); -+ long currentTick = net.minecraft.server.MinecraftServer.currentTickLong; -+ net.minecraft.server.level.ServerChunkCache chunkProvider = this.world.getChunkSource(); -+ net.minecraft.server.level.ChunkMap playerChunkMap = chunkProvider.chunkMap; -+ // copied target determination from PlayerChunkMap -+ -+ java.util.Iterator iterator = this.queuedUnloads.iterator(); -+ for (int i = 0; iterator.hasNext() && (i < 200 || this.queuedUnloads.size() > 2000 || canSleepForTick.getAsBoolean()); i++) { -+ QueuedUnload unload = iterator.next(); -+ if (unload.unloadTick > currentTick) { -+ break; -+ } -+ -+ long coordinate = unload.coordinate; -+ -+ iterator.remove(); -+ this.queuedUnloadsByCoordinate.remove(coordinate); -+ -+ if (playerChunkMap.getUnloadingChunkHolder(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null -+ || playerChunkMap.getUpdatingChunkIfPresent(coordinate) != null) { -+ continue; -+ } -+ -+ this.unloadData(coordinate); -+ } -+ } -+ -+ @Override -+ public void unloadData(long coordinate) { -+ io.papermc.paper.util.TickThread.softEnsureTickThread("async unloading poi data"); -+ super.unloadData(coordinate); -+ } -+ -+ @Override -+ protected void onUnload(long coordinate) { -+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload callback"); -+ this.loadedChunks.remove(coordinate); -+ int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate); -+ int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate); -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ); -+ this.updateDistanceTracking(sectionPos); -+ } -+ } -+ // Paper end - actually unload POI data -+ - public void add(BlockPos pos, Holder type) { - this.getOrCreate(SectionPos.asLong(pos)).add(pos, type); - } -@@ -201,8 +331,8 @@ public class PoiManager extends SectionStorage { - } - - public int sectionsToVillage(SectionPos pos) { -- this.distanceTracker.runAllUpdates(); -- return this.distanceTracker.getLevel(pos.asLong()); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util -+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util - } - - boolean isVillageCenter(long pos) { -@@ -217,7 +347,7 @@ public class PoiManager extends SectionStorage { - @Override - public void tick(BooleanSupplier shouldKeepTicking) { - // Paper start - async chunk io -- while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) { -+ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean() && !this.world.noSave()) { // Paper - unload POI data - don't write to disk if saving is disabled - ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk(); - - net.minecraft.nbt.CompoundTag data; -@@ -227,19 +357,24 @@ public class PoiManager extends SectionStorage { - com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, - chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); - } -+ // Paper start - unload POI data -+ if (!this.world.noSave()) { // don't write to disk if saving is disabled -+ this.pollUnloads(shouldKeepTicking); -+ } -+ // Paper end - unload POI data - // Paper end -- this.distanceTracker.runAllUpdates(); -+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking until - } - - @Override - protected void setDirty(long pos) { - super.setDirty(pos); -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util - } - - @Override - protected void onSectionLoad(long pos) { -- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false); -+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util - } - - public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) { -@@ -297,7 +432,7 @@ public class PoiManager extends SectionStorage { - - @Override - protected int getLevelFromSource(long id) { -- return PoiManager.this.isVillageCenter(id) ? 0 : 7; -+ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - unload poi data - diff on change, this specifies the source level to use for distance tracking - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 47cda78efcce597d3d7ba8fc93a2865e10cdc237..38287fad39d553a86370bbdc755c0a006615e0cf 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -58,6 +58,40 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - // Paper - remove mojang I/O thread - } - -+ // Paper start - actually unload POI data -+ public void unloadData(long coordinate) { -+ ChunkPos chunkPos = new ChunkPos(coordinate); -+ this.flush(chunkPos); -+ -+ Long2ObjectMap> data = this.storage; -+ int before = data.size(); -+ -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ data.remove(SectionPos.asLong(chunkPos.x, section, chunkPos.z)); -+ } -+ -+ if (before != data.size()) { -+ this.onUnload(coordinate); -+ } -+ } -+ -+ protected void onUnload(long coordinate) {} -+ -+ public boolean isEmpty(long coordinate) { -+ Long2ObjectMap> data = this.storage; -+ int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate); -+ int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate); -+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) { -+ Optional optional = data.get(SectionPos.asLong(x, section, z)); -+ if (optional != null && optional.orElse(null) != null) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ // Paper end - actually unload POI data -+ - protected void tick(BooleanSupplier shouldKeepTicking) { - while(this.hasWork() && shouldKeepTicking.getAsBoolean()) { - ChunkPos chunkPos = SectionPos.of(this.dirty.firstLong()).chunk(); -@@ -175,6 +209,7 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - }); - } - } -+ if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data - - } - diff --git a/patches/server/0802-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0802-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch new file mode 100644 index 0000000000..344c081f95 --- /dev/null +++ b/patches/server/0802-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 9 Dec 2021 00:08:11 -0800 +Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize + PalettedContainer copying by not using codecs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index 0b9312dc5ee43d2d450dc6e9f07a9ac0320955ca..4c109bbc4694e9d3d8804cc64650f79abf315e3a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -283,13 +283,17 @@ public class CraftChunk implements Chunk { + PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; + + Registry iregistry = this.worldServer.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); +- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); + + for (int i = 0; i < cs.length; i++) { +- CompoundTag data = new CompoundTag(); + +- data.put("block_states", ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).get().left().get()); +- sectionBlockIDs[i] = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).get().left().get(); ++ // Paper start ++ sectionEmpty[i] = cs[i].hasOnlyAir(); // Paper - fix sectionEmpty array not being filled ++ if (!sectionEmpty[i]) { ++ sectionBlockIDs[i] = cs[i].getStates().copy(); // Paper - use copy instead of round tripping with codecs ++ } else { ++ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // Paper - use cached instance for empty block sections ++ } ++ // Paper end + + LevelLightEngine lightengine = chunk.level.getLightEngine(); + DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(x, i, z)); +@@ -308,8 +312,7 @@ public class CraftChunk implements Chunk { + } + + if (biome != null) { +- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).get().left().get()); +- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).get().left().get(); ++ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - use copy instead of round tripping with codecs + } + } + diff --git a/patches/server/0803-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0803-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch deleted file mode 100644 index 4eb953eb31..0000000000 --- a/patches/server/0803-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 9 Dec 2021 00:08:11 -0800 -Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize - PalettedContainer copying by not using codecs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index f0b41e4d08691f2f97b0a4396b5d6c8fd75b2c86..947b0b10fb965f30513fd0df5bc0910fb9cb9a71 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -283,13 +283,17 @@ public class CraftChunk implements Chunk { - PalettedContainerRO>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null; - - Registry iregistry = this.worldServer.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); -- Codec>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS)); - - for (int i = 0; i < cs.length; i++) { -- CompoundTag data = new CompoundTag(); - -- data.put("block_states", ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).get().left().get()); -- sectionBlockIDs[i] = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).get().left().get(); -+ // Paper start -+ sectionEmpty[i] = cs[i].hasOnlyAir(); // Paper - fix sectionEmpty array not being filled -+ if (!sectionEmpty[i]) { -+ sectionBlockIDs[i] = cs[i].getStates().copy(); // Paper - use copy instead of round tripping with codecs -+ } else { -+ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // Paper - use cached instance for empty block sections -+ } -+ // Paper end - - LevelLightEngine lightengine = chunk.level.getLightEngine(); - DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(x, i, z)); -@@ -308,8 +312,7 @@ public class CraftChunk implements Chunk { - } - - if (biome != null) { -- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).get().left().get()); -- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).get().left().get(); -+ biome[i] = ((PalettedContainer>) cs[i].getBiomes()).copy(); // Paper - use copy instead of round tripping with codecs - } - } - diff --git a/patches/server/0803-Update-Log4j.patch b/patches/server/0803-Update-Log4j.patch new file mode 100644 index 0000000000..41cc02c122 --- /dev/null +++ b/patches/server/0803-Update-Log4j.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: darbyjack +Date: Thu, 9 Dec 2021 19:17:02 -0600 +Subject: [PATCH] Update Log4j + + +diff --git a/build.gradle.kts b/build.gradle.kts +index fb6bfd4967b4ec113463cfaa77e621183f93e441..effc19371309a1af44e1b660b547b58530a8df3c 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -20,10 +20,11 @@ dependencies { + all its classes to check if they are plugins. + Scanning takes about 1-2 seconds so adding this speeds up the server start. + */ +- implementation("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - implementation +- annotationProcessor("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - Needed to generate meta for our Log4j plugins ++ implementation("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - implementation ++ annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - Needed to generate meta for our Log4j plugins + // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper ++ implementation("org.apache.logging.log4j:log4j-slf4j18-impl:2.17.1") // Paper + implementation("org.ow2.asm:asm:9.3") + implementation("org.ow2.asm:asm-commons:9.3") // Paper - ASM event executor generation + implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files diff --git a/patches/server/0804-Add-more-Campfire-API.patch b/patches/server/0804-Add-more-Campfire-API.patch new file mode 100644 index 0000000000..5381096620 --- /dev/null +++ b/patches/server/0804-Add-more-Campfire-API.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: LemonCaramel +Date: Fri, 16 Jul 2021 00:39:03 +0900 +Subject: [PATCH] Add more Campfire API + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +index 18b22efe9f5335bb49aa0e899727d1911dc20718..8e198941b4ad7845d73ab1cfea5ac07b0014fe22 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +@@ -39,6 +39,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + public final int[] cookingProgress; + public final int[] cookingTime; + private final RecipeManager.CachedCheck quickCheck; ++ public final boolean[] stopCooking; // Paper + + public CampfireBlockEntity(BlockPos pos, BlockState state) { + super(BlockEntityType.CAMPFIRE, pos, state); +@@ -46,6 +47,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + this.cookingProgress = new int[4]; + this.cookingTime = new int[4]; + this.quickCheck = RecipeManager.createCheck(RecipeType.CAMPFIRE_COOKING); ++ this.stopCooking = new boolean[4]; // Paper + } + + public static void cookTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) { +@@ -56,7 +58,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + + if (!itemstack.isEmpty()) { + flag = true; ++ if (!campfire.stopCooking[i]) { // Paper + int j = campfire.cookingProgress[i]++; ++ } // Paper + + if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { + SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack}); +@@ -163,6 +167,16 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length)); + } + ++ // Paper start ++ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) { ++ byte[] abyte = nbt.getByteArray("Paper.StopCooking"); ++ boolean[] cookingState = new boolean[4]; ++ for (int index = 0; index < abyte.length; index++) { ++ cookingState[index] = abyte[index] == 1; ++ } ++ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length)); ++ } ++ // Paper end + } + + @Override +@@ -171,6 +185,13 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { + ContainerHelper.saveAllItems(nbt, this.items, true); + nbt.putIntArray("CookingTimes", this.cookingProgress); + nbt.putIntArray("CookingTotalTimes", this.cookingTime); ++ // Paper start ++ byte[] cookingState = new byte[4]; ++ for (int index = 0; index < cookingState.length; index++) { ++ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0); ++ } ++ nbt.putByteArray("Paper.StopCooking", cookingState); ++ // Paper end + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java +index 391ff41951f51a5f9225bf4a9e28c0a4d064d8c9..eafba0532920a3162575dbe656e07734605590f5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java +@@ -47,4 +47,40 @@ public class CraftCampfire extends CraftBlockEntityState im + public void setCookTimeTotal(int index, int cookTimeTotal) { + getSnapshot().cookingTime[index] = cookTimeTotal; + } ++ ++ // Paper start ++ @Override ++ public void stopCooking() { ++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) ++ this.stopCooking(i); ++ } ++ ++ @Override ++ public void startCooking() { ++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) ++ this.startCooking(i); ++ } ++ ++ @Override ++ public boolean stopCooking(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ boolean previous = this.isCookingDisabled(index); ++ getSnapshot().stopCooking[index] = true; ++ return previous; ++ } ++ ++ @Override ++ public boolean startCooking(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ boolean previous = this.isCookingDisabled(index); ++ getSnapshot().stopCooking[index] = false; ++ return previous; ++ } ++ ++ @Override ++ public boolean isCookingDisabled(int index) { ++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); ++ return getSnapshot().stopCooking[index]; ++ } ++ // Paper end + } diff --git a/patches/server/0804-Update-Log4j.patch b/patches/server/0804-Update-Log4j.patch deleted file mode 100644 index 41cc02c122..0000000000 --- a/patches/server/0804-Update-Log4j.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: darbyjack -Date: Thu, 9 Dec 2021 19:17:02 -0600 -Subject: [PATCH] Update Log4j - - -diff --git a/build.gradle.kts b/build.gradle.kts -index fb6bfd4967b4ec113463cfaa77e621183f93e441..effc19371309a1af44e1b660b547b58530a8df3c 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -20,10 +20,11 @@ dependencies { - all its classes to check if they are plugins. - Scanning takes about 1-2 seconds so adding this speeds up the server start. - */ -- implementation("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - implementation -- annotationProcessor("org.apache.logging.log4j:log4j-core:2.14.1") // Paper - Needed to generate meta for our Log4j plugins -+ implementation("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - implementation -+ annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - Needed to generate meta for our Log4j plugins - // Paper end - implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper -+ implementation("org.apache.logging.log4j:log4j-slf4j18-impl:2.17.1") // Paper - implementation("org.ow2.asm:asm:9.3") - implementation("org.ow2.asm:asm-commons:9.3") // Paper - ASM event executor generation - implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files diff --git a/patches/server/0805-Add-more-Campfire-API.patch b/patches/server/0805-Add-more-Campfire-API.patch deleted file mode 100644 index 5381096620..0000000000 --- a/patches/server/0805-Add-more-Campfire-API.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: LemonCaramel -Date: Fri, 16 Jul 2021 00:39:03 +0900 -Subject: [PATCH] Add more Campfire API - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -index 18b22efe9f5335bb49aa0e899727d1911dc20718..8e198941b4ad7845d73ab1cfea5ac07b0014fe22 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java -@@ -39,6 +39,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - public final int[] cookingProgress; - public final int[] cookingTime; - private final RecipeManager.CachedCheck quickCheck; -+ public final boolean[] stopCooking; // Paper - - public CampfireBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.CAMPFIRE, pos, state); -@@ -46,6 +47,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - this.cookingProgress = new int[4]; - this.cookingTime = new int[4]; - this.quickCheck = RecipeManager.createCheck(RecipeType.CAMPFIRE_COOKING); -+ this.stopCooking = new boolean[4]; // Paper - } - - public static void cookTick(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire) { -@@ -56,7 +58,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - - if (!itemstack.isEmpty()) { - flag = true; -+ if (!campfire.stopCooking[i]) { // Paper - int j = campfire.cookingProgress[i]++; -+ } // Paper - - if (campfire.cookingProgress[i] >= campfire.cookingTime[i]) { - SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack}); -@@ -163,6 +167,16 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length)); - } - -+ // Paper start -+ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) { -+ byte[] abyte = nbt.getByteArray("Paper.StopCooking"); -+ boolean[] cookingState = new boolean[4]; -+ for (int index = 0; index < abyte.length; index++) { -+ cookingState[index] = abyte[index] == 1; -+ } -+ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length)); -+ } -+ // Paper end - } - - @Override -@@ -171,6 +185,13 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { - ContainerHelper.saveAllItems(nbt, this.items, true); - nbt.putIntArray("CookingTimes", this.cookingProgress); - nbt.putIntArray("CookingTotalTimes", this.cookingTime); -+ // Paper start -+ byte[] cookingState = new byte[4]; -+ for (int index = 0; index < cookingState.length; index++) { -+ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0); -+ } -+ nbt.putByteArray("Paper.StopCooking", cookingState); -+ // Paper end - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -index 391ff41951f51a5f9225bf4a9e28c0a4d064d8c9..eafba0532920a3162575dbe656e07734605590f5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java -@@ -47,4 +47,40 @@ public class CraftCampfire extends CraftBlockEntityState im - public void setCookTimeTotal(int index, int cookTimeTotal) { - getSnapshot().cookingTime[index] = cookTimeTotal; - } -+ -+ // Paper start -+ @Override -+ public void stopCooking() { -+ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) -+ this.stopCooking(i); -+ } -+ -+ @Override -+ public void startCooking() { -+ for (int i = 0; i < getSnapshot().stopCooking.length; ++i) -+ this.startCooking(i); -+ } -+ -+ @Override -+ public boolean stopCooking(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ boolean previous = this.isCookingDisabled(index); -+ getSnapshot().stopCooking[index] = true; -+ return previous; -+ } -+ -+ @Override -+ public boolean startCooking(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ boolean previous = this.isCookingDisabled(index); -+ getSnapshot().stopCooking[index] = false; -+ return previous; -+ } -+ -+ @Override -+ public boolean isCookingDisabled(int index) { -+ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)"); -+ return getSnapshot().stopCooking[index]; -+ } -+ // Paper end - } diff --git a/patches/server/0805-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0805-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch new file mode 100644 index 0000000000..fca5a00257 --- /dev/null +++ b/patches/server/0805-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 19 Dec 2021 09:13:41 -0800 +Subject: [PATCH] Only write chunk data to disk if it serializes without + throwing + +This ensures at least a valid version of the chunk exists +on disk, even if outdated + +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 8ff8855c5267379b3a5f5d8baa4a275ffee2c4bf..fc3442b4c7e1f22080fe6bf36d4fade162d6709e 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 +@@ -1004,6 +1004,9 @@ public class RegionFile implements AutoCloseable { + } + + } ++ ++ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails ++ + // Paper end + private class ChunkBuffer extends ByteArrayOutputStream { + +@@ -1019,6 +1022,24 @@ public class RegionFile implements AutoCloseable { + this.pos = chunkcoordintpair; + } + ++ // Paper start - don't write garbage data to disk if writing serialization fails ++ @Override ++ public void write(final int b) { ++ if (this.count > MAX_CHUNK_SIZE) { ++ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + this.count); ++ } ++ super.write(b); ++ } ++ ++ @Override ++ public void write(final byte[] b, final int off, final int len) { ++ if (this.count + len > MAX_CHUNK_SIZE) { ++ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + (this.count + len)); ++ } ++ super.write(b, off, len); ++ } ++ // Paper end ++ + public void close() throws IOException { + ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index f38ec8914e1953091ab65aa3aaefc886d3eede8a..c2356ed1a00fd8087cca285be5e7f6a5442e73fb 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -298,10 +298,17 @@ public class RegionFileStorage implements AutoCloseable { + NbtIo.write(nbt, (DataOutput) dataoutputstream); + regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk + regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone ++ dataoutputstream.close(); // Paper - only write if successful ++ // Paper start - don't write garbage data to disk if writing serialization fails ++ } catch (RegionFileSizeException e) { ++ attempts = 5; // Don't retry ++ regionfile.clear(pos); ++ throw e; ++ // Paper end - don't write garbage data to disk if writing serialization fails + } catch (Throwable throwable) { + if (dataoutputstream != null) { + try { +- dataoutputstream.close(); ++ //dataoutputstream.close(); // Paper - don't write garbage data to disk if writing serialization fails + } catch (Throwable throwable1) { + throwable.addSuppressed(throwable1); + } +@@ -309,10 +316,7 @@ public class RegionFileStorage implements AutoCloseable { + + throw throwable; + } +- +- if (dataoutputstream != null) { +- dataoutputstream.close(); +- } ++ // Paper - move into try block to only write if successfully serialized + } + // Paper start + return; +@@ -358,4 +362,13 @@ public class RegionFileStorage implements AutoCloseable { + } + + } ++ ++ // Paper start ++ public static final class RegionFileSizeException extends RuntimeException { ++ ++ public RegionFileSizeException(String message) { ++ super(message); ++ } ++ } ++ // Paper end + } diff --git a/patches/server/0806-Fix-tripwire-state-inconsistency.patch b/patches/server/0806-Fix-tripwire-state-inconsistency.patch new file mode 100644 index 0000000000..eba71d4136 --- /dev/null +++ b/patches/server/0806-Fix-tripwire-state-inconsistency.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sun, 19 Dec 2021 21:11:20 +0100 +Subject: [PATCH] Fix tripwire state inconsistency + +This patch prevents updating and re-setting the tripwire when being removed in certain conditions + +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +index 4e2fb4ee8e46b3c363992ff23e26f5a648c5f003..7f60175bf671d282c11e9084670d2bb900968255 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java +@@ -74,7 +74,7 @@ public class TripWireBlock extends Block { + @Override + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { + if (!moved && !state.is(newState.getBlock())) { +- this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true)); ++ this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true), true); // Paper - fix state inconsistency + } + } + +@@ -89,6 +89,12 @@ public class TripWireBlock extends Block { + } + + private void updateSource(Level world, BlockPos pos, BlockState state) { ++ // Paper start - fix state inconsistency ++ this.updateSource(world, pos, state, false); ++ } ++ ++ private void updateSource(Level world, BlockPos pos, BlockState state, boolean beingRemoved) { ++ // Paper end + Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST}; + int i = aenumdirection.length; + int j = 0; +@@ -104,7 +110,7 @@ public class TripWireBlock extends Block { + + if (iblockdata1.is((Block) this.hook)) { + if (iblockdata1.getValue(TripWireHookBlock.FACING) == enumdirection.getOpposite()) { +- this.hook.calculateState(world, blockposition1, iblockdata1, false, true, k, state); ++ this.hook.calculateState(world, blockposition1, iblockdata1, false, true, k, state, beingRemoved); // Paper - fix state inconsistency + } + } else if (iblockdata1.is((Block) this)) { + ++k; +diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +index 4a516828e5c6abd63511ee7c93fcff11203cf8d0..004dce26ff073f1de52a84cd425c4f60fdab5e50 100644 +--- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -108,6 +108,12 @@ public class TripWireHookBlock extends Block { + } + + public void calculateState(Level world, BlockPos pos, BlockState state, boolean beingRemoved, boolean flag1, int i, @Nullable BlockState iblockdata1) { ++ // Paper start - fix tripwire inconsistency ++ this.calculateState(world, pos, state, beingRemoved, flag1, i, iblockdata1, false); ++ } ++ ++ public void calculateState(Level world, BlockPos pos, BlockState state, boolean beingRemoved, boolean flag1, int i, @Nullable BlockState iblockdata1, boolean tripWireBeingRemoved) { ++ // Paper end + Direction enumdirection = (Direction) state.getValue(TripWireHookBlock.FACING); + boolean flag2 = (Boolean) state.getValue(TripWireHookBlock.ATTACHED); + boolean flag3 = (Boolean) state.getValue(TripWireHookBlock.POWERED); +@@ -141,6 +147,7 @@ public class TripWireHookBlock extends Block { + boolean flag7 = (Boolean) iblockdata2.getValue(TripWireBlock.POWERED); + + flag5 |= flag6 && flag7; ++ if (k != i || !tripWireBeingRemoved || !flag6) // Paper - don't update the tripwire again if being removed and not disarmed + aiblockdata[k] = iblockdata2; + if (k == i) { + world.scheduleTick(pos, (Block) this, 10); diff --git a/patches/server/0806-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0806-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch deleted file mode 100644 index fca5a00257..0000000000 --- a/patches/server/0806-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch +++ /dev/null @@ -1,97 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 19 Dec 2021 09:13:41 -0800 -Subject: [PATCH] Only write chunk data to disk if it serializes without - throwing - -This ensures at least a valid version of the chunk exists -on disk, even if outdated - -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 8ff8855c5267379b3a5f5d8baa4a275ffee2c4bf..fc3442b4c7e1f22080fe6bf36d4fade162d6709e 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 -@@ -1004,6 +1004,9 @@ public class RegionFile implements AutoCloseable { - } - - } -+ -+ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails -+ - // Paper end - private class ChunkBuffer extends ByteArrayOutputStream { - -@@ -1019,6 +1022,24 @@ public class RegionFile implements AutoCloseable { - this.pos = chunkcoordintpair; - } - -+ // Paper start - don't write garbage data to disk if writing serialization fails -+ @Override -+ public void write(final int b) { -+ if (this.count > MAX_CHUNK_SIZE) { -+ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + this.count); -+ } -+ super.write(b); -+ } -+ -+ @Override -+ public void write(final byte[] b, final int off, final int len) { -+ if (this.count + len > MAX_CHUNK_SIZE) { -+ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + (this.count + len)); -+ } -+ super.write(b, off, len); -+ } -+ // Paper end -+ - public void close() throws IOException { - ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index f38ec8914e1953091ab65aa3aaefc886d3eede8a..c2356ed1a00fd8087cca285be5e7f6a5442e73fb 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -298,10 +298,17 @@ public class RegionFileStorage implements AutoCloseable { - NbtIo.write(nbt, (DataOutput) dataoutputstream); - regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk - regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone -+ dataoutputstream.close(); // Paper - only write if successful -+ // Paper start - don't write garbage data to disk if writing serialization fails -+ } catch (RegionFileSizeException e) { -+ attempts = 5; // Don't retry -+ regionfile.clear(pos); -+ throw e; -+ // Paper end - don't write garbage data to disk if writing serialization fails - } catch (Throwable throwable) { - if (dataoutputstream != null) { - try { -- dataoutputstream.close(); -+ //dataoutputstream.close(); // Paper - don't write garbage data to disk if writing serialization fails - } catch (Throwable throwable1) { - throwable.addSuppressed(throwable1); - } -@@ -309,10 +316,7 @@ public class RegionFileStorage implements AutoCloseable { - - throw throwable; - } -- -- if (dataoutputstream != null) { -- dataoutputstream.close(); -- } -+ // Paper - move into try block to only write if successfully serialized - } - // Paper start - return; -@@ -358,4 +362,13 @@ public class RegionFileStorage implements AutoCloseable { - } - - } -+ -+ // Paper start -+ public static final class RegionFileSizeException extends RuntimeException { -+ -+ public RegionFileSizeException(String message) { -+ super(message); -+ } -+ } -+ // Paper end - } diff --git a/patches/server/0807-Fix-fluid-logging-on-Block-breakNaturally.patch b/patches/server/0807-Fix-fluid-logging-on-Block-breakNaturally.patch new file mode 100644 index 0000000000..1f9012a293 --- /dev/null +++ b/patches/server/0807-Fix-fluid-logging-on-Block-breakNaturally.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 21 Dec 2021 16:28:17 -0800 +Subject: [PATCH] Fix fluid-logging on Block#breakNaturally + +Leaves fluid if the block broken was fluid-logged which is what +happens if a player breaks a fluid-logged block + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 2881bcb570dc86a7602309e1f540ecd1c7f00284..0030ac394d44acddcd2fc716277ae2b509a378af 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -515,6 +515,7 @@ public class CraftBlock implements Block { + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ net.minecraft.world.level.material.FluidState fluidState = this.world.getFluidState(this.position); // Paper + boolean result = false; + + // Modelled off EntityHuman#hasBlock +@@ -525,7 +526,7 @@ public class CraftBlock implements Block { + } + + // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. +- return this.world.setBlock(position, Blocks.AIR.defaultBlockState(), 3) && result; ++ return this.world.setBlock(position, fluidState.createLegacyBlock(), 3) && result; // Paper - leave liquid if waterlogged + } + + @Override diff --git a/patches/server/0807-Fix-tripwire-state-inconsistency.patch b/patches/server/0807-Fix-tripwire-state-inconsistency.patch deleted file mode 100644 index eba71d4136..0000000000 --- a/patches/server/0807-Fix-tripwire-state-inconsistency.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Sun, 19 Dec 2021 21:11:20 +0100 -Subject: [PATCH] Fix tripwire state inconsistency - -This patch prevents updating and re-setting the tripwire when being removed in certain conditions - -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -index 4e2fb4ee8e46b3c363992ff23e26f5a648c5f003..7f60175bf671d282c11e9084670d2bb900968255 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java -@@ -74,7 +74,7 @@ public class TripWireBlock extends Block { - @Override - public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) { - if (!moved && !state.is(newState.getBlock())) { -- this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true)); -+ this.updateSource(world, pos, (BlockState) state.setValue(TripWireBlock.POWERED, true), true); // Paper - fix state inconsistency - } - } - -@@ -89,6 +89,12 @@ public class TripWireBlock extends Block { - } - - private void updateSource(Level world, BlockPos pos, BlockState state) { -+ // Paper start - fix state inconsistency -+ this.updateSource(world, pos, state, false); -+ } -+ -+ private void updateSource(Level world, BlockPos pos, BlockState state, boolean beingRemoved) { -+ // Paper end - Direction[] aenumdirection = new Direction[]{Direction.SOUTH, Direction.WEST}; - int i = aenumdirection.length; - int j = 0; -@@ -104,7 +110,7 @@ public class TripWireBlock extends Block { - - if (iblockdata1.is((Block) this.hook)) { - if (iblockdata1.getValue(TripWireHookBlock.FACING) == enumdirection.getOpposite()) { -- this.hook.calculateState(world, blockposition1, iblockdata1, false, true, k, state); -+ this.hook.calculateState(world, blockposition1, iblockdata1, false, true, k, state, beingRemoved); // Paper - fix state inconsistency - } - } else if (iblockdata1.is((Block) this)) { - ++k; -diff --git a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -index 4a516828e5c6abd63511ee7c93fcff11203cf8d0..004dce26ff073f1de52a84cd425c4f60fdab5e50 100644 ---- a/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TripWireHookBlock.java -@@ -108,6 +108,12 @@ public class TripWireHookBlock extends Block { - } - - public void calculateState(Level world, BlockPos pos, BlockState state, boolean beingRemoved, boolean flag1, int i, @Nullable BlockState iblockdata1) { -+ // Paper start - fix tripwire inconsistency -+ this.calculateState(world, pos, state, beingRemoved, flag1, i, iblockdata1, false); -+ } -+ -+ public void calculateState(Level world, BlockPos pos, BlockState state, boolean beingRemoved, boolean flag1, int i, @Nullable BlockState iblockdata1, boolean tripWireBeingRemoved) { -+ // Paper end - Direction enumdirection = (Direction) state.getValue(TripWireHookBlock.FACING); - boolean flag2 = (Boolean) state.getValue(TripWireHookBlock.ATTACHED); - boolean flag3 = (Boolean) state.getValue(TripWireHookBlock.POWERED); -@@ -141,6 +147,7 @@ public class TripWireHookBlock extends Block { - boolean flag7 = (Boolean) iblockdata2.getValue(TripWireBlock.POWERED); - - flag5 |= flag6 && flag7; -+ if (k != i || !tripWireBeingRemoved || !flag6) // Paper - don't update the tripwire again if being removed and not disarmed - aiblockdata[k] = iblockdata2; - if (k == i) { - world.scheduleTick(pos, (Block) this, 10); diff --git a/patches/server/0808-Fix-fluid-logging-on-Block-breakNaturally.patch b/patches/server/0808-Fix-fluid-logging-on-Block-breakNaturally.patch deleted file mode 100644 index 1f9012a293..0000000000 --- a/patches/server/0808-Fix-fluid-logging-on-Block-breakNaturally.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 21 Dec 2021 16:28:17 -0800 -Subject: [PATCH] Fix fluid-logging on Block#breakNaturally - -Leaves fluid if the block broken was fluid-logged which is what -happens if a player breaks a fluid-logged block - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 2881bcb570dc86a7602309e1f540ecd1c7f00284..0030ac394d44acddcd2fc716277ae2b509a378af 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -515,6 +515,7 @@ public class CraftBlock implements Block { - net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); - net.minecraft.world.level.block.Block block = iblockdata.getBlock(); - net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); -+ net.minecraft.world.level.material.FluidState fluidState = this.world.getFluidState(this.position); // Paper - boolean result = false; - - // Modelled off EntityHuman#hasBlock -@@ -525,7 +526,7 @@ public class CraftBlock implements Block { - } - - // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. -- return this.world.setBlock(position, Blocks.AIR.defaultBlockState(), 3) && result; -+ return this.world.setBlock(position, fluidState.createLegacyBlock(), 3) && result; // Paper - leave liquid if waterlogged - } - - @Override diff --git a/patches/server/0808-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0808-Forward-CraftEntity-in-teleport-command.patch new file mode 100644 index 0000000000..92fe7d6f8c --- /dev/null +++ b/patches/server/0808-Forward-CraftEntity-in-teleport-command.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 4 Dec 2021 17:04:47 -0800 +Subject: [PATCH] Forward CraftEntity in teleport command + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4f19b25518d92c05a1ae49be905abe7dd2f69638..c6f203f95f70ca6286946e2ccc40de05d8eabc49 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -3276,6 +3276,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + public void restoreFrom(Entity original) { ++ // Paper start ++ CraftEntity bukkitEntity = original.bukkitEntity; ++ if (bukkitEntity != null) { ++ bukkitEntity.setHandle(this); ++ this.bukkitEntity = bukkitEntity; ++ } ++ // Paper end + CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); + + nbttagcompound.remove("Dimension"); +@@ -3357,10 +3364,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit + ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit + } +- // CraftBukkit start - Forward the CraftEntity to the new entity +- this.getBukkitEntity().setHandle(entity); +- entity.bukkitEntity = this.getBukkitEntity(); +- // CraftBukkit end ++ // // CraftBukkit start - Forward the CraftEntity to the new entity // Paper - moved to Entity#restoreFrom ++ // this.getBukkitEntity().setHandle(entity); ++ // entity.bukkitEntity = this.getBukkitEntity(); ++ // // CraftBukkit end + } + + this.removeAfterChangingDimensions(); diff --git a/patches/server/0809-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0809-Forward-CraftEntity-in-teleport-command.patch deleted file mode 100644 index fccb9245d5..0000000000 --- a/patches/server/0809-Forward-CraftEntity-in-teleport-command.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 4 Dec 2021 17:04:47 -0800 -Subject: [PATCH] Forward CraftEntity in teleport command - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 983b3b73ed071b1958e96b5be33e1b1097b95659..ccef7676eee4024885fee3c6350410100a97c748 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3262,6 +3262,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - public void restoreFrom(Entity original) { -+ // Paper start -+ CraftEntity bukkitEntity = original.bukkitEntity; -+ if (bukkitEntity != null) { -+ bukkitEntity.setHandle(this); -+ this.bukkitEntity = bukkitEntity; -+ } -+ // Paper end - CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag()); - - nbttagcompound.remove("Dimension"); -@@ -3343,10 +3350,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit - ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit - } -- // CraftBukkit start - Forward the CraftEntity to the new entity -- this.getBukkitEntity().setHandle(entity); -- entity.bukkitEntity = this.getBukkitEntity(); -- // CraftBukkit end -+ // // CraftBukkit start - Forward the CraftEntity to the new entity // Paper - moved to Entity#restoreFrom -+ // this.getBukkitEntity().setHandle(entity); -+ // entity.bukkitEntity = this.getBukkitEntity(); -+ // // CraftBukkit end - } - - this.removeAfterChangingDimensions(); diff --git a/patches/server/0809-Improve-scoreboard-entries.patch b/patches/server/0809-Improve-scoreboard-entries.patch new file mode 100644 index 0000000000..2a65cbf36c --- /dev/null +++ b/patches/server/0809-Improve-scoreboard-entries.patch @@ -0,0 +1,84 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 4 Nov 2021 12:31:24 -0700 +Subject: [PATCH] Improve scoreboard entries + + +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +index b7f0277b50a0f45c32b818bf9fe1218874aa8533..20b29f78fe56909e02061021b82a84cb7728d8a8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java +@@ -146,6 +146,14 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective + return new CraftScore(this, entry); + } + ++ // Paper start ++ @Override ++ public Score getScoreFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException, IllegalStateException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ return getScore(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ // Paper end ++ + @Override + public void unregister() throws IllegalStateException { + CraftScoreboard scoreboard = this.checkState(); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +index a038ede38ebb507d6a0182a4a34f2b0722ef024e..fe57437155ff9471738d3b85e787350601b79584 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java +@@ -247,4 +247,23 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { + public Scoreboard getHandle() { + return this.board; + } ++ // Paper start ++ @Override ++ public ImmutableSet getScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ return this.getScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ ++ @Override ++ public void resetScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ this.resetScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ ++ @Override ++ public Team getEntityTeam(org.bukkit.entity.Entity entity) throws IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ return this.getEntryTeam(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index 60b1cffdccde4715832546d6edbf206fbab4e82f..4b64d6c5c987e127d1ed5edad0a78f7172370b9f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -310,6 +310,26 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + } + } + ++ // Paper start ++ @Override ++ public void addEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ this.addEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ ++ @Override ++ public boolean removeEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ return this.removeEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ ++ @Override ++ public boolean hasEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { ++ Validate.notNull(entity, "Entity cannot be null"); ++ return this.hasEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); ++ } ++ // Paper end ++ + public static Visibility bukkitToNotch(NameTagVisibility visibility) { + switch (visibility) { + case ALWAYS: diff --git a/patches/server/0810-Entity-powdered-snow-API.patch b/patches/server/0810-Entity-powdered-snow-API.patch new file mode 100644 index 0000000000..e3764b4811 --- /dev/null +++ b/patches/server/0810-Entity-powdered-snow-API.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 24 Oct 2021 20:58:43 -0700 +Subject: [PATCH] Entity powdered snow API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index a1ef4ba683b1721359fb162b5f97ceefd7207184..385368d0c6a13f9b1b907bcc48d1fb2a845262ef 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1322,5 +1322,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + entity.setRot(location.getYaw(), location.getPitch()); + return !entity.valid && entity.level.addFreshEntity(entity, reason); + } ++ ++ @Override ++ public boolean isInPowderedSnow() { ++ return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +index 90f34d75f99f31f5c98c499df209979a03e23191..1737857424c5da885c46f39502cafd2a670d3be7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java +@@ -51,4 +51,11 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton { + public SkeletonType getSkeletonType() { + return SkeletonType.NORMAL; + } ++ ++ // Paper start ++ @Override ++ public int inPowderedSnowTime() { ++ return getHandle().inPowderSnowTime; ++ } ++ // Paper end + } diff --git a/patches/server/0810-Improve-scoreboard-entries.patch b/patches/server/0810-Improve-scoreboard-entries.patch deleted file mode 100644 index 2a65cbf36c..0000000000 --- a/patches/server/0810-Improve-scoreboard-entries.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 4 Nov 2021 12:31:24 -0700 -Subject: [PATCH] Improve scoreboard entries - - -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java -index b7f0277b50a0f45c32b818bf9fe1218874aa8533..20b29f78fe56909e02061021b82a84cb7728d8a8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java -@@ -146,6 +146,14 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective - return new CraftScore(this, entry); - } - -+ // Paper start -+ @Override -+ public Score getScoreFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException, IllegalStateException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ return getScore(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ // Paper end -+ - @Override - public void unregister() throws IllegalStateException { - CraftScoreboard scoreboard = this.checkState(); -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -index a038ede38ebb507d6a0182a4a34f2b0722ef024e..fe57437155ff9471738d3b85e787350601b79584 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java -@@ -247,4 +247,23 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard { - public Scoreboard getHandle() { - return this.board; - } -+ // Paper start -+ @Override -+ public ImmutableSet getScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ return this.getScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ -+ @Override -+ public void resetScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ this.resetScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ -+ @Override -+ public Team getEntityTeam(org.bukkit.entity.Entity entity) throws IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ return this.getEntryTeam(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -index 60b1cffdccde4715832546d6edbf206fbab4e82f..4b64d6c5c987e127d1ed5edad0a78f7172370b9f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -@@ -310,6 +310,26 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { - } - } - -+ // Paper start -+ @Override -+ public void addEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ this.addEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ -+ @Override -+ public boolean removeEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ return this.removeEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ -+ @Override -+ public boolean hasEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException { -+ Validate.notNull(entity, "Entity cannot be null"); -+ return this.hasEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()); -+ } -+ // Paper end -+ - public static Visibility bukkitToNotch(NameTagVisibility visibility) { - switch (visibility) { - case ALWAYS: diff --git a/patches/server/0811-Add-API-for-item-entity-health.patch b/patches/server/0811-Add-API-for-item-entity-health.patch new file mode 100644 index 0000000000..d923270a18 --- /dev/null +++ b/patches/server/0811-Add-API-for-item-entity-health.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 28 Aug 2021 09:00:45 -0700 +Subject: [PATCH] Add API for item entity health + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +index 1d90219c3a0e86786a9497d4c078c2d4077ab6cd..fea44ba6a6584b4a510af6a58cab07eecec6b68b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +@@ -102,6 +102,21 @@ public class CraftItem extends CraftEntity implements Item { + public void setWillAge(boolean willAge) { + item.age = willAge ? 0 : NO_AGE_TIME; + } ++ ++ @Override ++ public int getHealth() { ++ return item.health; ++ } ++ ++ @Override ++ public void setHealth(int health) { ++ if (health <= 0) { ++ item.getItem().onDestroyed(item); ++ item.discard(); ++ } else { ++ item.health = health; ++ } ++ } + // Paper End + + @Override diff --git a/patches/server/0811-Entity-powdered-snow-API.patch b/patches/server/0811-Entity-powdered-snow-API.patch deleted file mode 100644 index dec5da0889..0000000000 --- a/patches/server/0811-Entity-powdered-snow-API.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 24 Oct 2021 20:58:43 -0700 -Subject: [PATCH] Entity powdered snow API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index ff8562821ebb363c755e9d316679226d9febe54f..e12dcc33e859950efec36b91ad9a43e435545d5b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1305,5 +1305,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - entity.setRot(location.getYaw(), location.getPitch()); - return !entity.valid && entity.level.addFreshEntity(entity, reason); - } -+ -+ @Override -+ public boolean isInPowderedSnow() { -+ return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -index 90f34d75f99f31f5c98c499df209979a03e23191..1737857424c5da885c46f39502cafd2a670d3be7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java -@@ -51,4 +51,11 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton { - public SkeletonType getSkeletonType() { - return SkeletonType.NORMAL; - } -+ -+ // Paper start -+ @Override -+ public int inPowderedSnowTime() { -+ return getHandle().inPowderSnowTime; -+ } -+ // Paper end - } diff --git a/patches/server/0812-Add-API-for-item-entity-health.patch b/patches/server/0812-Add-API-for-item-entity-health.patch deleted file mode 100644 index d923270a18..0000000000 --- a/patches/server/0812-Add-API-for-item-entity-health.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 28 Aug 2021 09:00:45 -0700 -Subject: [PATCH] Add API for item entity health - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index 1d90219c3a0e86786a9497d4c078c2d4077ab6cd..fea44ba6a6584b4a510af6a58cab07eecec6b68b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -102,6 +102,21 @@ public class CraftItem extends CraftEntity implements Item { - public void setWillAge(boolean willAge) { - item.age = willAge ? 0 : NO_AGE_TIME; - } -+ -+ @Override -+ public int getHealth() { -+ return item.health; -+ } -+ -+ @Override -+ public void setHealth(int health) { -+ if (health <= 0) { -+ item.getItem().onDestroyed(item); -+ item.discard(); -+ } else { -+ item.health = health; -+ } -+ } - // Paper End - - @Override diff --git a/patches/server/0812-Fix-entity-type-tags-suggestions-in-selectors.patch b/patches/server/0812-Fix-entity-type-tags-suggestions-in-selectors.patch new file mode 100644 index 0000000000..ca29c1bbf2 --- /dev/null +++ b/patches/server/0812-Fix-entity-type-tags-suggestions-in-selectors.patch @@ -0,0 +1,135 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 22 Aug 2021 15:21:57 -0700 +Subject: [PATCH] Fix entity type tags suggestions in selectors + +This would really be better as a client fix because just to fix it +all EntityArguments have been told to ask the server for completions +when if this was fixed on the client, that wouldn't be needed. + +Mojira Issue: https://bugs.mojang.com/browse/MC-235045 + +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index 6fdbe747645eb83f31b56bca77a9d7962237aed8..dd0143f319d4adef8834c513af34b1cce7a94a84 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -412,4 +412,20 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy + return this.source.getBukkitSender(this); + } + // CraftBukkit end ++ // Paper start - override getSelectedEntities ++ @Override ++ public Collection getSelectedEntities() { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && this.source instanceof ServerPlayer player) { ++ double pickDistance = player.gameMode.getGameModeForPlayer().isCreative() ? 5.0F : 4.5F; ++ Vec3 min = player.getEyePosition(1.0F); ++ Vec3 viewVector = player.getViewVector(1.0F); ++ Vec3 max = min.add(viewVector.x * pickDistance, viewVector.y * pickDistance, viewVector.z * pickDistance); ++ net.minecraft.world.phys.AABB aabb = player.getBoundingBox().expandTowards(viewVector.scale(pickDistance)).inflate(1.0D, 1.0D, 1.0D); ++ pickDistance = player.gameMode.getGameModeForPlayer().isCreative() ? 6.0F : pickDistance; ++ net.minecraft.world.phys.EntityHitResult hitResult = net.minecraft.world.entity.projectile.ProjectileUtil.getEntityHitResult(player, min, max, aabb, (e) -> !e.isSpectator() && e.isPickable(), pickDistance); ++ return hitResult != null ? java.util.Collections.singletonList(hitResult.getEntity().getStringUUID()) : SharedSuggestionProvider.super.getSelectedEntities(); ++ } ++ return SharedSuggestionProvider.super.getSelectedEntities(); ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 27093aed1f4112a5414671fd5d9c4e683011506d..67ab16743b36dbf8b4336e33988d8e78433f566d 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -433,6 +433,7 @@ public class Commands { + private void fillUsableCommands(CommandNode tree, CommandNode result, CommandSourceStack source, Map, CommandNode> resultNodes) { + Iterator iterator = tree.getChildren().iterator(); + ++ boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments + while (iterator.hasNext()) { + CommandNode commandnode2 = (CommandNode) iterator.next(); + // Paper start +@@ -459,6 +460,12 @@ public class Commands { + + if (requiredargumentbuilder.getSuggestionsProvider() != null) { + requiredargumentbuilder.suggests(SuggestionProviders.safelySwap(requiredargumentbuilder.getSuggestionsProvider())); ++ // Paper start - tell clients to ask server for suggestions for EntityArguments ++ registeredAskServerSuggestionsForTree = requiredargumentbuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER; ++ } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredargumentbuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) { ++ requiredargumentbuilder.suggests(requiredargumentbuilder.getType()::listSuggestions); ++ registeredAskServerSuggestionsForTree = true; // You can only ++ // Paper end - tell clients to ask server for suggestions for EntityArguments + } + } + +diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java +index 850db283bf12345e9e7d7e8e590dbe8135c6dce1..a2ea64b7ec5f47224312a1e08dd64347be6f7c43 100644 +--- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java ++++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java +@@ -128,7 +128,7 @@ public class EntityArgument implements ArgumentType { + StringReader stringreader = new StringReader(suggestionsbuilder.getInput()); + + stringreader.setCursor(suggestionsbuilder.getStart()); +- EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, icompletionprovider.hasPermission(2)); ++ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, icompletionprovider.hasPermission(2), true); // Paper + + try { + argumentparserselector.parse(); +diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java +index ad99d67af92cda03beb1142b02082ee1bfc8a64a..a57ae219d57ed47baedfb73b3d815f39d4f87035 100644 +--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java ++++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java +@@ -115,12 +115,19 @@ public class EntitySelectorParser { + private boolean hasScores; + private boolean hasAdvancements; + private boolean usesSelectors; ++ public boolean parsingEntityArgumentSuggestions; // Paper - track when parsing EntityArgument suggestions + + public EntitySelectorParser(StringReader reader) { + this(reader, true); + } + + public EntitySelectorParser(StringReader reader, boolean atAllowed) { ++ // Paper start ++ this(reader, atAllowed, false); ++ } ++ public EntitySelectorParser(StringReader reader, boolean atAllowed, boolean parsingEntityArgumentSuggestions) { ++ this.parsingEntityArgumentSuggestions = parsingEntityArgumentSuggestions; ++ // Paper end + this.distance = MinMaxBounds.Doubles.ANY; + this.level = MinMaxBounds.Ints.ANY; + this.rotX = WrappedMinMaxBounds.ANY; +diff --git a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java +index c2b26a089c423e5df9a5cbfd1c70efbd1acb0e7a..f2376374c4c6efdfd5cfcea4d30c4b8b4dbbb0d5 100644 +--- a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java ++++ b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java +@@ -67,6 +67,19 @@ public class EntitySelectorOptions { + public static final DynamicCommandExceptionType ERROR_ENTITY_TYPE_INVALID = new DynamicCommandExceptionType((entity) -> { + return Component.translatable("argument.entity.options.type.invalid", entity); + }); ++ // Paper start ++ public static final DynamicCommandExceptionType ERROR_ENTITY_TAG_INVALID = new DynamicCommandExceptionType((object) -> { ++ return io.papermc.paper.adventure.PaperAdventure ++ .asVanilla(net.kyori.adventure.text.Component ++ .text("Invalid or unknown entity type tag '" + object + "'") ++ .hoverEvent(net.kyori.adventure.text.event.HoverEvent ++ .showText(net.kyori.adventure.text.Component ++ .text("You can disable this error in 'paper.yml'") ++ ) ++ ) ++ ); ++ }); ++ // Paper end + + private static void register(String id, EntitySelectorOptions.Modifier handler, Predicate condition, Component description) { + OPTIONS.put(id, new EntitySelectorOptions.Option(handler, condition, description)); +@@ -314,6 +327,12 @@ public class EntitySelectorOptions { + + if (reader.isTag()) { + TagKey> tagKey = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, ResourceLocation.read(reader.getReader())); ++ // Paper start - throw error if invalid entity tag (only on suggestions to keep cmd success behavior) ++ if (reader.parsingEntityArgumentSuggestions && io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !Registry.ENTITY_TYPE.isKnownTagName(tagKey)) { ++ reader.getReader().setCursor(i); ++ throw ERROR_ENTITY_TAG_INVALID.createWithContext(reader.getReader(), tagKey); ++ } ++ // Paper end + reader.addPredicate((entity) -> { + return entity.getType().is(tagKey) != bl; + }); diff --git a/patches/server/0813-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0813-Configurable-max-block-light-for-monster-spawning.patch new file mode 100644 index 0000000000..baf913d352 --- /dev/null +++ b/patches/server/0813-Configurable-max-block-light-for-monster-spawning.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 16 Dec 2021 09:40:39 +0100 +Subject: [PATCH] Configurable max block light for monster spawning + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java +index 6e0bd0eab0b06a4ac3042496bbb91292544e9f3c..55c245d0dfa369dc6de2197ae37335fba4fae4ae 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java +@@ -93,7 +93,7 @@ public abstract class Monster extends PathfinderMob implements Enemy { + return false; + } else { + DimensionType dimensionType = world.dimensionType(); +- int i = dimensionType.monsterSpawnBlockLightLimit(); ++ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel >= 0 ? world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel : dimensionType.monsterSpawnBlockLightLimit(); // Paper + if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { + return false; + } else { diff --git a/patches/server/0813-Fix-entity-type-tags-suggestions-in-selectors.patch b/patches/server/0813-Fix-entity-type-tags-suggestions-in-selectors.patch deleted file mode 100644 index ca29c1bbf2..0000000000 --- a/patches/server/0813-Fix-entity-type-tags-suggestions-in-selectors.patch +++ /dev/null @@ -1,135 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 22 Aug 2021 15:21:57 -0700 -Subject: [PATCH] Fix entity type tags suggestions in selectors - -This would really be better as a client fix because just to fix it -all EntityArguments have been told to ask the server for completions -when if this was fixed on the client, that wouldn't be needed. - -Mojira Issue: https://bugs.mojang.com/browse/MC-235045 - -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 6fdbe747645eb83f31b56bca77a9d7962237aed8..dd0143f319d4adef8834c513af34b1cce7a94a84 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -412,4 +412,20 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy - return this.source.getBukkitSender(this); - } - // CraftBukkit end -+ // Paper start - override getSelectedEntities -+ @Override -+ public Collection getSelectedEntities() { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && this.source instanceof ServerPlayer player) { -+ double pickDistance = player.gameMode.getGameModeForPlayer().isCreative() ? 5.0F : 4.5F; -+ Vec3 min = player.getEyePosition(1.0F); -+ Vec3 viewVector = player.getViewVector(1.0F); -+ Vec3 max = min.add(viewVector.x * pickDistance, viewVector.y * pickDistance, viewVector.z * pickDistance); -+ net.minecraft.world.phys.AABB aabb = player.getBoundingBox().expandTowards(viewVector.scale(pickDistance)).inflate(1.0D, 1.0D, 1.0D); -+ pickDistance = player.gameMode.getGameModeForPlayer().isCreative() ? 6.0F : pickDistance; -+ net.minecraft.world.phys.EntityHitResult hitResult = net.minecraft.world.entity.projectile.ProjectileUtil.getEntityHitResult(player, min, max, aabb, (e) -> !e.isSpectator() && e.isPickable(), pickDistance); -+ return hitResult != null ? java.util.Collections.singletonList(hitResult.getEntity().getStringUUID()) : SharedSuggestionProvider.super.getSelectedEntities(); -+ } -+ return SharedSuggestionProvider.super.getSelectedEntities(); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 27093aed1f4112a5414671fd5d9c4e683011506d..67ab16743b36dbf8b4336e33988d8e78433f566d 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -433,6 +433,7 @@ public class Commands { - private void fillUsableCommands(CommandNode tree, CommandNode result, CommandSourceStack source, Map, CommandNode> resultNodes) { - Iterator iterator = tree.getChildren().iterator(); - -+ boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments - while (iterator.hasNext()) { - CommandNode commandnode2 = (CommandNode) iterator.next(); - // Paper start -@@ -459,6 +460,12 @@ public class Commands { - - if (requiredargumentbuilder.getSuggestionsProvider() != null) { - requiredargumentbuilder.suggests(SuggestionProviders.safelySwap(requiredargumentbuilder.getSuggestionsProvider())); -+ // Paper start - tell clients to ask server for suggestions for EntityArguments -+ registeredAskServerSuggestionsForTree = requiredargumentbuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER; -+ } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredargumentbuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) { -+ requiredargumentbuilder.suggests(requiredargumentbuilder.getType()::listSuggestions); -+ registeredAskServerSuggestionsForTree = true; // You can only -+ // Paper end - tell clients to ask server for suggestions for EntityArguments - } - } - -diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -index 850db283bf12345e9e7d7e8e590dbe8135c6dce1..a2ea64b7ec5f47224312a1e08dd64347be6f7c43 100644 ---- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -+++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java -@@ -128,7 +128,7 @@ public class EntityArgument implements ArgumentType { - StringReader stringreader = new StringReader(suggestionsbuilder.getInput()); - - stringreader.setCursor(suggestionsbuilder.getStart()); -- EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, icompletionprovider.hasPermission(2)); -+ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, icompletionprovider.hasPermission(2), true); // Paper - - try { - argumentparserselector.parse(); -diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java -index ad99d67af92cda03beb1142b02082ee1bfc8a64a..a57ae219d57ed47baedfb73b3d815f39d4f87035 100644 ---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java -+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java -@@ -115,12 +115,19 @@ public class EntitySelectorParser { - private boolean hasScores; - private boolean hasAdvancements; - private boolean usesSelectors; -+ public boolean parsingEntityArgumentSuggestions; // Paper - track when parsing EntityArgument suggestions - - public EntitySelectorParser(StringReader reader) { - this(reader, true); - } - - public EntitySelectorParser(StringReader reader, boolean atAllowed) { -+ // Paper start -+ this(reader, atAllowed, false); -+ } -+ public EntitySelectorParser(StringReader reader, boolean atAllowed, boolean parsingEntityArgumentSuggestions) { -+ this.parsingEntityArgumentSuggestions = parsingEntityArgumentSuggestions; -+ // Paper end - this.distance = MinMaxBounds.Doubles.ANY; - this.level = MinMaxBounds.Ints.ANY; - this.rotX = WrappedMinMaxBounds.ANY; -diff --git a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java -index c2b26a089c423e5df9a5cbfd1c70efbd1acb0e7a..f2376374c4c6efdfd5cfcea4d30c4b8b4dbbb0d5 100644 ---- a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java -+++ b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java -@@ -67,6 +67,19 @@ public class EntitySelectorOptions { - public static final DynamicCommandExceptionType ERROR_ENTITY_TYPE_INVALID = new DynamicCommandExceptionType((entity) -> { - return Component.translatable("argument.entity.options.type.invalid", entity); - }); -+ // Paper start -+ public static final DynamicCommandExceptionType ERROR_ENTITY_TAG_INVALID = new DynamicCommandExceptionType((object) -> { -+ return io.papermc.paper.adventure.PaperAdventure -+ .asVanilla(net.kyori.adventure.text.Component -+ .text("Invalid or unknown entity type tag '" + object + "'") -+ .hoverEvent(net.kyori.adventure.text.event.HoverEvent -+ .showText(net.kyori.adventure.text.Component -+ .text("You can disable this error in 'paper.yml'") -+ ) -+ ) -+ ); -+ }); -+ // Paper end - - private static void register(String id, EntitySelectorOptions.Modifier handler, Predicate condition, Component description) { - OPTIONS.put(id, new EntitySelectorOptions.Option(handler, condition, description)); -@@ -314,6 +327,12 @@ public class EntitySelectorOptions { - - if (reader.isTag()) { - TagKey> tagKey = TagKey.create(Registry.ENTITY_TYPE_REGISTRY, ResourceLocation.read(reader.getReader())); -+ // Paper start - throw error if invalid entity tag (only on suggestions to keep cmd success behavior) -+ if (reader.parsingEntityArgumentSuggestions && io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !Registry.ENTITY_TYPE.isKnownTagName(tagKey)) { -+ reader.getReader().setCursor(i); -+ throw ERROR_ENTITY_TAG_INVALID.createWithContext(reader.getReader(), tagKey); -+ } -+ // Paper end - reader.addPredicate((entity) -> { - return entity.getType().is(tagKey) != bl; - }); diff --git a/patches/server/0814-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0814-Configurable-max-block-light-for-monster-spawning.patch deleted file mode 100644 index baf913d352..0000000000 --- a/patches/server/0814-Configurable-max-block-light-for-monster-spawning.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 16 Dec 2021 09:40:39 +0100 -Subject: [PATCH] Configurable max block light for monster spawning - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java -index 6e0bd0eab0b06a4ac3042496bbb91292544e9f3c..55c245d0dfa369dc6de2197ae37335fba4fae4ae 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Monster.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java -@@ -93,7 +93,7 @@ public abstract class Monster extends PathfinderMob implements Enemy { - return false; - } else { - DimensionType dimensionType = world.dimensionType(); -- int i = dimensionType.monsterSpawnBlockLightLimit(); -+ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel >= 0 ? world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel : dimensionType.monsterSpawnBlockLightLimit(); // Paper - if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) { - return false; - } else { diff --git a/patches/server/0814-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0814-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch new file mode 100644 index 0000000000..698fbc2d5e --- /dev/null +++ b/patches/server/0814-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 22 Dec 2021 09:51:48 -0800 +Subject: [PATCH] Fix sticky pistons and BlockPistonRetractEvent + +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. + +Co-authored-by: Zach Brown +Co-authored-by: Madeline Miller + +diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 24a822ade17849a083161216c184f02c30b5cb1f..f456ad8a74464414f69b616a48ee9a2c1cee4d90 100644 +--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -146,15 +146,15 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + // CraftBukkit start +- if (!this.isSticky) { +- org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); +- BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); +- world.getCraftServer().getPluginManager().callEvent(event); +- +- if (event.isCancelled()) { +- return; +- } +- } ++ // if (!this.isSticky) { // Paper - Move further down ++ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // ++ // if (event.isCancelled()) { ++ // return; ++ // } ++ // } + // PAIL: checkME - what happened to setTypeAndData? + // CraftBukkit end + world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); +@@ -236,6 +236,13 @@ public class PistonBaseBlock extends DirectionalBlock { + + BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); + ++ // Paper start - Move empty piston retract call to fix multiple event fires ++ if (!this.isSticky) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end + world.setBlock(pos, iblockdata1, 20); + world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change + world.blockUpdated(pos, iblockdata1.getBlock()); +@@ -262,6 +269,13 @@ public class PistonBaseBlock extends DirectionalBlock { + if (type == 1 && !iblockdata2.isAir() && PistonBaseBlock.isPushable(iblockdata2, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata2.getPistonPushReaction() == PushReaction.NORMAL || iblockdata2.is(Blocks.PISTON) || iblockdata2.is(Blocks.STICKY_PISTON))) { + this.moveBlocks(world, pos, enumdirection, false); + } else { ++ // Paper start - fire BlockPistonRetractEvent for sticky pistons retracting nothing (air) ++ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) { ++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { ++ return false; ++ } ++ } ++ // Paper end + world.removeBlock(pos.relative(enumdirection), false); + } + } diff --git a/patches/server/0815-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0815-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch deleted file mode 100644 index 698fbc2d5e..0000000000 --- a/patches/server/0815-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 22 Dec 2021 09:51:48 -0800 -Subject: [PATCH] Fix sticky pistons and BlockPistonRetractEvent - -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. - -Co-authored-by: Zach Brown -Co-authored-by: Madeline Miller - -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -index 24a822ade17849a083161216c184f02c30b5cb1f..f456ad8a74464414f69b616a48ee9a2c1cee4d90 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java -@@ -146,15 +146,15 @@ public class PistonBaseBlock extends DirectionalBlock { - } - - // CraftBukkit start -- if (!this.isSticky) { -- org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -- BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); -- world.getCraftServer().getPluginManager().callEvent(event); -- -- if (event.isCancelled()) { -- return; -- } -- } -+ // if (!this.isSticky) { // Paper - Move further down -+ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -+ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.of(), CraftBlock.notchToBlockFace(enumdirection)); -+ // world.getCraftServer().getPluginManager().callEvent(event); -+ // -+ // if (event.isCancelled()) { -+ // return; -+ // } -+ // } - // PAIL: checkME - what happened to setTypeAndData? - // CraftBukkit end - world.blockEvent(pos, this, b0, enumdirection.get3DDataValue()); -@@ -236,6 +236,13 @@ public class PistonBaseBlock extends DirectionalBlock { - - BlockState iblockdata1 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT); - -+ // Paper start - Move empty piston retract call to fix multiple event fires -+ if (!this.isSticky) { -+ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { -+ return false; -+ } -+ } -+ // Paper end - world.setBlock(pos, iblockdata1, 20); - world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata1, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true)); // Paper - diff on change - world.blockUpdated(pos, iblockdata1.getBlock()); -@@ -262,6 +269,13 @@ public class PistonBaseBlock extends DirectionalBlock { - if (type == 1 && !iblockdata2.isAir() && PistonBaseBlock.isPushable(iblockdata2, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata2.getPistonPushReaction() == PushReaction.NORMAL || iblockdata2.is(Blocks.PISTON) || iblockdata2.is(Blocks.STICKY_PISTON))) { - this.moveBlocks(world, pos, enumdirection, false); - } else { -+ // Paper start - fire BlockPistonRetractEvent for sticky pistons retracting nothing (air) -+ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) { -+ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) { -+ return false; -+ } -+ } -+ // Paper end - world.removeBlock(pos.relative(enumdirection), false); - } - } diff --git a/patches/server/0815-Load-effect-amplifiers-greater-than-127-correctly.patch b/patches/server/0815-Load-effect-amplifiers-greater-than-127-correctly.patch new file mode 100644 index 0000000000..768bf70b1f --- /dev/null +++ b/patches/server/0815-Load-effect-amplifiers-greater-than-127-correctly.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 21 Dec 2021 22:13:26 -0800 +Subject: [PATCH] Load effect amplifiers greater than 127 correctly + +MOJIRA: MC-118857 + +diff --git a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java +index 3232ecbf96761528be1bc88f223b51771386ad37..f5c9be3fde2654bd5a6b3ee737afe96a9393e836 100644 +--- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java ++++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java +@@ -265,7 +265,7 @@ public class MobEffectInstance implements Comparable { + } + + private static MobEffectInstance loadSpecifiedEffect(MobEffect type, CompoundTag nbt) { +- int i = nbt.getByte("Amplifier"); ++ int i = Byte.toUnsignedInt(nbt.getByte("Amplifier")); // Paper - correctly load amplifiers > 127 + int j = nbt.getInt("Duration"); + boolean bl = nbt.getBoolean("Ambient"); + boolean bl2 = true; diff --git a/patches/server/0816-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0816-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch new file mode 100644 index 0000000000..9f3ee548ee --- /dev/null +++ b/patches/server/0816-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: BillyGalbreath +Date: Thu, 23 Dec 2021 15:32:50 -0600 +Subject: [PATCH] Expose isFuel and canSmelt methods to FurnaceInventory + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java +index 05c29a788c96282fc18066ae253cf0b5be37e95c..e8e53d3c7d8b1bba7d77dc0c76d242eb177ad851 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java +@@ -40,6 +40,20 @@ public class CraftInventoryFurnace extends CraftInventory implements FurnaceInve + setItem(0, stack); + } + ++ // Paper start ++ @Override ++ public boolean isFuel(ItemStack stack) { ++ return stack != null && !stack.getType().isEmpty() && AbstractFurnaceBlockEntity.isFuel(CraftItemStack.asNMSCopy(stack)); ++ } ++ ++ @Override ++ public boolean canSmelt(ItemStack stack) { ++ // data packs are always loaded in the main world ++ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle(); ++ return stack != null && !stack.getType().isEmpty() && world.getRecipeManager().getRecipeFor(((AbstractFurnaceBlockEntity) this.inventory).recipeType, new net.minecraft.world.SimpleContainer(CraftItemStack.asNMSCopy(stack)), world).isPresent(); ++ } ++ // Paper end ++ + @Override + public Furnace getHolder() { + return (Furnace) inventory.getOwner(); diff --git a/patches/server/0816-Load-effect-amplifiers-greater-than-127-correctly.patch b/patches/server/0816-Load-effect-amplifiers-greater-than-127-correctly.patch deleted file mode 100644 index 768bf70b1f..0000000000 --- a/patches/server/0816-Load-effect-amplifiers-greater-than-127-correctly.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 21 Dec 2021 22:13:26 -0800 -Subject: [PATCH] Load effect amplifiers greater than 127 correctly - -MOJIRA: MC-118857 - -diff --git a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java -index 3232ecbf96761528be1bc88f223b51771386ad37..f5c9be3fde2654bd5a6b3ee737afe96a9393e836 100644 ---- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java -+++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java -@@ -265,7 +265,7 @@ public class MobEffectInstance implements Comparable { - } - - private static MobEffectInstance loadSpecifiedEffect(MobEffect type, CompoundTag nbt) { -- int i = nbt.getByte("Amplifier"); -+ int i = Byte.toUnsignedInt(nbt.getByte("Amplifier")); // Paper - correctly load amplifiers > 127 - int j = nbt.getInt("Duration"); - boolean bl = nbt.getBoolean("Ambient"); - boolean bl2 = true; diff --git a/patches/server/0817-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0817-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch deleted file mode 100644 index 9f3ee548ee..0000000000 --- a/patches/server/0817-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath -Date: Thu, 23 Dec 2021 15:32:50 -0600 -Subject: [PATCH] Expose isFuel and canSmelt methods to FurnaceInventory - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -index 05c29a788c96282fc18066ae253cf0b5be37e95c..e8e53d3c7d8b1bba7d77dc0c76d242eb177ad851 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java -@@ -40,6 +40,20 @@ public class CraftInventoryFurnace extends CraftInventory implements FurnaceInve - setItem(0, stack); - } - -+ // Paper start -+ @Override -+ public boolean isFuel(ItemStack stack) { -+ return stack != null && !stack.getType().isEmpty() && AbstractFurnaceBlockEntity.isFuel(CraftItemStack.asNMSCopy(stack)); -+ } -+ -+ @Override -+ public boolean canSmelt(ItemStack stack) { -+ // data packs are always loaded in the main world -+ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle(); -+ return stack != null && !stack.getType().isEmpty() && world.getRecipeManager().getRecipeFor(((AbstractFurnaceBlockEntity) this.inventory).recipeType, new net.minecraft.world.SimpleContainer(CraftItemStack.asNMSCopy(stack)), world).isPresent(); -+ } -+ // Paper end -+ - @Override - public Furnace getHolder() { - return (Furnace) inventory.getOwner(); diff --git a/patches/server/0817-Fix-bees-aging-inside-hives.patch b/patches/server/0817-Fix-bees-aging-inside-hives.patch new file mode 100644 index 0000000000..2be01a557c --- /dev/null +++ b/patches/server/0817-Fix-bees-aging-inside-hives.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 21 Aug 2021 21:54:16 -0700 +Subject: [PATCH] Fix bees aging inside hives + +Fixes bees incorrectly being aged up due to upstream's +resetting the ticks inside hive on a failed release + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +index 82ad97800cb115cc4830337a59cc4608c1d4a7a0..41c9f074203915c31c1ae7a160ce509c13383f84 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +@@ -328,7 +328,7 @@ public class BeehiveBlockEntity extends BlockEntity { + + for (Iterator iterator = bees.iterator(); iterator.hasNext(); ++tileentitybeehive_hivebee.ticksInHive) { + tileentitybeehive_hivebee = (BeehiveBlockEntity.BeeData) iterator.next(); +- if (tileentitybeehive_hivebee.ticksInHive > tileentitybeehive_hivebee.minOccupationTicks) { ++ if (tileentitybeehive_hivebee.exitTickCounter > tileentitybeehive_hivebee.minOccupationTicks) { // Paper - use exitTickCounter + BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus = tileentitybeehive_hivebee.entityData.getBoolean("HasNectar") ? BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED : BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED; + + if (BeehiveBlockEntity.releaseOccupant(world, pos, state, tileentitybeehive_hivebee, (List) null, tileentitybeehive_releasestatus, flowerPos)) { +@@ -336,10 +336,11 @@ public class BeehiveBlockEntity extends BlockEntity { + iterator.remove(); + // CraftBukkit start + } else { +- tileentitybeehive_hivebee.ticksInHive = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable ++ tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - use exitTickCounter to keep actual bee life + // CraftBukkit end + } + } ++ tileentitybeehive_hivebee.exitTickCounter++; // Paper + } + + if (flag) { +@@ -428,12 +429,14 @@ public class BeehiveBlockEntity extends BlockEntity { + + final CompoundTag entityData; + int ticksInHive; ++ int exitTickCounter; // Paper - separate counter for checking if bee should exit to reduce exit attempts + final int minOccupationTicks; + + BeeData(CompoundTag entityData, int ticksInHive, int minOccupationTicks) { + BeehiveBlockEntity.removeIgnoredBeeTags(entityData); + this.entityData = entityData; + this.ticksInHive = ticksInHive; ++ this.exitTickCounter = ticksInHive; // Paper + this.minOccupationTicks = minOccupationTicks; + } + } diff --git a/patches/server/0818-Bucketable-API.patch b/patches/server/0818-Bucketable-API.patch new file mode 100644 index 0000000000..ff1194970a --- /dev/null +++ b/patches/server/0818-Bucketable-API.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 14:03:17 -0500 +Subject: [PATCH] Bucketable API + + +diff --git a/src/main/java/io/papermc/paper/entity/PaperBucketable.java b/src/main/java/io/papermc/paper/entity/PaperBucketable.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c4eee682f5b8cd82f71f92f0549f39b76e735cd8 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/entity/PaperBucketable.java +@@ -0,0 +1,31 @@ ++package io.papermc.paper.entity; ++ ++import org.bukkit.Sound; ++import org.bukkit.craftbukkit.CraftSound; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.inventory.ItemStack; ++ ++public interface PaperBucketable extends Bucketable { ++ ++ net.minecraft.world.entity.animal.Bucketable getHandle(); ++ ++ @Override ++ default boolean isFromBucket() { ++ return this.getHandle().fromBucket(); ++ } ++ ++ @Override ++ default void setFromBucket(boolean fromBucket) { ++ this.getHandle().setFromBucket(fromBucket); ++ } ++ ++ @Override ++ default ItemStack getBaseBucketItem() { ++ return CraftItemStack.asBukkitCopy(this.getHandle().getBucketItemStack()); ++ } ++ ++ @Override ++ default Sound getPickupSound() { ++ return CraftSound.getBukkit(this.getHandle().getPickupSound()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java +index cd751a13a29c34be1d084253f19bf2dce8399d01..cdaf860ec1b84c4ad9f2345968786d878f483f74 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java +@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Axolotl; + import org.bukkit.entity.EntityType; + +-public class CraftAxolotl extends CraftAnimals implements Axolotl { ++public class CraftAxolotl extends CraftAnimals implements Axolotl, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API + + public CraftAxolotl(CraftServer server, net.minecraft.world.entity.animal.axolotl.Axolotl entity) { + super(server, entity); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java +index 69d103a8ec74b17ee3116bb4d448494bd66f50b6..3c64461119391ec2e987fc936104e21ef0a95ce4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java +@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.AbstractFish; + import org.bukkit.craftbukkit.CraftServer; + import org.bukkit.entity.Fish; + +-public class CraftFish extends CraftWaterMob implements Fish { ++public class CraftFish extends CraftWaterMob implements Fish, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API + + public CraftFish(CraftServer server, AbstractFish entity) { + super(server, entity); diff --git a/patches/server/0818-Fix-bees-aging-inside-hives.patch b/patches/server/0818-Fix-bees-aging-inside-hives.patch deleted file mode 100644 index 2be01a557c..0000000000 --- a/patches/server/0818-Fix-bees-aging-inside-hives.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 21 Aug 2021 21:54:16 -0700 -Subject: [PATCH] Fix bees aging inside hives - -Fixes bees incorrectly being aged up due to upstream's -resetting the ticks inside hive on a failed release - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index 82ad97800cb115cc4830337a59cc4608c1d4a7a0..41c9f074203915c31c1ae7a160ce509c13383f84 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -328,7 +328,7 @@ public class BeehiveBlockEntity extends BlockEntity { - - for (Iterator iterator = bees.iterator(); iterator.hasNext(); ++tileentitybeehive_hivebee.ticksInHive) { - tileentitybeehive_hivebee = (BeehiveBlockEntity.BeeData) iterator.next(); -- if (tileentitybeehive_hivebee.ticksInHive > tileentitybeehive_hivebee.minOccupationTicks) { -+ if (tileentitybeehive_hivebee.exitTickCounter > tileentitybeehive_hivebee.minOccupationTicks) { // Paper - use exitTickCounter - BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus = tileentitybeehive_hivebee.entityData.getBoolean("HasNectar") ? BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED : BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED; - - if (BeehiveBlockEntity.releaseOccupant(world, pos, state, tileentitybeehive_hivebee, (List) null, tileentitybeehive_releasestatus, flowerPos)) { -@@ -336,10 +336,11 @@ public class BeehiveBlockEntity extends BlockEntity { - iterator.remove(); - // CraftBukkit start - } else { -- tileentitybeehive_hivebee.ticksInHive = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable -+ tileentitybeehive_hivebee.exitTickCounter = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable // Paper - use exitTickCounter to keep actual bee life - // CraftBukkit end - } - } -+ tileentitybeehive_hivebee.exitTickCounter++; // Paper - } - - if (flag) { -@@ -428,12 +429,14 @@ public class BeehiveBlockEntity extends BlockEntity { - - final CompoundTag entityData; - int ticksInHive; -+ int exitTickCounter; // Paper - separate counter for checking if bee should exit to reduce exit attempts - final int minOccupationTicks; - - BeeData(CompoundTag entityData, int ticksInHive, int minOccupationTicks) { - BeehiveBlockEntity.removeIgnoredBeeTags(entityData); - this.entityData = entityData; - this.ticksInHive = ticksInHive; -+ this.exitTickCounter = ticksInHive; // Paper - this.minOccupationTicks = minOccupationTicks; - } - } diff --git a/patches/server/0819-Bucketable-API.patch b/patches/server/0819-Bucketable-API.patch deleted file mode 100644 index ff1194970a..0000000000 --- a/patches/server/0819-Bucketable-API.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 14:03:17 -0500 -Subject: [PATCH] Bucketable API - - -diff --git a/src/main/java/io/papermc/paper/entity/PaperBucketable.java b/src/main/java/io/papermc/paper/entity/PaperBucketable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c4eee682f5b8cd82f71f92f0549f39b76e735cd8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/entity/PaperBucketable.java -@@ -0,0 +1,31 @@ -+package io.papermc.paper.entity; -+ -+import org.bukkit.Sound; -+import org.bukkit.craftbukkit.CraftSound; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.inventory.ItemStack; -+ -+public interface PaperBucketable extends Bucketable { -+ -+ net.minecraft.world.entity.animal.Bucketable getHandle(); -+ -+ @Override -+ default boolean isFromBucket() { -+ return this.getHandle().fromBucket(); -+ } -+ -+ @Override -+ default void setFromBucket(boolean fromBucket) { -+ this.getHandle().setFromBucket(fromBucket); -+ } -+ -+ @Override -+ default ItemStack getBaseBucketItem() { -+ return CraftItemStack.asBukkitCopy(this.getHandle().getBucketItemStack()); -+ } -+ -+ @Override -+ default Sound getPickupSound() { -+ return CraftSound.getBukkit(this.getHandle().getPickupSound()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java -index cd751a13a29c34be1d084253f19bf2dce8399d01..cdaf860ec1b84c4ad9f2345968786d878f483f74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java -@@ -5,7 +5,7 @@ import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Axolotl; - import org.bukkit.entity.EntityType; - --public class CraftAxolotl extends CraftAnimals implements Axolotl { -+public class CraftAxolotl extends CraftAnimals implements Axolotl, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API - - public CraftAxolotl(CraftServer server, net.minecraft.world.entity.animal.axolotl.Axolotl entity) { - super(server, entity); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java -index 69d103a8ec74b17ee3116bb4d448494bd66f50b6..3c64461119391ec2e987fc936104e21ef0a95ce4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java -@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.AbstractFish; - import org.bukkit.craftbukkit.CraftServer; - import org.bukkit.entity.Fish; - --public class CraftFish extends CraftWaterMob implements Fish { -+public class CraftFish extends CraftWaterMob implements Fish, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API - - public CraftFish(CraftServer server, AbstractFish entity) { - super(server, entity); diff --git a/patches/server/0819-Check-player-world-in-endPortalSoundRadius.patch b/patches/server/0819-Check-player-world-in-endPortalSoundRadius.patch new file mode 100644 index 0000000000..a5edd3fe44 --- /dev/null +++ b/patches/server/0819-Check-player-world-in-endPortalSoundRadius.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: lexikiq +Date: Sat, 17 Jul 2021 20:37:02 -0400 +Subject: [PATCH] Check player world in endPortalSoundRadius + +Fixes Spigot's endPortalSoundRadius not checking player worlds + +diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +index a88ffff41481d346a99762352094cdb4e8dd6cc2..0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254 100644 +--- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +@@ -68,7 +68,7 @@ public class EnderEyeItem extends Item { + double deltaX = soundPos.getX() - player.getX(); + double deltaZ = soundPos.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +- if (world.spigotConfig.endPortalSoundRadius > 0 && distanceSquared > world.spigotConfig.endPortalSoundRadius * world.spigotConfig.endPortalSoundRadius) continue; // Spigot ++ if (world.spigotConfig.endPortalSoundRadius > 0 && (distanceSquared > world.spigotConfig.endPortalSoundRadius * world.spigotConfig.endPortalSoundRadius || player.getLevel() != world)) continue; // Spigot // Paper - ensure recipient is in same world as portal + if (distanceSquared > viewDistance * viewDistance) { + double deltaLength = Math.sqrt(distanceSquared); + double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; diff --git a/patches/server/0820-Check-player-world-in-endPortalSoundRadius.patch b/patches/server/0820-Check-player-world-in-endPortalSoundRadius.patch deleted file mode 100644 index a5edd3fe44..0000000000 --- a/patches/server/0820-Check-player-world-in-endPortalSoundRadius.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: lexikiq -Date: Sat, 17 Jul 2021 20:37:02 -0400 -Subject: [PATCH] Check player world in endPortalSoundRadius - -Fixes Spigot's endPortalSoundRadius not checking player worlds - -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index a88ffff41481d346a99762352094cdb4e8dd6cc2..0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -68,7 +68,7 @@ public class EnderEyeItem extends Item { - double deltaX = soundPos.getX() - player.getX(); - double deltaZ = soundPos.getZ() - player.getZ(); - double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -- if (world.spigotConfig.endPortalSoundRadius > 0 && distanceSquared > world.spigotConfig.endPortalSoundRadius * world.spigotConfig.endPortalSoundRadius) continue; // Spigot -+ if (world.spigotConfig.endPortalSoundRadius > 0 && (distanceSquared > world.spigotConfig.endPortalSoundRadius * world.spigotConfig.endPortalSoundRadius || player.getLevel() != world)) continue; // Spigot // Paper - ensure recipient is in same world as portal - if (distanceSquared > viewDistance * viewDistance) { - double deltaLength = Math.sqrt(distanceSquared); - double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance; diff --git a/patches/server/0820-Validate-usernames.patch b/patches/server/0820-Validate-usernames.patch new file mode 100644 index 0000000000..c032b67744 --- /dev/null +++ b/patches/server/0820-Validate-usernames.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 1 Jan 2022 05:19:37 -0800 +Subject: [PATCH] Validate usernames + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index acd581d14e0ef1fe5a6545ee67be00deff589879..553eb8e437b07376dbfc54b0018bcc3ff93e4461 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -66,6 +66,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + @Nullable + private ProfilePublicKey.Data profilePublicKeyData; + public String hostname = ""; // CraftBukkit - add field ++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -255,10 +256,38 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + } + } + ++ // Paper start - validate usernames ++ public static boolean validateUsername(String in) { ++ if (in == null || in.isEmpty() || in.length() > 16) { ++ return false; ++ } ++ ++ for (int i = 0, len = in.length(); i < len; ++i) { ++ char c = in.charAt(i); ++ ++ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ } ++ // Paper end - validate usernames ++ + @Override + public void handleHello(ServerboundHelloPacket packet) { + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); + Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]); ++ // Paper start - validate usernames ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation) { ++ if (!this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation && !validateUsername(packet.name())) { ++ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); ++ return; ++ } ++ } ++ // Paper end - validate usernames + this.profilePublicKeyData = (ProfilePublicKey.Data) packet.publicKey().orElse(null); // CraftBukkit - decompile error + GameProfile gameprofile = this.server.getSingleplayerProfile(); + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 70d648bc5e795355d28579cc2fda43c3c9eb255d..67f90c75aa4858bf1575bf7b0a62b8113de7c2ea 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -714,7 +714,7 @@ public abstract class PlayerList { + + for (int i = 0; i < this.players.size(); ++i) { + entityplayer = (ServerPlayer) this.players.get(i); +- if (entityplayer.getUUID().equals(uuid)) { ++ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames + list.add(entityplayer); + } + } diff --git a/patches/server/0821-Fix-saving-configs-with-more-long-comments.patch b/patches/server/0821-Fix-saving-configs-with-more-long-comments.patch new file mode 100644 index 0000000000..7d52b01085 --- /dev/null +++ b/patches/server/0821-Fix-saving-configs-with-more-long-comments.patch @@ -0,0 +1,1702 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 1 Jan 2022 21:24:50 -0800 +Subject: [PATCH] Fix saving configs with more long comments + +This is a bug with snakeyaml +PR: https://bitbucket.org/snakeyaml/snakeyaml/pull-requests/3 +Issue: https://bitbucket.org/snakeyaml/snakeyaml/issues/518/comments-could-cause-queue-full + +Added the entire Emitter class from snakeyaml because +dev-imports doesn't work with non-mojang-added dependencies + +Replacement for upstream: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/a7505b3cd0498baca152777767f0e4ddebbe4d1a + +diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..ca32c00775f3d98abea39bbfeb0268bb9efbc12b +--- /dev/null ++++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java +@@ -0,0 +1,1682 @@ ++/** ++ * Copyright (c) 2008, SnakeYAML ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++package org.yaml.snakeyaml.emitter; ++ ++import org.yaml.snakeyaml.DumperOptions; ++import org.yaml.snakeyaml.DumperOptions.ScalarStyle; ++import org.yaml.snakeyaml.DumperOptions.Version; ++import org.yaml.snakeyaml.comments.CommentEventsCollector; ++import org.yaml.snakeyaml.comments.CommentLine; ++import org.yaml.snakeyaml.comments.CommentType; ++import org.yaml.snakeyaml.error.YAMLException; ++import org.yaml.snakeyaml.events.AliasEvent; ++import org.yaml.snakeyaml.events.CollectionEndEvent; ++import org.yaml.snakeyaml.events.CollectionStartEvent; ++import org.yaml.snakeyaml.events.CommentEvent; ++import org.yaml.snakeyaml.events.DocumentEndEvent; ++import org.yaml.snakeyaml.events.DocumentStartEvent; ++import org.yaml.snakeyaml.events.Event; ++import org.yaml.snakeyaml.events.Event.ID; ++import org.yaml.snakeyaml.events.MappingEndEvent; ++import org.yaml.snakeyaml.events.MappingStartEvent; ++import org.yaml.snakeyaml.events.NodeEvent; ++import org.yaml.snakeyaml.events.ScalarEvent; ++import org.yaml.snakeyaml.events.SequenceEndEvent; ++import org.yaml.snakeyaml.events.SequenceStartEvent; ++import org.yaml.snakeyaml.events.StreamEndEvent; ++import org.yaml.snakeyaml.events.StreamStartEvent; ++import org.yaml.snakeyaml.nodes.Tag; ++import org.yaml.snakeyaml.reader.StreamReader; ++import org.yaml.snakeyaml.scanner.Constant; ++import org.yaml.snakeyaml.util.ArrayStack; ++ ++import java.io.IOException; ++import java.io.Writer; ++import java.util.ArrayDeque; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.LinkedHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Queue; ++import java.util.Set; ++import java.util.TreeSet; ++import java.util.concurrent.ArrayBlockingQueue; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ ++/** ++ *
++ * Emitter expects events obeying the following grammar:
++ * stream ::= STREAM-START document* STREAM-END
++ * document ::= DOCUMENT-START node DOCUMENT-END
++ * node ::= SCALAR | sequence | mapping
++ * sequence ::= SEQUENCE-START node* SEQUENCE-END
++ * mapping ::= MAPPING-START (node node)* MAPPING-END
++ * 
++ */ ++public final class Emitter implements Emitable { ++ public static final int MIN_INDENT = 1; ++ public static final int MAX_INDENT = 10; ++ private static final char[] SPACE = {' '}; ++ ++ private static final Pattern SPACES_PATTERN = Pattern.compile("\\s"); ++ private static final Set INVALID_ANCHOR = new HashSet(); ++ static { ++ INVALID_ANCHOR.add('['); ++ INVALID_ANCHOR.add(']'); ++ INVALID_ANCHOR.add('{'); ++ INVALID_ANCHOR.add('}'); ++ INVALID_ANCHOR.add(','); ++ INVALID_ANCHOR.add('*'); ++ INVALID_ANCHOR.add('&'); ++ } ++ ++ private static final Map ESCAPE_REPLACEMENTS = new HashMap(); ++ static { ++ ESCAPE_REPLACEMENTS.put('\0', "0"); ++ ESCAPE_REPLACEMENTS.put('\u0007', "a"); ++ ESCAPE_REPLACEMENTS.put('\u0008', "b"); ++ ESCAPE_REPLACEMENTS.put('\u0009', "t"); ++ ESCAPE_REPLACEMENTS.put('\n', "n"); ++ ESCAPE_REPLACEMENTS.put('\u000B', "v"); ++ ESCAPE_REPLACEMENTS.put('\u000C', "f"); ++ ESCAPE_REPLACEMENTS.put('\r', "r"); ++ ESCAPE_REPLACEMENTS.put('\u001B', "e"); ++ ESCAPE_REPLACEMENTS.put('"', "\""); ++ ESCAPE_REPLACEMENTS.put('\\', "\\"); ++ ESCAPE_REPLACEMENTS.put('\u0085', "N"); ++ ESCAPE_REPLACEMENTS.put('\u00A0', "_"); ++ ESCAPE_REPLACEMENTS.put('\u2028', "L"); ++ ESCAPE_REPLACEMENTS.put('\u2029', "P"); ++ } ++ ++ private final static Map DEFAULT_TAG_PREFIXES = new LinkedHashMap(); ++ static { ++ DEFAULT_TAG_PREFIXES.put("!", "!"); ++ DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!"); ++ } ++ // The stream should have the methods `write` and possibly `flush`. ++ private final Writer stream; ++ ++ // Encoding is defined by Writer (cannot be overridden by STREAM-START.) ++ // private Charset encoding; ++ ++ // Emitter is a state machine with a stack of states to handle nested ++ // structures. ++ private final ArrayStack states; ++ private EmitterState state; ++ ++ // Current event and the event queue. ++ private final Queue events; ++ private Event event; ++ ++ // The current indentation level and the stack of previous indents. ++ private final ArrayStack indents; ++ private Integer indent; ++ ++ // Flow level. ++ private int flowLevel; ++ ++ // Contexts. ++ private boolean rootContext; ++ private boolean mappingContext; ++ private boolean simpleKeyContext; ++ ++ // ++ // Characteristics of the last emitted character: ++ // - current position. ++ // - is it a whitespace? ++ // - is it an indention character ++ // (indentation space, '-', '?', or ':')? ++ // private int line; this variable is not used ++ private int column; ++ private boolean whitespace; ++ private boolean indention; ++ private boolean openEnded; ++ ++ // Formatting details. ++ private final Boolean canonical; ++ // pretty print flow by adding extra line breaks ++ private final Boolean prettyFlow; ++ ++ private final boolean allowUnicode; ++ private int bestIndent; ++ private final int indicatorIndent; ++ private final boolean indentWithIndicator; ++ private int bestWidth; ++ private final char[] bestLineBreak; ++ private final boolean splitLines; ++ private final int maxSimpleKeyLength; ++ private final boolean emitComments; ++ ++ // Tag prefixes. ++ private Map tagPrefixes; ++ ++ // Prepared anchor and tag. ++ private String preparedAnchor; ++ private String preparedTag; ++ ++ // Scalar analysis and style. ++ private ScalarAnalysis analysis; ++ private DumperOptions.ScalarStyle style; ++ ++ // Comment processing ++ private final CommentEventsCollector blockCommentsCollector; ++ private final CommentEventsCollector inlineCommentsCollector; ++ ++ ++ public Emitter(Writer stream, DumperOptions opts) { ++ // The stream should have the methods `write` and possibly `flush`. ++ this.stream = stream; ++ // Emitter is a state machine with a stack of states to handle nested ++ // structures. ++ this.states = new ArrayStack(100); ++ this.state = new ExpectStreamStart(); ++ // Current event and the event queue. ++ this.events = new ArrayDeque<>(100); // Paper - allow more than 100 events (or comments) ++ // this.events = new ArrayBlockingQueue<>(100); ++ this.event = null; ++ // The current indentation level and the stack of previous indents. ++ this.indents = new ArrayStack(10); ++ this.indent = null; ++ // Flow level. ++ this.flowLevel = 0; ++ // Contexts. ++ mappingContext = false; ++ simpleKeyContext = false; ++ ++ // ++ // Characteristics of the last emitted character: ++ // - current position. ++ // - is it a whitespace? ++ // - is it an indention character ++ // (indentation space, '-', '?', or ':')? ++ column = 0; ++ whitespace = true; ++ indention = true; ++ ++ // Whether the document requires an explicit document indicator ++ openEnded = false; ++ ++ // Formatting details. ++ this.canonical = opts.isCanonical(); ++ this.prettyFlow = opts.isPrettyFlow(); ++ this.allowUnicode = opts.isAllowUnicode(); ++ this.bestIndent = 2; ++ if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) { ++ this.bestIndent = opts.getIndent(); ++ } ++ this.indicatorIndent = opts.getIndicatorIndent(); ++ this.indentWithIndicator = opts.getIndentWithIndicator(); ++ this.bestWidth = 80; ++ if (opts.getWidth() > this.bestIndent * 2) { ++ this.bestWidth = opts.getWidth(); ++ } ++ this.bestLineBreak = opts.getLineBreak().getString().toCharArray(); ++ this.splitLines = opts.getSplitLines(); ++ this.maxSimpleKeyLength = opts.getMaxSimpleKeyLength(); ++ this.emitComments = opts.isProcessComments(); ++ ++ // Tag prefixes. ++ this.tagPrefixes = new LinkedHashMap(); ++ ++ // Prepared anchor and tag. ++ this.preparedAnchor = null; ++ this.preparedTag = null; ++ ++ // Scalar analysis and style. ++ this.analysis = null; ++ this.style = null; ++ ++ // Comment processing ++ this.blockCommentsCollector = new CommentEventsCollector(events, ++ CommentType.BLANK_LINE, CommentType.BLOCK); ++ this.inlineCommentsCollector = new CommentEventsCollector(events, ++ CommentType.IN_LINE); ++ } ++ ++ public void emit(Event event) throws IOException { ++ this.events.add(event); ++ while (!needMoreEvents()) { ++ this.event = this.events.poll(); ++ this.state.expect(); ++ this.event = null; ++ } ++ } ++ ++ // In some cases, we wait for a few next events before emitting. ++ ++ private boolean needMoreEvents() { ++ if (events.isEmpty()) { ++ return true; ++ } ++ ++ Iterator iter = events.iterator(); ++ Event event = iter.next(); // FIXME why without check ??? ++ while(event instanceof CommentEvent) { ++ if (!iter.hasNext()) { ++ return true; ++ } ++ event = iter.next(); ++ } ++ ++ if (event instanceof DocumentStartEvent) { ++ return needEvents(iter, 1); ++ } else if (event instanceof SequenceStartEvent) { ++ return needEvents(iter, 2); ++ } else if (event instanceof MappingStartEvent) { ++ return needEvents(iter, 3); ++ } else if (event instanceof StreamStartEvent) { ++ return needEvents(iter, 2); ++ } else if (event instanceof StreamEndEvent) { ++ return false; ++ } else if (emitComments) { ++ return needEvents(iter, 1); ++ } ++ return false; ++ } ++ ++ private boolean needEvents(Iterator iter, int count) { ++ int level = 0; ++ int actualCount = 0; ++ while (iter.hasNext()) { ++ Event event = iter.next(); ++ if (event instanceof CommentEvent) { ++ continue; ++ } ++ actualCount++; ++ if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) { ++ level++; ++ } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) { ++ level--; ++ } else if (event instanceof StreamEndEvent) { ++ level = -1; ++ } ++ if (level < 0) { ++ return false; ++ } ++ } ++ return actualCount < count; ++ } ++ ++ private void increaseIndent(boolean flow, boolean indentless) { ++ indents.push(indent); ++ if (indent == null) { ++ if (flow) { ++ indent = bestIndent; ++ } else { ++ indent = 0; ++ } ++ } else if (!indentless) { ++ this.indent += bestIndent; ++ } ++ } ++ ++ // States ++ ++ // Stream handlers. ++ ++ private class ExpectStreamStart implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof StreamStartEvent) { ++ writeStreamStart(); ++ state = new ExpectFirstDocumentStart(); ++ } else { ++ throw new EmitterException("expected StreamStartEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectNothing implements EmitterState { ++ public void expect() throws IOException { ++ throw new EmitterException("expecting nothing, but got " + event); ++ } ++ } ++ ++ // Document handlers. ++ ++ private class ExpectFirstDocumentStart implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectDocumentStart(true).expect(); ++ } ++ } ++ ++ private class ExpectDocumentStart implements EmitterState { ++ private final boolean first; ++ ++ public ExpectDocumentStart(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ if (event instanceof DocumentStartEvent) { ++ DocumentStartEvent ev = (DocumentStartEvent) event; ++ if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) { ++ writeIndicator("...", true, false, false); ++ writeIndent(); ++ } ++ if (ev.getVersion() != null) { ++ String versionText = prepareVersion(ev.getVersion()); ++ writeVersionDirective(versionText); ++ } ++ tagPrefixes = new LinkedHashMap(DEFAULT_TAG_PREFIXES); ++ if (ev.getTags() != null) { ++ Set handles = new TreeSet(ev.getTags().keySet()); ++ for (String handle : handles) { ++ String prefix = ev.getTags().get(handle); ++ tagPrefixes.put(prefix, handle); ++ String handleText = prepareTagHandle(handle); ++ String prefixText = prepareTagPrefix(prefix); ++ writeTagDirective(handleText, prefixText); ++ } ++ } ++ boolean implicit = first && !ev.getExplicit() && !canonical ++ && ev.getVersion() == null ++ && (ev.getTags() == null || ev.getTags().isEmpty()) ++ && !checkEmptyDocument(); ++ if (!implicit) { ++ writeIndent(); ++ writeIndicator("---", true, false, false); ++ if (canonical) { ++ writeIndent(); ++ } ++ } ++ state = new ExpectDocumentRoot(); ++ } else if (event instanceof StreamEndEvent) { ++ writeStreamEnd(); ++ state = new ExpectNothing(); ++ } else if (event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ writeBlockComment(); ++ // state = state; remains unchanged ++ } else { ++ throw new EmitterException("expected DocumentStartEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectDocumentEnd implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (event instanceof DocumentEndEvent) { ++ writeIndent(); ++ if (((DocumentEndEvent) event).getExplicit()) { ++ writeIndicator("...", true, false, false); ++ writeIndent(); ++ } ++ flushStream(); ++ state = new ExpectDocumentStart(false); ++ } else { ++ throw new EmitterException("expected DocumentEndEvent, but got " + event); ++ } ++ } ++ } ++ ++ private class ExpectDocumentRoot implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ if (!blockCommentsCollector.isEmpty()) { ++ writeBlockComment(); ++ if (event instanceof DocumentEndEvent) { ++ new ExpectDocumentEnd().expect(); ++ return; ++ } ++ } ++ states.push(new ExpectDocumentEnd()); ++ expectNode(true, false, false); ++ } ++ } ++ ++ // Node handlers. ++ ++ private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException { ++ rootContext = root; ++ mappingContext = mapping; ++ simpleKeyContext = simpleKey; ++ if (event instanceof AliasEvent) { ++ expectAlias(); ++ } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) { ++ processAnchor("&"); ++ processTag(); ++ if (event instanceof ScalarEvent) { ++ expectScalar(); ++ } else if (event instanceof SequenceStartEvent) { ++ if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow() ++ || checkEmptySequence()) { ++ expectFlowSequence(); ++ } else { ++ expectBlockSequence(); ++ } ++ } else {// MappingStartEvent ++ if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow() ++ || checkEmptyMapping()) { ++ expectFlowMapping(); ++ } else { ++ expectBlockMapping(); ++ } ++ } ++ } else { ++ throw new EmitterException("expected NodeEvent, but got " + event); ++ } ++ } ++ ++ private void expectAlias() throws IOException { ++ if (!(event instanceof AliasEvent)) { ++ throw new EmitterException("Alias must be provided"); ++ } ++ processAnchor("*"); ++ state = states.pop(); ++ } ++ ++ private void expectScalar() throws IOException { ++ increaseIndent(true, false); ++ processScalar(); ++ indent = indents.pop(); ++ state = states.pop(); ++ } ++ ++ // Flow sequence handlers. ++ ++ private void expectFlowSequence() throws IOException { ++ writeIndicator("[", true, true, false); ++ flowLevel++; ++ increaseIndent(true, false); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = new ExpectFirstFlowSequenceItem(); ++ } ++ ++ private class ExpectFirstFlowSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ writeIndicator("]", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else if (event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ writeBlockComment(); ++ } else { ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ states.push(new ExpectFlowSequenceItem()); ++ expectNode(false, false, false); ++ event = inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ private class ExpectFlowSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ if (canonical) { ++ writeIndicator(",", false, false, false); ++ writeIndent(); ++ } else if (prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator("]", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = states.pop(); ++ } else if (event instanceof CommentEvent) { ++ event = blockCommentsCollector.collectEvents(event); ++ } else { ++ writeIndicator(",", false, false, false); ++ writeBlockComment(); ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ states.push(new ExpectFlowSequenceItem()); ++ expectNode(false, false, false); ++ event = inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ // Flow mapping handlers. ++ ++ private void expectFlowMapping() throws IOException { ++ writeIndicator("{", true, true, false); ++ flowLevel++; ++ increaseIndent(true, false); ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ state = new ExpectFirstFlowMappingKey(); ++ } ++ ++ private class ExpectFirstFlowMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ writeIndicator("}", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else { ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ if (!canonical && checkSimpleKey()) { ++ states.push(new ExpectFlowMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, false); ++ states.push(new ExpectFlowMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private class ExpectFlowMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ if (event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ flowLevel--; ++ if (canonical) { ++ writeIndicator(",", false, false, false); ++ writeIndent(); ++ } ++ if (prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator("}", false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ state = states.pop(); ++ } else { ++ writeIndicator(",", false, false, false); ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { ++ writeIndent(); ++ } ++ if (!canonical && checkSimpleKey()) { ++ states.push(new ExpectFlowMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, false); ++ states.push(new ExpectFlowMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private class ExpectFlowMappingSimpleValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndicator(":", false, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ states.push(new ExpectFlowMappingKey()); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ private class ExpectFlowMappingValue implements EmitterState { ++ public void expect() throws IOException { ++ if (canonical || (column > bestWidth) || prettyFlow) { ++ writeIndent(); ++ } ++ writeIndicator(":", true, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ states.push(new ExpectFlowMappingKey()); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ // Block sequence handlers. ++ ++ private void expectBlockSequence() throws IOException { ++ boolean indentless = mappingContext && !indention; ++ increaseIndent(false, indentless); ++ state = new ExpectFirstBlockSequenceItem(); ++ } ++ ++ private class ExpectFirstBlockSequenceItem implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectBlockSequenceItem(true).expect(); ++ } ++ } ++ ++ private class ExpectBlockSequenceItem implements EmitterState { ++ private final boolean first; ++ ++ public ExpectBlockSequenceItem(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ if (!this.first && event instanceof SequenceEndEvent) { ++ indent = indents.pop(); ++ state = states.pop(); ++ } else if( event instanceof CommentEvent) { ++ blockCommentsCollector.collectEvents(event); ++ } else { ++ writeIndent(); ++ if (!indentWithIndicator || this.first) { ++ writeWhitespace(indicatorIndent); ++ } ++ writeIndicator("-", true, false, true); ++ if (indentWithIndicator && this.first) { ++ indent += indicatorIndent; ++ } ++ if (!blockCommentsCollector.isEmpty()) { ++ increaseIndent(false, false); ++ writeBlockComment(); ++ if(event instanceof ScalarEvent) { ++ analysis = analyzeScalar(((ScalarEvent)event).getValue()); ++ if (!analysis.isEmpty()) { ++ writeIndent(); ++ } ++ } ++ indent = indents.pop(); ++ } ++ states.push(new ExpectBlockSequenceItem(false)); ++ expectNode(false, false, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ } ++ } ++ } ++ ++ // Block mapping handlers. ++ private void expectBlockMapping() throws IOException { ++ increaseIndent(false, false); ++ state = new ExpectFirstBlockMappingKey(); ++ } ++ ++ private class ExpectFirstBlockMappingKey implements EmitterState { ++ public void expect() throws IOException { ++ new ExpectBlockMappingKey(true).expect(); ++ } ++ } ++ ++ private class ExpectBlockMappingKey implements EmitterState { ++ private final boolean first; ++ ++ public ExpectBlockMappingKey(boolean first) { ++ this.first = first; ++ } ++ ++ public void expect() throws IOException { ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ if (!this.first && event instanceof MappingEndEvent) { ++ indent = indents.pop(); ++ state = states.pop(); ++ } else { ++ writeIndent(); ++ if (checkSimpleKey()) { ++ states.push(new ExpectBlockMappingSimpleValue()); ++ expectNode(false, true, true); ++ } else { ++ writeIndicator("?", true, false, true); ++ states.push(new ExpectBlockMappingValue()); ++ expectNode(false, true, false); ++ } ++ } ++ } ++ } ++ ++ private boolean isFoldedOrLiteral(Event event) { ++ if(!event.is(ID.Scalar)) { ++ return false; ++ } ++ ScalarEvent scalarEvent = (ScalarEvent) event; ++ ScalarStyle style = scalarEvent.getScalarStyle(); ++ return style == ScalarStyle.FOLDED || style == ScalarStyle.LITERAL; ++ } ++ ++ private class ExpectBlockMappingSimpleValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndicator(":", false, false, false); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ if(!isFoldedOrLiteral(event)) { ++ if(writeInlineComments()) { ++ increaseIndent(true, false); ++ writeIndent(); ++ indent = indents.pop(); ++ } ++ } ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ if(!blockCommentsCollector.isEmpty()) { ++ increaseIndent(true, false); ++ writeBlockComment(); ++ writeIndent(); ++ indent = indents.pop(); ++ } ++ states.push(new ExpectBlockMappingKey(false)); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(); ++ writeInlineComments(); ++ } ++ } ++ ++ private class ExpectBlockMappingValue implements EmitterState { ++ public void expect() throws IOException { ++ writeIndent(); ++ writeIndicator(":", true, false, true); ++ event = inlineCommentsCollector.collectEventsAndPoll(event); ++ writeInlineComments(); ++ event = blockCommentsCollector.collectEventsAndPoll(event); ++ writeBlockComment(); ++ states.push(new ExpectBlockMappingKey(false)); ++ expectNode(false, true, false); ++ inlineCommentsCollector.collectEvents(event); ++ writeInlineComments(); ++ } ++ } ++ ++ // Checkers. ++ ++ private boolean checkEmptySequence() { ++ return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent; ++ } ++ ++ private boolean checkEmptyMapping() { ++ return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent; ++ } ++ ++ private boolean checkEmptyDocument() { ++ if (!(event instanceof DocumentStartEvent) || events.isEmpty()) { ++ return false; ++ } ++ Event event = events.peek(); ++ if (event instanceof ScalarEvent) { ++ ScalarEvent e = (ScalarEvent) event; ++ return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e ++ .getValue().length() == 0; ++ } ++ return false; ++ } ++ ++ private boolean checkSimpleKey() { ++ int length = 0; ++ if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) { ++ if (preparedAnchor == null) { ++ preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor()); ++ } ++ length += preparedAnchor.length(); ++ } ++ String tag = null; ++ if (event instanceof ScalarEvent) { ++ tag = ((ScalarEvent) event).getTag(); ++ } else if (event instanceof CollectionStartEvent) { ++ tag = ((CollectionStartEvent) event).getTag(); ++ } ++ if (tag != null) { ++ if (preparedTag == null) { ++ preparedTag = prepareTag(tag); ++ } ++ length += preparedTag.length(); ++ } ++ if (event instanceof ScalarEvent) { ++ if (analysis == null) { ++ analysis = analyzeScalar(((ScalarEvent) event).getValue()); ++ } ++ length += analysis.getScalar().length(); ++ } ++ return length < maxSimpleKeyLength && (event instanceof AliasEvent ++ || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline()) ++ || checkEmptySequence() || checkEmptyMapping()); ++ } ++ ++ // Anchor, Tag, and Scalar processors. ++ ++ private void processAnchor(String indicator) throws IOException { ++ NodeEvent ev = (NodeEvent) event; ++ if (ev.getAnchor() == null) { ++ preparedAnchor = null; ++ return; ++ } ++ if (preparedAnchor == null) { ++ preparedAnchor = prepareAnchor(ev.getAnchor()); ++ } ++ writeIndicator(indicator + preparedAnchor, true, false, false); ++ preparedAnchor = null; ++ } ++ ++ private void processTag() throws IOException { ++ String tag = null; ++ if (event instanceof ScalarEvent) { ++ ScalarEvent ev = (ScalarEvent) event; ++ tag = ev.getTag(); ++ if (style == null) { ++ style = chooseScalarStyle(); ++ } ++ if ((!canonical || tag == null) && ((style == null && ev.getImplicit() ++ .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit() ++ .canOmitTagInNonPlainScalar()))) { ++ preparedTag = null; ++ return; ++ } ++ if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) { ++ tag = "!"; ++ preparedTag = null; ++ } ++ } else { ++ CollectionStartEvent ev = (CollectionStartEvent) event; ++ tag = ev.getTag(); ++ if ((!canonical || tag == null) && ev.getImplicit()) { ++ preparedTag = null; ++ return; ++ } ++ } ++ if (tag == null) { ++ throw new EmitterException("tag is not specified"); ++ } ++ if (preparedTag == null) { ++ preparedTag = prepareTag(tag); ++ } ++ writeIndicator(preparedTag, true, false, false); ++ preparedTag = null; ++ } ++ ++ private DumperOptions.ScalarStyle chooseScalarStyle() { ++ ScalarEvent ev = (ScalarEvent) event; ++ if (analysis == null) { ++ analysis = analyzeScalar(ev.getValue()); ++ } ++ if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED || this.canonical) { ++ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; ++ } ++ if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) { ++ if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline())) ++ && ((flowLevel != 0 && analysis.isAllowFlowPlain()) || (flowLevel == 0 && analysis.isAllowBlockPlain()))) { ++ return null; ++ } ++ } ++ if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) { ++ if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) { ++ return ev.getScalarStyle(); ++ } ++ } ++ if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) { ++ if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) { ++ return DumperOptions.ScalarStyle.SINGLE_QUOTED; ++ } ++ } ++ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; ++ } ++ ++ private void processScalar() throws IOException { ++ ScalarEvent ev = (ScalarEvent) event; ++ if (analysis == null) { ++ analysis = analyzeScalar(ev.getValue()); ++ } ++ if (style == null) { ++ style = chooseScalarStyle(); ++ } ++ boolean split = !simpleKeyContext && splitLines; ++ if (style == null) { ++ writePlain(analysis.getScalar(), split); ++ } else { ++ switch (style) { ++ case DOUBLE_QUOTED: ++ writeDoubleQuoted(analysis.getScalar(), split); ++ break; ++ case SINGLE_QUOTED: ++ writeSingleQuoted(analysis.getScalar(), split); ++ break; ++ case FOLDED: ++ writeFolded(analysis.getScalar(), split); ++ break; ++ case LITERAL: ++ writeLiteral(analysis.getScalar()); ++ break; ++ default: ++ throw new YAMLException("Unexpected style: " + style); ++ } ++ } ++ analysis = null; ++ style = null; ++ } ++ ++ // Analyzers. ++ ++ private String prepareVersion(Version version) { ++ if (version.major() != 1) { ++ throw new EmitterException("unsupported YAML version: " + version); ++ } ++ return version.getRepresentation(); ++ } ++ ++ private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$"); ++ ++ private String prepareTagHandle(String handle) { ++ if (handle.length() == 0) { ++ throw new EmitterException("tag handle must not be empty"); ++ } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') { ++ throw new EmitterException("tag handle must start and end with '!': " + handle); ++ } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) { ++ throw new EmitterException("invalid character in the tag handle: " + handle); ++ } ++ return handle; ++ } ++ ++ private String prepareTagPrefix(String prefix) { ++ if (prefix.length() == 0) { ++ throw new EmitterException("tag prefix must not be empty"); ++ } ++ StringBuilder chunks = new StringBuilder(); ++ int start = 0; ++ int end = 0; ++ if (prefix.charAt(0) == '!') { ++ end = 1; ++ } ++ while (end < prefix.length()) { ++ end++; ++ } ++ if (start < end) { ++ chunks.append(prefix, start, end); ++ } ++ return chunks.toString(); ++ } ++ ++ private String prepareTag(String tag) { ++ if (tag.length() == 0) { ++ throw new EmitterException("tag must not be empty"); ++ } ++ if ("!".equals(tag)) { ++ return tag; ++ } ++ String handle = null; ++ String suffix = tag; ++ // shall the tag prefixes be sorted as in PyYAML? ++ for (String prefix : tagPrefixes.keySet()) { ++ if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) { ++ handle = prefix; ++ } ++ } ++ if (handle != null) { ++ suffix = tag.substring(handle.length()); ++ handle = tagPrefixes.get(handle); ++ } ++ ++ int end = suffix.length(); ++ String suffixText = end > 0 ? suffix.substring(0, end) : ""; ++ ++ if (handle != null) { ++ return handle + suffixText; ++ } ++ return "!<" + suffixText + ">"; ++ } ++ ++ static String prepareAnchor(String anchor) { ++ if (anchor.length() == 0) { ++ throw new EmitterException("anchor must not be empty"); ++ } ++ for (Character invalid : INVALID_ANCHOR) { ++ if (anchor.indexOf(invalid) > -1) { ++ throw new EmitterException("Invalid character '" + invalid + "' in the anchor: " + anchor); ++ } ++ } ++ Matcher matcher = SPACES_PATTERN.matcher(anchor); ++ if (matcher.find()) { ++ throw new EmitterException("Anchor may not contain spaces: " + anchor); ++ } ++ return anchor; ++ } ++ ++ private ScalarAnalysis analyzeScalar(String scalar) { ++ // Empty scalar is a special case. ++ if (scalar.length() == 0) { ++ return new ScalarAnalysis(scalar, true, false, false, true, true, false); ++ } ++ // Indicators and special characters. ++ boolean blockIndicators = false; ++ boolean flowIndicators = false; ++ boolean lineBreaks = false; ++ boolean specialCharacters = false; ++ ++ // Important whitespace combinations. ++ boolean leadingSpace = false; ++ boolean leadingBreak = false; ++ boolean trailingSpace = false; ++ boolean trailingBreak = false; ++ boolean breakSpace = false; ++ boolean spaceBreak = false; ++ ++ // Check document indicators. ++ if (scalar.startsWith("---") || scalar.startsWith("...")) { ++ blockIndicators = true; ++ flowIndicators = true; ++ } ++ // First character or preceded by a whitespace. ++ boolean preceededByWhitespace = true; ++ boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1)); ++ // The previous character is a space. ++ boolean previousSpace = false; ++ ++ // The previous character is a break. ++ boolean previousBreak = false; ++ ++ int index = 0; ++ ++ while (index < scalar.length()) { ++ int c = scalar.codePointAt(index); ++ // Check for indicators. ++ if (index == 0) { ++ // Leading indicators are special characters. ++ if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ if (c == '?' || c == ':') { ++ flowIndicators = true; ++ if (followedByWhitespace) { ++ blockIndicators = true; ++ } ++ } ++ if (c == '-' && followedByWhitespace) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ } else { ++ // Some indicators cannot appear within a scalar as well. ++ if (",?[]{}".indexOf(c) != -1) { ++ flowIndicators = true; ++ } ++ if (c == ':') { ++ flowIndicators = true; ++ if (followedByWhitespace) { ++ blockIndicators = true; ++ } ++ } ++ if (c == '#' && preceededByWhitespace) { ++ flowIndicators = true; ++ blockIndicators = true; ++ } ++ } ++ // Check for line breaks, special, and unicode characters. ++ boolean isLineBreak = Constant.LINEBR.has(c); ++ if (isLineBreak) { ++ lineBreaks = true; ++ } ++ if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) { ++ if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) ++ || (c >= 0xE000 && c <= 0xFFFD) ++ || (c >= 0x10000 && c <= 0x10FFFF)) { ++ // unicode is used ++ if (!this.allowUnicode) { ++ specialCharacters = true; ++ } ++ } else { ++ specialCharacters = true; ++ } ++ } ++ // Detect important whitespace combinations. ++ if (c == ' ') { ++ if (index == 0) { ++ leadingSpace = true; ++ } ++ if (index == scalar.length() - 1) { ++ trailingSpace = true; ++ } ++ if (previousBreak) { ++ breakSpace = true; ++ } ++ previousSpace = true; ++ previousBreak = false; ++ } else if (isLineBreak) { ++ if (index == 0) { ++ leadingBreak = true; ++ } ++ if (index == scalar.length() - 1) { ++ trailingBreak = true; ++ } ++ if (previousSpace) { ++ spaceBreak = true; ++ } ++ previousSpace = false; ++ previousBreak = true; ++ } else { ++ previousSpace = false; ++ previousBreak = false; ++ } ++ ++ // Prepare for the next character. ++ index += Character.charCount(c); ++ preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak; ++ followedByWhitespace = true; ++ if (index + 1 < scalar.length()) { ++ int nextIndex = index + Character.charCount(scalar.codePointAt(index)); ++ if (nextIndex < scalar.length()) { ++ followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak; ++ } ++ } ++ } ++ // Let's decide what styles are allowed. ++ boolean allowFlowPlain = true; ++ boolean allowBlockPlain = true; ++ boolean allowSingleQuoted = true; ++ boolean allowBlock = true; ++ // Leading and trailing whitespaces are bad for plain scalars. ++ if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) { ++ allowFlowPlain = allowBlockPlain = false; ++ } ++ // We do not permit trailing spaces for block scalars. ++ if (trailingSpace) { ++ allowBlock = false; ++ } ++ // Spaces at the beginning of a new line are only acceptable for block ++ // scalars. ++ if (breakSpace) { ++ allowFlowPlain = allowBlockPlain = allowSingleQuoted = false; ++ } ++ // Spaces followed by breaks, as well as special character are only ++ // allowed for double quoted scalars. ++ if (spaceBreak || specialCharacters) { ++ allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false; ++ } ++ // Although the plain scalar writer supports breaks, we never emit ++ // multiline plain scalars in the flow context. ++ if (lineBreaks) { ++ allowFlowPlain = false; ++ } ++ // Flow indicators are forbidden for flow plain scalars. ++ if (flowIndicators) { ++ allowFlowPlain = false; ++ } ++ // Block indicators are forbidden for block plain scalars. ++ if (blockIndicators) { ++ allowBlockPlain = false; ++ } ++ ++ return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain, ++ allowSingleQuoted, allowBlock); ++ } ++ ++ // Writers. ++ ++ void flushStream() throws IOException { ++ stream.flush(); ++ } ++ ++ void writeStreamStart() { ++ // BOM is written by Writer. ++ } ++ ++ void writeStreamEnd() throws IOException { ++ flushStream(); ++ } ++ ++ void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace, ++ boolean indentation) throws IOException { ++ if (!this.whitespace && needWhitespace) { ++ this.column++; ++ stream.write(SPACE); ++ } ++ this.whitespace = whitespace; ++ this.indention = this.indention && indentation; ++ this.column += indicator.length(); ++ openEnded = false; ++ stream.write(indicator); ++ } ++ ++ void writeIndent() throws IOException { ++ int indent; ++ if (this.indent != null) { ++ indent = this.indent; ++ } else { ++ indent = 0; ++ } ++ ++ if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) { ++ writeLineBreak(null); ++ } ++ ++ writeWhitespace(indent - this.column); ++ } ++ ++ private void writeWhitespace(int length) throws IOException { ++ if (length <= 0) { ++ return; ++ } ++ this.whitespace = true; ++ char[] data = new char[length]; ++ for (int i = 0; i < data.length; i++) { ++ data[i] = ' '; ++ } ++ this.column += length; ++ stream.write(data); ++ } ++ ++ private void writeLineBreak(String data) throws IOException { ++ this.whitespace = true; ++ this.indention = true; ++ this.column = 0; ++ if (data == null) { ++ stream.write(this.bestLineBreak); ++ } else { ++ stream.write(data); ++ } ++ } ++ ++ void writeVersionDirective(String versionText) throws IOException { ++ stream.write("%YAML "); ++ stream.write(versionText); ++ writeLineBreak(null); ++ } ++ ++ void writeTagDirective(String handleText, String prefixText) throws IOException { ++ // XXX: not sure 4 invocations better then StringBuilders created by str ++ // + str ++ stream.write("%TAG "); ++ stream.write(handleText); ++ stream.write(SPACE); ++ stream.write(prefixText); ++ writeLineBreak(null); ++ } ++ ++ // Scalar streams. ++ private void writeSingleQuoted(String text, boolean split) throws IOException { ++ writeIndicator("'", true, false, false); ++ boolean spaces = false; ++ boolean breaks = false; ++ int start = 0, end = 0; ++ char ch; ++ while (end <= text.length()) { ++ ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (spaces) { ++ if (ch == 0 || ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split && start != 0 ++ && end != text.length()) { ++ writeIndent(); ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ if (text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ writeIndent(); ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 '")) { ++ if (start < end) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ } ++ } ++ if (ch == '\'') { ++ this.column += 2; ++ stream.write("''"); ++ start = end + 1; ++ } ++ if (ch != 0) { ++ spaces = ch == ' '; ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ writeIndicator("'", false, false, false); ++ } ++ ++ private void writeDoubleQuoted(String text, boolean split) throws IOException { ++ writeIndicator("\"", true, false, false); ++ int start = 0; ++ int end = 0; ++ while (end <= text.length()) { ++ Character ch = null; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1 ++ || !('\u0020' <= ch && ch <= '\u007E')) { ++ if (start < end) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ if (ch != null) { ++ String data; ++ if (ESCAPE_REPLACEMENTS.containsKey(ch)) { ++ data = "\\" + ESCAPE_REPLACEMENTS.get(ch); ++ } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) { ++ // if !allowUnicode or the character is not printable, ++ // we must encode it ++ if (ch <= '\u00FF') { ++ String s = "0" + Integer.toString(ch, 16); ++ data = "\\x" + s.substring(s.length() - 2); ++ } else if (ch >= '\uD800' && ch <= '\uDBFF') { ++ if (end + 1 < text.length()) { ++ Character ch2 = text.charAt(++end); ++ String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2)); ++ data = "\\U" + s.substring(s.length() - 8); ++ } else { ++ String s = "000" + Integer.toString(ch, 16); ++ data = "\\u" + s.substring(s.length() - 4); ++ } ++ } else { ++ String s = "000" + Integer.toString(ch, 16); ++ data = "\\u" + s.substring(s.length() - 4); ++ } ++ } else { ++ data = String.valueOf(ch); ++ } ++ this.column += data.length(); ++ stream.write(data); ++ start = end + 1; ++ } ++ } ++ if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end) ++ && (this.column + (end - start)) > this.bestWidth && split) { ++ String data; ++ if (start >= end) { ++ data = "\\"; ++ } else { ++ data = text.substring(start, end) + "\\"; ++ } ++ if (start < end) { ++ start = end; ++ } ++ this.column += data.length(); ++ stream.write(data); ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ if (text.charAt(start) == ' ') { ++ data = "\\"; ++ this.column += data.length(); ++ stream.write(data); ++ } ++ } ++ end += 1; ++ } ++ writeIndicator("\"", false, false, false); ++ } ++ ++ private boolean writeCommentLines(List commentLines) throws IOException { ++ boolean wroteComment = false; ++ if(emitComments) { ++ int indentColumns = 0; ++ boolean firstComment = true; ++ for (CommentLine commentLine : commentLines) { ++ if (commentLine.getCommentType() != CommentType.BLANK_LINE) { ++ if (firstComment) { ++ firstComment = false; ++ writeIndicator("#", commentLine.getCommentType() == CommentType.IN_LINE, false, false); ++ indentColumns = this.column > 0 ? this.column - 1 : 0; ++ } else { ++ writeWhitespace(indentColumns); ++ writeIndicator("#", false, false, false); ++ } ++ stream.write(commentLine.getValue()); ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(null); ++ writeIndent(); ++ } ++ wroteComment = true; ++ } ++ } ++ return wroteComment; ++ } ++ ++ private void writeBlockComment() throws IOException { ++ if(!blockCommentsCollector.isEmpty()) { ++ writeIndent(); ++ writeCommentLines(blockCommentsCollector.consume()); ++ } ++ } ++ ++ private boolean writeInlineComments() throws IOException { ++ return writeCommentLines(inlineCommentsCollector.consume()); ++ } ++ ++ private String determineBlockHints(String text) { ++ StringBuilder hints = new StringBuilder(); ++ if (Constant.LINEBR.has(text.charAt(0), " ")) { ++ hints.append(bestIndent); ++ } ++ char ch1 = text.charAt(text.length() - 1); ++ if (Constant.LINEBR.hasNo(ch1)) { ++ hints.append("-"); ++ } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) { ++ hints.append("+"); ++ } ++ return hints.toString(); ++ } ++ ++ void writeFolded(String text, boolean split) throws IOException { ++ String hints = determineBlockHints(text); ++ writeIndicator(">" + hints, true, false, false); ++ if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { ++ openEnded = true; ++ } ++ if(!writeInlineComments()) { ++ writeLineBreak(null); ++ } ++ boolean leadingSpace = true; ++ boolean spaces = false; ++ boolean breaks = true; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ leadingSpace = ch == ' '; ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ if (ch != 0) { ++ writeIndent(); ++ } ++ start = end; ++ } ++ } else if (spaces) { ++ if (ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split) { ++ writeIndent(); ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 ")) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ if (ch == 0) { ++ writeLineBreak(null); ++ } ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ breaks = Constant.LINEBR.has(ch); ++ spaces = ch == ' '; ++ } ++ end++; ++ } ++ } ++ ++ void writeLiteral(String text) throws IOException { ++ String hints = determineBlockHints(text); ++ writeIndicator("|" + hints, true, false, false); ++ if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { ++ openEnded = true; ++ } ++ if(!writeInlineComments()) { ++ writeLineBreak(null); ++ } ++ boolean breaks = true; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (breaks) { ++ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ if (ch != 0) { ++ writeIndent(); ++ } ++ start = end; ++ } ++ } else { ++ if (ch == 0 || Constant.LINEBR.has(ch)) { ++ stream.write(text, start, end - start); ++ if (ch == 0) { ++ writeLineBreak(null); ++ } ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ } ++ ++ void writePlain(String text, boolean split) throws IOException { ++ if (rootContext) { ++ openEnded = true; ++ } ++ if (text.length() == 0) { ++ return; ++ } ++ if (!this.whitespace) { ++ this.column++; ++ stream.write(SPACE); ++ } ++ this.whitespace = false; ++ this.indention = false; ++ boolean spaces = false; ++ boolean breaks = false; ++ int start = 0, end = 0; ++ while (end <= text.length()) { ++ char ch = 0; ++ if (end < text.length()) { ++ ch = text.charAt(end); ++ } ++ if (spaces) { ++ if (ch != ' ') { ++ if (start + 1 == end && this.column > this.bestWidth && split) { ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ } else { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ } ++ start = end; ++ } ++ } else if (breaks) { ++ if (Constant.LINEBR.hasNo(ch)) { ++ if (text.charAt(start) == '\n') { ++ writeLineBreak(null); ++ } ++ String data = text.substring(start, end); ++ for (char br : data.toCharArray()) { ++ if (br == '\n') { ++ writeLineBreak(null); ++ } else { ++ writeLineBreak(String.valueOf(br)); ++ } ++ } ++ writeIndent(); ++ this.whitespace = false; ++ this.indention = false; ++ start = end; ++ } ++ } else { ++ if (Constant.LINEBR.has(ch, "\0 ")) { ++ int len = end - start; ++ this.column += len; ++ stream.write(text, start, len); ++ start = end; ++ } ++ } ++ if (ch != 0) { ++ spaces = ch == ' '; ++ breaks = Constant.LINEBR.has(ch); ++ } ++ end++; ++ } ++ } ++} diff --git a/patches/server/0821-Validate-usernames.patch b/patches/server/0821-Validate-usernames.patch deleted file mode 100644 index c032b67744..0000000000 --- a/patches/server/0821-Validate-usernames.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 1 Jan 2022 05:19:37 -0800 -Subject: [PATCH] Validate usernames - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index acd581d14e0ef1fe5a6545ee67be00deff589879..553eb8e437b07376dbfc54b0018bcc3ff93e4461 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -66,6 +66,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - @Nullable - private ProfilePublicKey.Data profilePublicKeyData; - public String hostname = ""; // CraftBukkit - add field -+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -255,10 +256,38 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - } - } - -+ // Paper start - validate usernames -+ public static boolean validateUsername(String in) { -+ if (in == null || in.isEmpty() || in.length() > 16) { -+ return false; -+ } -+ -+ for (int i = 0, len = in.length(); i < len; ++i) { -+ char c = in.charAt(i); -+ -+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) { -+ continue; -+ } -+ -+ return false; -+ } -+ -+ return true; -+ } -+ // Paper end - validate usernames -+ - @Override - public void handleHello(ServerboundHelloPacket packet) { - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); - Validate.validState(ServerLoginPacketListenerImpl.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]); -+ // Paper start - validate usernames -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation) { -+ if (!this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation && !validateUsername(packet.name())) { -+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); -+ return; -+ } -+ } -+ // Paper end - validate usernames - this.profilePublicKeyData = (ProfilePublicKey.Data) packet.publicKey().orElse(null); // CraftBukkit - decompile error - GameProfile gameprofile = this.server.getSingleplayerProfile(); - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 70d648bc5e795355d28579cc2fda43c3c9eb255d..67f90c75aa4858bf1575bf7b0a62b8113de7c2ea 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -714,7 +714,7 @@ public abstract class PlayerList { - - for (int i = 0; i < this.players.size(); ++i) { - entityplayer = (ServerPlayer) this.players.get(i); -- if (entityplayer.getUUID().equals(uuid)) { -+ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames - list.add(entityplayer); - } - } diff --git a/patches/server/0822-Fix-saving-configs-with-more-long-comments.patch b/patches/server/0822-Fix-saving-configs-with-more-long-comments.patch deleted file mode 100644 index 7d52b01085..0000000000 --- a/patches/server/0822-Fix-saving-configs-with-more-long-comments.patch +++ /dev/null @@ -1,1702 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 1 Jan 2022 21:24:50 -0800 -Subject: [PATCH] Fix saving configs with more long comments - -This is a bug with snakeyaml -PR: https://bitbucket.org/snakeyaml/snakeyaml/pull-requests/3 -Issue: https://bitbucket.org/snakeyaml/snakeyaml/issues/518/comments-could-cause-queue-full - -Added the entire Emitter class from snakeyaml because -dev-imports doesn't work with non-mojang-added dependencies - -Replacement for upstream: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/a7505b3cd0498baca152777767f0e4ddebbe4d1a - -diff --git a/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ca32c00775f3d98abea39bbfeb0268bb9efbc12b ---- /dev/null -+++ b/src/main/java/org/yaml/snakeyaml/emitter/Emitter.java -@@ -0,0 +1,1682 @@ -+/** -+ * Copyright (c) 2008, SnakeYAML -+ * -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at -+ * -+ * http://www.apache.org/licenses/LICENSE-2.0 -+ * -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. -+ */ -+package org.yaml.snakeyaml.emitter; -+ -+import org.yaml.snakeyaml.DumperOptions; -+import org.yaml.snakeyaml.DumperOptions.ScalarStyle; -+import org.yaml.snakeyaml.DumperOptions.Version; -+import org.yaml.snakeyaml.comments.CommentEventsCollector; -+import org.yaml.snakeyaml.comments.CommentLine; -+import org.yaml.snakeyaml.comments.CommentType; -+import org.yaml.snakeyaml.error.YAMLException; -+import org.yaml.snakeyaml.events.AliasEvent; -+import org.yaml.snakeyaml.events.CollectionEndEvent; -+import org.yaml.snakeyaml.events.CollectionStartEvent; -+import org.yaml.snakeyaml.events.CommentEvent; -+import org.yaml.snakeyaml.events.DocumentEndEvent; -+import org.yaml.snakeyaml.events.DocumentStartEvent; -+import org.yaml.snakeyaml.events.Event; -+import org.yaml.snakeyaml.events.Event.ID; -+import org.yaml.snakeyaml.events.MappingEndEvent; -+import org.yaml.snakeyaml.events.MappingStartEvent; -+import org.yaml.snakeyaml.events.NodeEvent; -+import org.yaml.snakeyaml.events.ScalarEvent; -+import org.yaml.snakeyaml.events.SequenceEndEvent; -+import org.yaml.snakeyaml.events.SequenceStartEvent; -+import org.yaml.snakeyaml.events.StreamEndEvent; -+import org.yaml.snakeyaml.events.StreamStartEvent; -+import org.yaml.snakeyaml.nodes.Tag; -+import org.yaml.snakeyaml.reader.StreamReader; -+import org.yaml.snakeyaml.scanner.Constant; -+import org.yaml.snakeyaml.util.ArrayStack; -+ -+import java.io.IOException; -+import java.io.Writer; -+import java.util.ArrayDeque; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.LinkedHashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.Queue; -+import java.util.Set; -+import java.util.TreeSet; -+import java.util.concurrent.ArrayBlockingQueue; -+import java.util.regex.Matcher; -+import java.util.regex.Pattern; -+ -+/** -+ *
-+ * Emitter expects events obeying the following grammar:
-+ * stream ::= STREAM-START document* STREAM-END
-+ * document ::= DOCUMENT-START node DOCUMENT-END
-+ * node ::= SCALAR | sequence | mapping
-+ * sequence ::= SEQUENCE-START node* SEQUENCE-END
-+ * mapping ::= MAPPING-START (node node)* MAPPING-END
-+ * 
-+ */ -+public final class Emitter implements Emitable { -+ public static final int MIN_INDENT = 1; -+ public static final int MAX_INDENT = 10; -+ private static final char[] SPACE = {' '}; -+ -+ private static final Pattern SPACES_PATTERN = Pattern.compile("\\s"); -+ private static final Set INVALID_ANCHOR = new HashSet(); -+ static { -+ INVALID_ANCHOR.add('['); -+ INVALID_ANCHOR.add(']'); -+ INVALID_ANCHOR.add('{'); -+ INVALID_ANCHOR.add('}'); -+ INVALID_ANCHOR.add(','); -+ INVALID_ANCHOR.add('*'); -+ INVALID_ANCHOR.add('&'); -+ } -+ -+ private static final Map ESCAPE_REPLACEMENTS = new HashMap(); -+ static { -+ ESCAPE_REPLACEMENTS.put('\0', "0"); -+ ESCAPE_REPLACEMENTS.put('\u0007', "a"); -+ ESCAPE_REPLACEMENTS.put('\u0008', "b"); -+ ESCAPE_REPLACEMENTS.put('\u0009', "t"); -+ ESCAPE_REPLACEMENTS.put('\n', "n"); -+ ESCAPE_REPLACEMENTS.put('\u000B', "v"); -+ ESCAPE_REPLACEMENTS.put('\u000C', "f"); -+ ESCAPE_REPLACEMENTS.put('\r', "r"); -+ ESCAPE_REPLACEMENTS.put('\u001B', "e"); -+ ESCAPE_REPLACEMENTS.put('"', "\""); -+ ESCAPE_REPLACEMENTS.put('\\', "\\"); -+ ESCAPE_REPLACEMENTS.put('\u0085', "N"); -+ ESCAPE_REPLACEMENTS.put('\u00A0', "_"); -+ ESCAPE_REPLACEMENTS.put('\u2028', "L"); -+ ESCAPE_REPLACEMENTS.put('\u2029', "P"); -+ } -+ -+ private final static Map DEFAULT_TAG_PREFIXES = new LinkedHashMap(); -+ static { -+ DEFAULT_TAG_PREFIXES.put("!", "!"); -+ DEFAULT_TAG_PREFIXES.put(Tag.PREFIX, "!!"); -+ } -+ // The stream should have the methods `write` and possibly `flush`. -+ private final Writer stream; -+ -+ // Encoding is defined by Writer (cannot be overridden by STREAM-START.) -+ // private Charset encoding; -+ -+ // Emitter is a state machine with a stack of states to handle nested -+ // structures. -+ private final ArrayStack states; -+ private EmitterState state; -+ -+ // Current event and the event queue. -+ private final Queue events; -+ private Event event; -+ -+ // The current indentation level and the stack of previous indents. -+ private final ArrayStack indents; -+ private Integer indent; -+ -+ // Flow level. -+ private int flowLevel; -+ -+ // Contexts. -+ private boolean rootContext; -+ private boolean mappingContext; -+ private boolean simpleKeyContext; -+ -+ // -+ // Characteristics of the last emitted character: -+ // - current position. -+ // - is it a whitespace? -+ // - is it an indention character -+ // (indentation space, '-', '?', or ':')? -+ // private int line; this variable is not used -+ private int column; -+ private boolean whitespace; -+ private boolean indention; -+ private boolean openEnded; -+ -+ // Formatting details. -+ private final Boolean canonical; -+ // pretty print flow by adding extra line breaks -+ private final Boolean prettyFlow; -+ -+ private final boolean allowUnicode; -+ private int bestIndent; -+ private final int indicatorIndent; -+ private final boolean indentWithIndicator; -+ private int bestWidth; -+ private final char[] bestLineBreak; -+ private final boolean splitLines; -+ private final int maxSimpleKeyLength; -+ private final boolean emitComments; -+ -+ // Tag prefixes. -+ private Map tagPrefixes; -+ -+ // Prepared anchor and tag. -+ private String preparedAnchor; -+ private String preparedTag; -+ -+ // Scalar analysis and style. -+ private ScalarAnalysis analysis; -+ private DumperOptions.ScalarStyle style; -+ -+ // Comment processing -+ private final CommentEventsCollector blockCommentsCollector; -+ private final CommentEventsCollector inlineCommentsCollector; -+ -+ -+ public Emitter(Writer stream, DumperOptions opts) { -+ // The stream should have the methods `write` and possibly `flush`. -+ this.stream = stream; -+ // Emitter is a state machine with a stack of states to handle nested -+ // structures. -+ this.states = new ArrayStack(100); -+ this.state = new ExpectStreamStart(); -+ // Current event and the event queue. -+ this.events = new ArrayDeque<>(100); // Paper - allow more than 100 events (or comments) -+ // this.events = new ArrayBlockingQueue<>(100); -+ this.event = null; -+ // The current indentation level and the stack of previous indents. -+ this.indents = new ArrayStack(10); -+ this.indent = null; -+ // Flow level. -+ this.flowLevel = 0; -+ // Contexts. -+ mappingContext = false; -+ simpleKeyContext = false; -+ -+ // -+ // Characteristics of the last emitted character: -+ // - current position. -+ // - is it a whitespace? -+ // - is it an indention character -+ // (indentation space, '-', '?', or ':')? -+ column = 0; -+ whitespace = true; -+ indention = true; -+ -+ // Whether the document requires an explicit document indicator -+ openEnded = false; -+ -+ // Formatting details. -+ this.canonical = opts.isCanonical(); -+ this.prettyFlow = opts.isPrettyFlow(); -+ this.allowUnicode = opts.isAllowUnicode(); -+ this.bestIndent = 2; -+ if ((opts.getIndent() > MIN_INDENT) && (opts.getIndent() < MAX_INDENT)) { -+ this.bestIndent = opts.getIndent(); -+ } -+ this.indicatorIndent = opts.getIndicatorIndent(); -+ this.indentWithIndicator = opts.getIndentWithIndicator(); -+ this.bestWidth = 80; -+ if (opts.getWidth() > this.bestIndent * 2) { -+ this.bestWidth = opts.getWidth(); -+ } -+ this.bestLineBreak = opts.getLineBreak().getString().toCharArray(); -+ this.splitLines = opts.getSplitLines(); -+ this.maxSimpleKeyLength = opts.getMaxSimpleKeyLength(); -+ this.emitComments = opts.isProcessComments(); -+ -+ // Tag prefixes. -+ this.tagPrefixes = new LinkedHashMap(); -+ -+ // Prepared anchor and tag. -+ this.preparedAnchor = null; -+ this.preparedTag = null; -+ -+ // Scalar analysis and style. -+ this.analysis = null; -+ this.style = null; -+ -+ // Comment processing -+ this.blockCommentsCollector = new CommentEventsCollector(events, -+ CommentType.BLANK_LINE, CommentType.BLOCK); -+ this.inlineCommentsCollector = new CommentEventsCollector(events, -+ CommentType.IN_LINE); -+ } -+ -+ public void emit(Event event) throws IOException { -+ this.events.add(event); -+ while (!needMoreEvents()) { -+ this.event = this.events.poll(); -+ this.state.expect(); -+ this.event = null; -+ } -+ } -+ -+ // In some cases, we wait for a few next events before emitting. -+ -+ private boolean needMoreEvents() { -+ if (events.isEmpty()) { -+ return true; -+ } -+ -+ Iterator iter = events.iterator(); -+ Event event = iter.next(); // FIXME why without check ??? -+ while(event instanceof CommentEvent) { -+ if (!iter.hasNext()) { -+ return true; -+ } -+ event = iter.next(); -+ } -+ -+ if (event instanceof DocumentStartEvent) { -+ return needEvents(iter, 1); -+ } else if (event instanceof SequenceStartEvent) { -+ return needEvents(iter, 2); -+ } else if (event instanceof MappingStartEvent) { -+ return needEvents(iter, 3); -+ } else if (event instanceof StreamStartEvent) { -+ return needEvents(iter, 2); -+ } else if (event instanceof StreamEndEvent) { -+ return false; -+ } else if (emitComments) { -+ return needEvents(iter, 1); -+ } -+ return false; -+ } -+ -+ private boolean needEvents(Iterator iter, int count) { -+ int level = 0; -+ int actualCount = 0; -+ while (iter.hasNext()) { -+ Event event = iter.next(); -+ if (event instanceof CommentEvent) { -+ continue; -+ } -+ actualCount++; -+ if (event instanceof DocumentStartEvent || event instanceof CollectionStartEvent) { -+ level++; -+ } else if (event instanceof DocumentEndEvent || event instanceof CollectionEndEvent) { -+ level--; -+ } else if (event instanceof StreamEndEvent) { -+ level = -1; -+ } -+ if (level < 0) { -+ return false; -+ } -+ } -+ return actualCount < count; -+ } -+ -+ private void increaseIndent(boolean flow, boolean indentless) { -+ indents.push(indent); -+ if (indent == null) { -+ if (flow) { -+ indent = bestIndent; -+ } else { -+ indent = 0; -+ } -+ } else if (!indentless) { -+ this.indent += bestIndent; -+ } -+ } -+ -+ // States -+ -+ // Stream handlers. -+ -+ private class ExpectStreamStart implements EmitterState { -+ public void expect() throws IOException { -+ if (event instanceof StreamStartEvent) { -+ writeStreamStart(); -+ state = new ExpectFirstDocumentStart(); -+ } else { -+ throw new EmitterException("expected StreamStartEvent, but got " + event); -+ } -+ } -+ } -+ -+ private class ExpectNothing implements EmitterState { -+ public void expect() throws IOException { -+ throw new EmitterException("expecting nothing, but got " + event); -+ } -+ } -+ -+ // Document handlers. -+ -+ private class ExpectFirstDocumentStart implements EmitterState { -+ public void expect() throws IOException { -+ new ExpectDocumentStart(true).expect(); -+ } -+ } -+ -+ private class ExpectDocumentStart implements EmitterState { -+ private final boolean first; -+ -+ public ExpectDocumentStart(boolean first) { -+ this.first = first; -+ } -+ -+ public void expect() throws IOException { -+ if (event instanceof DocumentStartEvent) { -+ DocumentStartEvent ev = (DocumentStartEvent) event; -+ if ((ev.getVersion() != null || ev.getTags() != null) && openEnded) { -+ writeIndicator("...", true, false, false); -+ writeIndent(); -+ } -+ if (ev.getVersion() != null) { -+ String versionText = prepareVersion(ev.getVersion()); -+ writeVersionDirective(versionText); -+ } -+ tagPrefixes = new LinkedHashMap(DEFAULT_TAG_PREFIXES); -+ if (ev.getTags() != null) { -+ Set handles = new TreeSet(ev.getTags().keySet()); -+ for (String handle : handles) { -+ String prefix = ev.getTags().get(handle); -+ tagPrefixes.put(prefix, handle); -+ String handleText = prepareTagHandle(handle); -+ String prefixText = prepareTagPrefix(prefix); -+ writeTagDirective(handleText, prefixText); -+ } -+ } -+ boolean implicit = first && !ev.getExplicit() && !canonical -+ && ev.getVersion() == null -+ && (ev.getTags() == null || ev.getTags().isEmpty()) -+ && !checkEmptyDocument(); -+ if (!implicit) { -+ writeIndent(); -+ writeIndicator("---", true, false, false); -+ if (canonical) { -+ writeIndent(); -+ } -+ } -+ state = new ExpectDocumentRoot(); -+ } else if (event instanceof StreamEndEvent) { -+ writeStreamEnd(); -+ state = new ExpectNothing(); -+ } else if (event instanceof CommentEvent) { -+ blockCommentsCollector.collectEvents(event); -+ writeBlockComment(); -+ // state = state; remains unchanged -+ } else { -+ throw new EmitterException("expected DocumentStartEvent, but got " + event); -+ } -+ } -+ } -+ -+ private class ExpectDocumentEnd implements EmitterState { -+ public void expect() throws IOException { -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ writeBlockComment(); -+ if (event instanceof DocumentEndEvent) { -+ writeIndent(); -+ if (((DocumentEndEvent) event).getExplicit()) { -+ writeIndicator("...", true, false, false); -+ writeIndent(); -+ } -+ flushStream(); -+ state = new ExpectDocumentStart(false); -+ } else { -+ throw new EmitterException("expected DocumentEndEvent, but got " + event); -+ } -+ } -+ } -+ -+ private class ExpectDocumentRoot implements EmitterState { -+ public void expect() throws IOException { -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ if (!blockCommentsCollector.isEmpty()) { -+ writeBlockComment(); -+ if (event instanceof DocumentEndEvent) { -+ new ExpectDocumentEnd().expect(); -+ return; -+ } -+ } -+ states.push(new ExpectDocumentEnd()); -+ expectNode(true, false, false); -+ } -+ } -+ -+ // Node handlers. -+ -+ private void expectNode(boolean root, boolean mapping, boolean simpleKey) throws IOException { -+ rootContext = root; -+ mappingContext = mapping; -+ simpleKeyContext = simpleKey; -+ if (event instanceof AliasEvent) { -+ expectAlias(); -+ } else if (event instanceof ScalarEvent || event instanceof CollectionStartEvent) { -+ processAnchor("&"); -+ processTag(); -+ if (event instanceof ScalarEvent) { -+ expectScalar(); -+ } else if (event instanceof SequenceStartEvent) { -+ if (flowLevel != 0 || canonical || ((SequenceStartEvent) event).isFlow() -+ || checkEmptySequence()) { -+ expectFlowSequence(); -+ } else { -+ expectBlockSequence(); -+ } -+ } else {// MappingStartEvent -+ if (flowLevel != 0 || canonical || ((MappingStartEvent) event).isFlow() -+ || checkEmptyMapping()) { -+ expectFlowMapping(); -+ } else { -+ expectBlockMapping(); -+ } -+ } -+ } else { -+ throw new EmitterException("expected NodeEvent, but got " + event); -+ } -+ } -+ -+ private void expectAlias() throws IOException { -+ if (!(event instanceof AliasEvent)) { -+ throw new EmitterException("Alias must be provided"); -+ } -+ processAnchor("*"); -+ state = states.pop(); -+ } -+ -+ private void expectScalar() throws IOException { -+ increaseIndent(true, false); -+ processScalar(); -+ indent = indents.pop(); -+ state = states.pop(); -+ } -+ -+ // Flow sequence handlers. -+ -+ private void expectFlowSequence() throws IOException { -+ writeIndicator("[", true, true, false); -+ flowLevel++; -+ increaseIndent(true, false); -+ if (prettyFlow) { -+ writeIndent(); -+ } -+ state = new ExpectFirstFlowSequenceItem(); -+ } -+ -+ private class ExpectFirstFlowSequenceItem implements EmitterState { -+ public void expect() throws IOException { -+ if (event instanceof SequenceEndEvent) { -+ indent = indents.pop(); -+ flowLevel--; -+ writeIndicator("]", false, false, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ state = states.pop(); -+ } else if (event instanceof CommentEvent) { -+ blockCommentsCollector.collectEvents(event); -+ writeBlockComment(); -+ } else { -+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { -+ writeIndent(); -+ } -+ states.push(new ExpectFlowSequenceItem()); -+ expectNode(false, false, false); -+ event = inlineCommentsCollector.collectEvents(event); -+ writeInlineComments(); -+ } -+ } -+ } -+ -+ private class ExpectFlowSequenceItem implements EmitterState { -+ public void expect() throws IOException { -+ if (event instanceof SequenceEndEvent) { -+ indent = indents.pop(); -+ flowLevel--; -+ if (canonical) { -+ writeIndicator(",", false, false, false); -+ writeIndent(); -+ } else if (prettyFlow) { -+ writeIndent(); -+ } -+ writeIndicator("]", false, false, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ if (prettyFlow) { -+ writeIndent(); -+ } -+ state = states.pop(); -+ } else if (event instanceof CommentEvent) { -+ event = blockCommentsCollector.collectEvents(event); -+ } else { -+ writeIndicator(",", false, false, false); -+ writeBlockComment(); -+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { -+ writeIndent(); -+ } -+ states.push(new ExpectFlowSequenceItem()); -+ expectNode(false, false, false); -+ event = inlineCommentsCollector.collectEvents(event); -+ writeInlineComments(); -+ } -+ } -+ } -+ -+ // Flow mapping handlers. -+ -+ private void expectFlowMapping() throws IOException { -+ writeIndicator("{", true, true, false); -+ flowLevel++; -+ increaseIndent(true, false); -+ if (prettyFlow) { -+ writeIndent(); -+ } -+ state = new ExpectFirstFlowMappingKey(); -+ } -+ -+ private class ExpectFirstFlowMappingKey implements EmitterState { -+ public void expect() throws IOException { -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ writeBlockComment(); -+ if (event instanceof MappingEndEvent) { -+ indent = indents.pop(); -+ flowLevel--; -+ writeIndicator("}", false, false, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ state = states.pop(); -+ } else { -+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { -+ writeIndent(); -+ } -+ if (!canonical && checkSimpleKey()) { -+ states.push(new ExpectFlowMappingSimpleValue()); -+ expectNode(false, true, true); -+ } else { -+ writeIndicator("?", true, false, false); -+ states.push(new ExpectFlowMappingValue()); -+ expectNode(false, true, false); -+ } -+ } -+ } -+ } -+ -+ private class ExpectFlowMappingKey implements EmitterState { -+ public void expect() throws IOException { -+ if (event instanceof MappingEndEvent) { -+ indent = indents.pop(); -+ flowLevel--; -+ if (canonical) { -+ writeIndicator(",", false, false, false); -+ writeIndent(); -+ } -+ if (prettyFlow) { -+ writeIndent(); -+ } -+ writeIndicator("}", false, false, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ state = states.pop(); -+ } else { -+ writeIndicator(",", false, false, false); -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ writeBlockComment(); -+ if (canonical || (column > bestWidth && splitLines) || prettyFlow) { -+ writeIndent(); -+ } -+ if (!canonical && checkSimpleKey()) { -+ states.push(new ExpectFlowMappingSimpleValue()); -+ expectNode(false, true, true); -+ } else { -+ writeIndicator("?", true, false, false); -+ states.push(new ExpectFlowMappingValue()); -+ expectNode(false, true, false); -+ } -+ } -+ } -+ } -+ -+ private class ExpectFlowMappingSimpleValue implements EmitterState { -+ public void expect() throws IOException { -+ writeIndicator(":", false, false, false); -+ event = inlineCommentsCollector.collectEventsAndPoll(event); -+ writeInlineComments(); -+ states.push(new ExpectFlowMappingKey()); -+ expectNode(false, true, false); -+ inlineCommentsCollector.collectEvents(event); -+ writeInlineComments(); -+ } -+ } -+ -+ private class ExpectFlowMappingValue implements EmitterState { -+ public void expect() throws IOException { -+ if (canonical || (column > bestWidth) || prettyFlow) { -+ writeIndent(); -+ } -+ writeIndicator(":", true, false, false); -+ event = inlineCommentsCollector.collectEventsAndPoll(event); -+ writeInlineComments(); -+ states.push(new ExpectFlowMappingKey()); -+ expectNode(false, true, false); -+ inlineCommentsCollector.collectEvents(event); -+ writeInlineComments(); -+ } -+ } -+ -+ // Block sequence handlers. -+ -+ private void expectBlockSequence() throws IOException { -+ boolean indentless = mappingContext && !indention; -+ increaseIndent(false, indentless); -+ state = new ExpectFirstBlockSequenceItem(); -+ } -+ -+ private class ExpectFirstBlockSequenceItem implements EmitterState { -+ public void expect() throws IOException { -+ new ExpectBlockSequenceItem(true).expect(); -+ } -+ } -+ -+ private class ExpectBlockSequenceItem implements EmitterState { -+ private final boolean first; -+ -+ public ExpectBlockSequenceItem(boolean first) { -+ this.first = first; -+ } -+ -+ public void expect() throws IOException { -+ if (!this.first && event instanceof SequenceEndEvent) { -+ indent = indents.pop(); -+ state = states.pop(); -+ } else if( event instanceof CommentEvent) { -+ blockCommentsCollector.collectEvents(event); -+ } else { -+ writeIndent(); -+ if (!indentWithIndicator || this.first) { -+ writeWhitespace(indicatorIndent); -+ } -+ writeIndicator("-", true, false, true); -+ if (indentWithIndicator && this.first) { -+ indent += indicatorIndent; -+ } -+ if (!blockCommentsCollector.isEmpty()) { -+ increaseIndent(false, false); -+ writeBlockComment(); -+ if(event instanceof ScalarEvent) { -+ analysis = analyzeScalar(((ScalarEvent)event).getValue()); -+ if (!analysis.isEmpty()) { -+ writeIndent(); -+ } -+ } -+ indent = indents.pop(); -+ } -+ states.push(new ExpectBlockSequenceItem(false)); -+ expectNode(false, false, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ } -+ } -+ } -+ -+ // Block mapping handlers. -+ private void expectBlockMapping() throws IOException { -+ increaseIndent(false, false); -+ state = new ExpectFirstBlockMappingKey(); -+ } -+ -+ private class ExpectFirstBlockMappingKey implements EmitterState { -+ public void expect() throws IOException { -+ new ExpectBlockMappingKey(true).expect(); -+ } -+ } -+ -+ private class ExpectBlockMappingKey implements EmitterState { -+ private final boolean first; -+ -+ public ExpectBlockMappingKey(boolean first) { -+ this.first = first; -+ } -+ -+ public void expect() throws IOException { -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ writeBlockComment(); -+ if (!this.first && event instanceof MappingEndEvent) { -+ indent = indents.pop(); -+ state = states.pop(); -+ } else { -+ writeIndent(); -+ if (checkSimpleKey()) { -+ states.push(new ExpectBlockMappingSimpleValue()); -+ expectNode(false, true, true); -+ } else { -+ writeIndicator("?", true, false, true); -+ states.push(new ExpectBlockMappingValue()); -+ expectNode(false, true, false); -+ } -+ } -+ } -+ } -+ -+ private boolean isFoldedOrLiteral(Event event) { -+ if(!event.is(ID.Scalar)) { -+ return false; -+ } -+ ScalarEvent scalarEvent = (ScalarEvent) event; -+ ScalarStyle style = scalarEvent.getScalarStyle(); -+ return style == ScalarStyle.FOLDED || style == ScalarStyle.LITERAL; -+ } -+ -+ private class ExpectBlockMappingSimpleValue implements EmitterState { -+ public void expect() throws IOException { -+ writeIndicator(":", false, false, false); -+ event = inlineCommentsCollector.collectEventsAndPoll(event); -+ if(!isFoldedOrLiteral(event)) { -+ if(writeInlineComments()) { -+ increaseIndent(true, false); -+ writeIndent(); -+ indent = indents.pop(); -+ } -+ } -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ if(!blockCommentsCollector.isEmpty()) { -+ increaseIndent(true, false); -+ writeBlockComment(); -+ writeIndent(); -+ indent = indents.pop(); -+ } -+ states.push(new ExpectBlockMappingKey(false)); -+ expectNode(false, true, false); -+ inlineCommentsCollector.collectEvents(); -+ writeInlineComments(); -+ } -+ } -+ -+ private class ExpectBlockMappingValue implements EmitterState { -+ public void expect() throws IOException { -+ writeIndent(); -+ writeIndicator(":", true, false, true); -+ event = inlineCommentsCollector.collectEventsAndPoll(event); -+ writeInlineComments(); -+ event = blockCommentsCollector.collectEventsAndPoll(event); -+ writeBlockComment(); -+ states.push(new ExpectBlockMappingKey(false)); -+ expectNode(false, true, false); -+ inlineCommentsCollector.collectEvents(event); -+ writeInlineComments(); -+ } -+ } -+ -+ // Checkers. -+ -+ private boolean checkEmptySequence() { -+ return event instanceof SequenceStartEvent && !events.isEmpty() && events.peek() instanceof SequenceEndEvent; -+ } -+ -+ private boolean checkEmptyMapping() { -+ return event instanceof MappingStartEvent && !events.isEmpty() && events.peek() instanceof MappingEndEvent; -+ } -+ -+ private boolean checkEmptyDocument() { -+ if (!(event instanceof DocumentStartEvent) || events.isEmpty()) { -+ return false; -+ } -+ Event event = events.peek(); -+ if (event instanceof ScalarEvent) { -+ ScalarEvent e = (ScalarEvent) event; -+ return e.getAnchor() == null && e.getTag() == null && e.getImplicit() != null && e -+ .getValue().length() == 0; -+ } -+ return false; -+ } -+ -+ private boolean checkSimpleKey() { -+ int length = 0; -+ if (event instanceof NodeEvent && ((NodeEvent) event).getAnchor() != null) { -+ if (preparedAnchor == null) { -+ preparedAnchor = prepareAnchor(((NodeEvent) event).getAnchor()); -+ } -+ length += preparedAnchor.length(); -+ } -+ String tag = null; -+ if (event instanceof ScalarEvent) { -+ tag = ((ScalarEvent) event).getTag(); -+ } else if (event instanceof CollectionStartEvent) { -+ tag = ((CollectionStartEvent) event).getTag(); -+ } -+ if (tag != null) { -+ if (preparedTag == null) { -+ preparedTag = prepareTag(tag); -+ } -+ length += preparedTag.length(); -+ } -+ if (event instanceof ScalarEvent) { -+ if (analysis == null) { -+ analysis = analyzeScalar(((ScalarEvent) event).getValue()); -+ } -+ length += analysis.getScalar().length(); -+ } -+ return length < maxSimpleKeyLength && (event instanceof AliasEvent -+ || (event instanceof ScalarEvent && !analysis.isEmpty() && !analysis.isMultiline()) -+ || checkEmptySequence() || checkEmptyMapping()); -+ } -+ -+ // Anchor, Tag, and Scalar processors. -+ -+ private void processAnchor(String indicator) throws IOException { -+ NodeEvent ev = (NodeEvent) event; -+ if (ev.getAnchor() == null) { -+ preparedAnchor = null; -+ return; -+ } -+ if (preparedAnchor == null) { -+ preparedAnchor = prepareAnchor(ev.getAnchor()); -+ } -+ writeIndicator(indicator + preparedAnchor, true, false, false); -+ preparedAnchor = null; -+ } -+ -+ private void processTag() throws IOException { -+ String tag = null; -+ if (event instanceof ScalarEvent) { -+ ScalarEvent ev = (ScalarEvent) event; -+ tag = ev.getTag(); -+ if (style == null) { -+ style = chooseScalarStyle(); -+ } -+ if ((!canonical || tag == null) && ((style == null && ev.getImplicit() -+ .canOmitTagInPlainScalar()) || (style != null && ev.getImplicit() -+ .canOmitTagInNonPlainScalar()))) { -+ preparedTag = null; -+ return; -+ } -+ if (ev.getImplicit().canOmitTagInPlainScalar() && tag == null) { -+ tag = "!"; -+ preparedTag = null; -+ } -+ } else { -+ CollectionStartEvent ev = (CollectionStartEvent) event; -+ tag = ev.getTag(); -+ if ((!canonical || tag == null) && ev.getImplicit()) { -+ preparedTag = null; -+ return; -+ } -+ } -+ if (tag == null) { -+ throw new EmitterException("tag is not specified"); -+ } -+ if (preparedTag == null) { -+ preparedTag = prepareTag(tag); -+ } -+ writeIndicator(preparedTag, true, false, false); -+ preparedTag = null; -+ } -+ -+ private DumperOptions.ScalarStyle chooseScalarStyle() { -+ ScalarEvent ev = (ScalarEvent) event; -+ if (analysis == null) { -+ analysis = analyzeScalar(ev.getValue()); -+ } -+ if (!ev.isPlain() && ev.getScalarStyle() == DumperOptions.ScalarStyle.DOUBLE_QUOTED || this.canonical) { -+ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; -+ } -+ if (ev.isPlain() && ev.getImplicit().canOmitTagInPlainScalar()) { -+ if (!(simpleKeyContext && (analysis.isEmpty() || analysis.isMultiline())) -+ && ((flowLevel != 0 && analysis.isAllowFlowPlain()) || (flowLevel == 0 && analysis.isAllowBlockPlain()))) { -+ return null; -+ } -+ } -+ if (!ev.isPlain() && (ev.getScalarStyle() == DumperOptions.ScalarStyle.LITERAL || ev.getScalarStyle() == DumperOptions.ScalarStyle.FOLDED)) { -+ if (flowLevel == 0 && !simpleKeyContext && analysis.isAllowBlock()) { -+ return ev.getScalarStyle(); -+ } -+ } -+ if (ev.isPlain() || ev.getScalarStyle() == DumperOptions.ScalarStyle.SINGLE_QUOTED) { -+ if (analysis.isAllowSingleQuoted() && !(simpleKeyContext && analysis.isMultiline())) { -+ return DumperOptions.ScalarStyle.SINGLE_QUOTED; -+ } -+ } -+ return DumperOptions.ScalarStyle.DOUBLE_QUOTED; -+ } -+ -+ private void processScalar() throws IOException { -+ ScalarEvent ev = (ScalarEvent) event; -+ if (analysis == null) { -+ analysis = analyzeScalar(ev.getValue()); -+ } -+ if (style == null) { -+ style = chooseScalarStyle(); -+ } -+ boolean split = !simpleKeyContext && splitLines; -+ if (style == null) { -+ writePlain(analysis.getScalar(), split); -+ } else { -+ switch (style) { -+ case DOUBLE_QUOTED: -+ writeDoubleQuoted(analysis.getScalar(), split); -+ break; -+ case SINGLE_QUOTED: -+ writeSingleQuoted(analysis.getScalar(), split); -+ break; -+ case FOLDED: -+ writeFolded(analysis.getScalar(), split); -+ break; -+ case LITERAL: -+ writeLiteral(analysis.getScalar()); -+ break; -+ default: -+ throw new YAMLException("Unexpected style: " + style); -+ } -+ } -+ analysis = null; -+ style = null; -+ } -+ -+ // Analyzers. -+ -+ private String prepareVersion(Version version) { -+ if (version.major() != 1) { -+ throw new EmitterException("unsupported YAML version: " + version); -+ } -+ return version.getRepresentation(); -+ } -+ -+ private final static Pattern HANDLE_FORMAT = Pattern.compile("^![-_\\w]*!$"); -+ -+ private String prepareTagHandle(String handle) { -+ if (handle.length() == 0) { -+ throw new EmitterException("tag handle must not be empty"); -+ } else if (handle.charAt(0) != '!' || handle.charAt(handle.length() - 1) != '!') { -+ throw new EmitterException("tag handle must start and end with '!': " + handle); -+ } else if (!"!".equals(handle) && !HANDLE_FORMAT.matcher(handle).matches()) { -+ throw new EmitterException("invalid character in the tag handle: " + handle); -+ } -+ return handle; -+ } -+ -+ private String prepareTagPrefix(String prefix) { -+ if (prefix.length() == 0) { -+ throw new EmitterException("tag prefix must not be empty"); -+ } -+ StringBuilder chunks = new StringBuilder(); -+ int start = 0; -+ int end = 0; -+ if (prefix.charAt(0) == '!') { -+ end = 1; -+ } -+ while (end < prefix.length()) { -+ end++; -+ } -+ if (start < end) { -+ chunks.append(prefix, start, end); -+ } -+ return chunks.toString(); -+ } -+ -+ private String prepareTag(String tag) { -+ if (tag.length() == 0) { -+ throw new EmitterException("tag must not be empty"); -+ } -+ if ("!".equals(tag)) { -+ return tag; -+ } -+ String handle = null; -+ String suffix = tag; -+ // shall the tag prefixes be sorted as in PyYAML? -+ for (String prefix : tagPrefixes.keySet()) { -+ if (tag.startsWith(prefix) && ("!".equals(prefix) || prefix.length() < tag.length())) { -+ handle = prefix; -+ } -+ } -+ if (handle != null) { -+ suffix = tag.substring(handle.length()); -+ handle = tagPrefixes.get(handle); -+ } -+ -+ int end = suffix.length(); -+ String suffixText = end > 0 ? suffix.substring(0, end) : ""; -+ -+ if (handle != null) { -+ return handle + suffixText; -+ } -+ return "!<" + suffixText + ">"; -+ } -+ -+ static String prepareAnchor(String anchor) { -+ if (anchor.length() == 0) { -+ throw new EmitterException("anchor must not be empty"); -+ } -+ for (Character invalid : INVALID_ANCHOR) { -+ if (anchor.indexOf(invalid) > -1) { -+ throw new EmitterException("Invalid character '" + invalid + "' in the anchor: " + anchor); -+ } -+ } -+ Matcher matcher = SPACES_PATTERN.matcher(anchor); -+ if (matcher.find()) { -+ throw new EmitterException("Anchor may not contain spaces: " + anchor); -+ } -+ return anchor; -+ } -+ -+ private ScalarAnalysis analyzeScalar(String scalar) { -+ // Empty scalar is a special case. -+ if (scalar.length() == 0) { -+ return new ScalarAnalysis(scalar, true, false, false, true, true, false); -+ } -+ // Indicators and special characters. -+ boolean blockIndicators = false; -+ boolean flowIndicators = false; -+ boolean lineBreaks = false; -+ boolean specialCharacters = false; -+ -+ // Important whitespace combinations. -+ boolean leadingSpace = false; -+ boolean leadingBreak = false; -+ boolean trailingSpace = false; -+ boolean trailingBreak = false; -+ boolean breakSpace = false; -+ boolean spaceBreak = false; -+ -+ // Check document indicators. -+ if (scalar.startsWith("---") || scalar.startsWith("...")) { -+ blockIndicators = true; -+ flowIndicators = true; -+ } -+ // First character or preceded by a whitespace. -+ boolean preceededByWhitespace = true; -+ boolean followedByWhitespace = scalar.length() == 1 || Constant.NULL_BL_T_LINEBR.has(scalar.codePointAt(1)); -+ // The previous character is a space. -+ boolean previousSpace = false; -+ -+ // The previous character is a break. -+ boolean previousBreak = false; -+ -+ int index = 0; -+ -+ while (index < scalar.length()) { -+ int c = scalar.codePointAt(index); -+ // Check for indicators. -+ if (index == 0) { -+ // Leading indicators are special characters. -+ if ("#,[]{}&*!|>'\"%@`".indexOf(c) != -1) { -+ flowIndicators = true; -+ blockIndicators = true; -+ } -+ if (c == '?' || c == ':') { -+ flowIndicators = true; -+ if (followedByWhitespace) { -+ blockIndicators = true; -+ } -+ } -+ if (c == '-' && followedByWhitespace) { -+ flowIndicators = true; -+ blockIndicators = true; -+ } -+ } else { -+ // Some indicators cannot appear within a scalar as well. -+ if (",?[]{}".indexOf(c) != -1) { -+ flowIndicators = true; -+ } -+ if (c == ':') { -+ flowIndicators = true; -+ if (followedByWhitespace) { -+ blockIndicators = true; -+ } -+ } -+ if (c == '#' && preceededByWhitespace) { -+ flowIndicators = true; -+ blockIndicators = true; -+ } -+ } -+ // Check for line breaks, special, and unicode characters. -+ boolean isLineBreak = Constant.LINEBR.has(c); -+ if (isLineBreak) { -+ lineBreaks = true; -+ } -+ if (!(c == '\n' || (0x20 <= c && c <= 0x7E))) { -+ if (c == 0x85 || (c >= 0xA0 && c <= 0xD7FF) -+ || (c >= 0xE000 && c <= 0xFFFD) -+ || (c >= 0x10000 && c <= 0x10FFFF)) { -+ // unicode is used -+ if (!this.allowUnicode) { -+ specialCharacters = true; -+ } -+ } else { -+ specialCharacters = true; -+ } -+ } -+ // Detect important whitespace combinations. -+ if (c == ' ') { -+ if (index == 0) { -+ leadingSpace = true; -+ } -+ if (index == scalar.length() - 1) { -+ trailingSpace = true; -+ } -+ if (previousBreak) { -+ breakSpace = true; -+ } -+ previousSpace = true; -+ previousBreak = false; -+ } else if (isLineBreak) { -+ if (index == 0) { -+ leadingBreak = true; -+ } -+ if (index == scalar.length() - 1) { -+ trailingBreak = true; -+ } -+ if (previousSpace) { -+ spaceBreak = true; -+ } -+ previousSpace = false; -+ previousBreak = true; -+ } else { -+ previousSpace = false; -+ previousBreak = false; -+ } -+ -+ // Prepare for the next character. -+ index += Character.charCount(c); -+ preceededByWhitespace = Constant.NULL_BL_T.has(c) || isLineBreak; -+ followedByWhitespace = true; -+ if (index + 1 < scalar.length()) { -+ int nextIndex = index + Character.charCount(scalar.codePointAt(index)); -+ if (nextIndex < scalar.length()) { -+ followedByWhitespace = (Constant.NULL_BL_T.has(scalar.codePointAt(nextIndex))) || isLineBreak; -+ } -+ } -+ } -+ // Let's decide what styles are allowed. -+ boolean allowFlowPlain = true; -+ boolean allowBlockPlain = true; -+ boolean allowSingleQuoted = true; -+ boolean allowBlock = true; -+ // Leading and trailing whitespaces are bad for plain scalars. -+ if (leadingSpace || leadingBreak || trailingSpace || trailingBreak) { -+ allowFlowPlain = allowBlockPlain = false; -+ } -+ // We do not permit trailing spaces for block scalars. -+ if (trailingSpace) { -+ allowBlock = false; -+ } -+ // Spaces at the beginning of a new line are only acceptable for block -+ // scalars. -+ if (breakSpace) { -+ allowFlowPlain = allowBlockPlain = allowSingleQuoted = false; -+ } -+ // Spaces followed by breaks, as well as special character are only -+ // allowed for double quoted scalars. -+ if (spaceBreak || specialCharacters) { -+ allowFlowPlain = allowBlockPlain = allowSingleQuoted = allowBlock = false; -+ } -+ // Although the plain scalar writer supports breaks, we never emit -+ // multiline plain scalars in the flow context. -+ if (lineBreaks) { -+ allowFlowPlain = false; -+ } -+ // Flow indicators are forbidden for flow plain scalars. -+ if (flowIndicators) { -+ allowFlowPlain = false; -+ } -+ // Block indicators are forbidden for block plain scalars. -+ if (blockIndicators) { -+ allowBlockPlain = false; -+ } -+ -+ return new ScalarAnalysis(scalar, false, lineBreaks, allowFlowPlain, allowBlockPlain, -+ allowSingleQuoted, allowBlock); -+ } -+ -+ // Writers. -+ -+ void flushStream() throws IOException { -+ stream.flush(); -+ } -+ -+ void writeStreamStart() { -+ // BOM is written by Writer. -+ } -+ -+ void writeStreamEnd() throws IOException { -+ flushStream(); -+ } -+ -+ void writeIndicator(String indicator, boolean needWhitespace, boolean whitespace, -+ boolean indentation) throws IOException { -+ if (!this.whitespace && needWhitespace) { -+ this.column++; -+ stream.write(SPACE); -+ } -+ this.whitespace = whitespace; -+ this.indention = this.indention && indentation; -+ this.column += indicator.length(); -+ openEnded = false; -+ stream.write(indicator); -+ } -+ -+ void writeIndent() throws IOException { -+ int indent; -+ if (this.indent != null) { -+ indent = this.indent; -+ } else { -+ indent = 0; -+ } -+ -+ if (!this.indention || this.column > indent || (this.column == indent && !this.whitespace)) { -+ writeLineBreak(null); -+ } -+ -+ writeWhitespace(indent - this.column); -+ } -+ -+ private void writeWhitespace(int length) throws IOException { -+ if (length <= 0) { -+ return; -+ } -+ this.whitespace = true; -+ char[] data = new char[length]; -+ for (int i = 0; i < data.length; i++) { -+ data[i] = ' '; -+ } -+ this.column += length; -+ stream.write(data); -+ } -+ -+ private void writeLineBreak(String data) throws IOException { -+ this.whitespace = true; -+ this.indention = true; -+ this.column = 0; -+ if (data == null) { -+ stream.write(this.bestLineBreak); -+ } else { -+ stream.write(data); -+ } -+ } -+ -+ void writeVersionDirective(String versionText) throws IOException { -+ stream.write("%YAML "); -+ stream.write(versionText); -+ writeLineBreak(null); -+ } -+ -+ void writeTagDirective(String handleText, String prefixText) throws IOException { -+ // XXX: not sure 4 invocations better then StringBuilders created by str -+ // + str -+ stream.write("%TAG "); -+ stream.write(handleText); -+ stream.write(SPACE); -+ stream.write(prefixText); -+ writeLineBreak(null); -+ } -+ -+ // Scalar streams. -+ private void writeSingleQuoted(String text, boolean split) throws IOException { -+ writeIndicator("'", true, false, false); -+ boolean spaces = false; -+ boolean breaks = false; -+ int start = 0, end = 0; -+ char ch; -+ while (end <= text.length()) { -+ ch = 0; -+ if (end < text.length()) { -+ ch = text.charAt(end); -+ } -+ if (spaces) { -+ if (ch == 0 || ch != ' ') { -+ if (start + 1 == end && this.column > this.bestWidth && split && start != 0 -+ && end != text.length()) { -+ writeIndent(); -+ } else { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ } -+ start = end; -+ } -+ } else if (breaks) { -+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { -+ if (text.charAt(start) == '\n') { -+ writeLineBreak(null); -+ } -+ String data = text.substring(start, end); -+ for (char br : data.toCharArray()) { -+ if (br == '\n') { -+ writeLineBreak(null); -+ } else { -+ writeLineBreak(String.valueOf(br)); -+ } -+ } -+ writeIndent(); -+ start = end; -+ } -+ } else { -+ if (Constant.LINEBR.has(ch, "\0 '")) { -+ if (start < end) { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ start = end; -+ } -+ } -+ } -+ if (ch == '\'') { -+ this.column += 2; -+ stream.write("''"); -+ start = end + 1; -+ } -+ if (ch != 0) { -+ spaces = ch == ' '; -+ breaks = Constant.LINEBR.has(ch); -+ } -+ end++; -+ } -+ writeIndicator("'", false, false, false); -+ } -+ -+ private void writeDoubleQuoted(String text, boolean split) throws IOException { -+ writeIndicator("\"", true, false, false); -+ int start = 0; -+ int end = 0; -+ while (end <= text.length()) { -+ Character ch = null; -+ if (end < text.length()) { -+ ch = text.charAt(end); -+ } -+ if (ch == null || "\"\\\u0085\u2028\u2029\uFEFF".indexOf(ch) != -1 -+ || !('\u0020' <= ch && ch <= '\u007E')) { -+ if (start < end) { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ start = end; -+ } -+ if (ch != null) { -+ String data; -+ if (ESCAPE_REPLACEMENTS.containsKey(ch)) { -+ data = "\\" + ESCAPE_REPLACEMENTS.get(ch); -+ } else if (!this.allowUnicode || !StreamReader.isPrintable(ch)) { -+ // if !allowUnicode or the character is not printable, -+ // we must encode it -+ if (ch <= '\u00FF') { -+ String s = "0" + Integer.toString(ch, 16); -+ data = "\\x" + s.substring(s.length() - 2); -+ } else if (ch >= '\uD800' && ch <= '\uDBFF') { -+ if (end + 1 < text.length()) { -+ Character ch2 = text.charAt(++end); -+ String s = "000" + Long.toHexString(Character.toCodePoint(ch, ch2)); -+ data = "\\U" + s.substring(s.length() - 8); -+ } else { -+ String s = "000" + Integer.toString(ch, 16); -+ data = "\\u" + s.substring(s.length() - 4); -+ } -+ } else { -+ String s = "000" + Integer.toString(ch, 16); -+ data = "\\u" + s.substring(s.length() - 4); -+ } -+ } else { -+ data = String.valueOf(ch); -+ } -+ this.column += data.length(); -+ stream.write(data); -+ start = end + 1; -+ } -+ } -+ if ((0 < end && end < (text.length() - 1)) && (ch == ' ' || start >= end) -+ && (this.column + (end - start)) > this.bestWidth && split) { -+ String data; -+ if (start >= end) { -+ data = "\\"; -+ } else { -+ data = text.substring(start, end) + "\\"; -+ } -+ if (start < end) { -+ start = end; -+ } -+ this.column += data.length(); -+ stream.write(data); -+ writeIndent(); -+ this.whitespace = false; -+ this.indention = false; -+ if (text.charAt(start) == ' ') { -+ data = "\\"; -+ this.column += data.length(); -+ stream.write(data); -+ } -+ } -+ end += 1; -+ } -+ writeIndicator("\"", false, false, false); -+ } -+ -+ private boolean writeCommentLines(List commentLines) throws IOException { -+ boolean wroteComment = false; -+ if(emitComments) { -+ int indentColumns = 0; -+ boolean firstComment = true; -+ for (CommentLine commentLine : commentLines) { -+ if (commentLine.getCommentType() != CommentType.BLANK_LINE) { -+ if (firstComment) { -+ firstComment = false; -+ writeIndicator("#", commentLine.getCommentType() == CommentType.IN_LINE, false, false); -+ indentColumns = this.column > 0 ? this.column - 1 : 0; -+ } else { -+ writeWhitespace(indentColumns); -+ writeIndicator("#", false, false, false); -+ } -+ stream.write(commentLine.getValue()); -+ writeLineBreak(null); -+ } else { -+ writeLineBreak(null); -+ writeIndent(); -+ } -+ wroteComment = true; -+ } -+ } -+ return wroteComment; -+ } -+ -+ private void writeBlockComment() throws IOException { -+ if(!blockCommentsCollector.isEmpty()) { -+ writeIndent(); -+ writeCommentLines(blockCommentsCollector.consume()); -+ } -+ } -+ -+ private boolean writeInlineComments() throws IOException { -+ return writeCommentLines(inlineCommentsCollector.consume()); -+ } -+ -+ private String determineBlockHints(String text) { -+ StringBuilder hints = new StringBuilder(); -+ if (Constant.LINEBR.has(text.charAt(0), " ")) { -+ hints.append(bestIndent); -+ } -+ char ch1 = text.charAt(text.length() - 1); -+ if (Constant.LINEBR.hasNo(ch1)) { -+ hints.append("-"); -+ } else if (text.length() == 1 || Constant.LINEBR.has(text.charAt(text.length() - 2))) { -+ hints.append("+"); -+ } -+ return hints.toString(); -+ } -+ -+ void writeFolded(String text, boolean split) throws IOException { -+ String hints = determineBlockHints(text); -+ writeIndicator(">" + hints, true, false, false); -+ if (hints.length() > 0 && (hints.charAt(hints.length() - 1) == '+')) { -+ openEnded = true; -+ } -+ if(!writeInlineComments()) { -+ writeLineBreak(null); -+ } -+ boolean leadingSpace = true; -+ boolean spaces = false; -+ boolean breaks = true; -+ int start = 0, end = 0; -+ while (end <= text.length()) { -+ char ch = 0; -+ if (end < text.length()) { -+ ch = text.charAt(end); -+ } -+ if (breaks) { -+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { -+ if (!leadingSpace && ch != 0 && ch != ' ' && text.charAt(start) == '\n') { -+ writeLineBreak(null); -+ } -+ leadingSpace = ch == ' '; -+ String data = text.substring(start, end); -+ for (char br : data.toCharArray()) { -+ if (br == '\n') { -+ writeLineBreak(null); -+ } else { -+ writeLineBreak(String.valueOf(br)); -+ } -+ } -+ if (ch != 0) { -+ writeIndent(); -+ } -+ start = end; -+ } -+ } else if (spaces) { -+ if (ch != ' ') { -+ if (start + 1 == end && this.column > this.bestWidth && split) { -+ writeIndent(); -+ } else { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ } -+ start = end; -+ } -+ } else { -+ if (Constant.LINEBR.has(ch, "\0 ")) { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ if (ch == 0) { -+ writeLineBreak(null); -+ } -+ start = end; -+ } -+ } -+ if (ch != 0) { -+ breaks = Constant.LINEBR.has(ch); -+ spaces = ch == ' '; -+ } -+ end++; -+ } -+ } -+ -+ void writeLiteral(String text) throws IOException { -+ String hints = determineBlockHints(text); -+ writeIndicator("|" + hints, true, false, false); -+ if (hints.length() > 0 && (hints.charAt(hints.length() - 1)) == '+') { -+ openEnded = true; -+ } -+ if(!writeInlineComments()) { -+ writeLineBreak(null); -+ } -+ boolean breaks = true; -+ int start = 0, end = 0; -+ while (end <= text.length()) { -+ char ch = 0; -+ if (end < text.length()) { -+ ch = text.charAt(end); -+ } -+ if (breaks) { -+ if (ch == 0 || Constant.LINEBR.hasNo(ch)) { -+ String data = text.substring(start, end); -+ for (char br : data.toCharArray()) { -+ if (br == '\n') { -+ writeLineBreak(null); -+ } else { -+ writeLineBreak(String.valueOf(br)); -+ } -+ } -+ if (ch != 0) { -+ writeIndent(); -+ } -+ start = end; -+ } -+ } else { -+ if (ch == 0 || Constant.LINEBR.has(ch)) { -+ stream.write(text, start, end - start); -+ if (ch == 0) { -+ writeLineBreak(null); -+ } -+ start = end; -+ } -+ } -+ if (ch != 0) { -+ breaks = Constant.LINEBR.has(ch); -+ } -+ end++; -+ } -+ } -+ -+ void writePlain(String text, boolean split) throws IOException { -+ if (rootContext) { -+ openEnded = true; -+ } -+ if (text.length() == 0) { -+ return; -+ } -+ if (!this.whitespace) { -+ this.column++; -+ stream.write(SPACE); -+ } -+ this.whitespace = false; -+ this.indention = false; -+ boolean spaces = false; -+ boolean breaks = false; -+ int start = 0, end = 0; -+ while (end <= text.length()) { -+ char ch = 0; -+ if (end < text.length()) { -+ ch = text.charAt(end); -+ } -+ if (spaces) { -+ if (ch != ' ') { -+ if (start + 1 == end && this.column > this.bestWidth && split) { -+ writeIndent(); -+ this.whitespace = false; -+ this.indention = false; -+ } else { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ } -+ start = end; -+ } -+ } else if (breaks) { -+ if (Constant.LINEBR.hasNo(ch)) { -+ if (text.charAt(start) == '\n') { -+ writeLineBreak(null); -+ } -+ String data = text.substring(start, end); -+ for (char br : data.toCharArray()) { -+ if (br == '\n') { -+ writeLineBreak(null); -+ } else { -+ writeLineBreak(String.valueOf(br)); -+ } -+ } -+ writeIndent(); -+ this.whitespace = false; -+ this.indention = false; -+ start = end; -+ } -+ } else { -+ if (Constant.LINEBR.has(ch, "\0 ")) { -+ int len = end - start; -+ this.column += len; -+ stream.write(text, start, len); -+ start = end; -+ } -+ } -+ if (ch != 0) { -+ spaces = ch == ' '; -+ breaks = Constant.LINEBR.has(ch); -+ } -+ end++; -+ } -+ } -+} diff --git a/patches/server/0822-Make-water-animal-spawn-height-configurable.patch b/patches/server/0822-Make-water-animal-spawn-height-configurable.patch new file mode 100644 index 0000000000..dea45f0b70 --- /dev/null +++ b/patches/server/0822-Make-water-animal-spawn-height-configurable.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brokkonaut +Date: Sat, 18 Dec 2021 08:26:55 +0100 +Subject: [PATCH] Make water animal spawn height configurable + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +index 522abd880c1e7f7c9026f0ab6457bc649f11802c..18389f46902bb9879ac6d734723e9a720724dc48 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +@@ -79,6 +79,10 @@ public abstract class WaterAnimal extends PathfinderMob { + public static boolean checkSurfaceWaterAnimalSpawnRules(EntityType type, LevelAccessor world, MobSpawnType reason, BlockPos pos, RandomSource random) { + int i = world.getSeaLevel(); + int j = i - 13; ++ // Paper start ++ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); ++ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); ++ // Paper end + return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); + } + } diff --git a/patches/server/0823-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0823-Expose-vanilla-BiomeProvider-from-WorldInfo.patch new file mode 100644 index 0000000000..7a4e27a8cd --- /dev/null +++ b/patches/server/0823-Expose-vanilla-BiomeProvider-from-WorldInfo.patch @@ -0,0 +1,139 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Thu, 6 Jan 2022 15:59:06 -0800 +Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo + + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 857af8ed37c10723d6cd81d7f3c2ba6169021050..28e2f22cc2e5b6dd47705cdd5095ff2394979c8f 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -562,7 +562,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop iregistry = worlddata.worldGenSettings().dimensions(); + LevelStem worlddimension = (LevelStem) iregistry.get(actualDimension); + +- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.typeHolder().value()); ++ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.typeHolder().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper + if (biomeProvider == null && generator != null) { + biomeProvider = generator.getDefaultBiomeProvider(worldInfo); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5138182d8004aec69848d10b8cc63453c1e8808f..7916426a9d7953c2cc15a319adea8d982b63b773 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -200,6 +200,30 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public int getPlayerCount() { + return world.players().size(); + } ++ ++ @Override ++ public BiomeProvider vanillaBiomeProvider() { ++ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; ++ ++ final net.minecraft.world.level.biome.BiomeSource biomeSource = serverCache.getGenerator().getBiomeSource(); ++ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); ++ final net.minecraft.core.Registry biomeRegistry = this.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); ++ ++ final List possibleBiomes = biomeSource.possibleBiomes().stream() ++ .map(biome -> CraftBlock.biomeBaseToBiome(biomeRegistry, biome)) ++ .toList(); ++ return new BiomeProvider() { ++ @Override ++ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { ++ return CraftBlock.biomeBaseToBiome(biomeRegistry, biomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); ++ } ++ ++ @Override ++ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } + // Paper end + + private static final Random rand = new Random(); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +index 3795dc9b12d62113146e803554283acd8d0e5db9..b9af0c68bfa877314de0d45741a54795b581d9b8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java +@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { + private final long seed; + private final int minHeight; + private final int maxHeight; ++ // Paper start ++ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; ++ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; + +- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { ++ public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { ++ this.registryAccess = registryAccess; ++ this.vanillaChunkGenerator = chunkGenerator; ++ // Paper end + this.name = worldDataServer.getLevelName(); + this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); + this.environment = environment; +@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { + this.maxHeight = dimensionManager.minY() + dimensionManager.height(); + } + +- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { +- this.name = name; +- this.uuid = uuid; +- this.environment = environment; +- this.seed = seed; +- this.minHeight = minHeight; +- this.maxHeight = maxHeight; +- } +- + @Override + public String getName() { + return this.name; +@@ -65,4 +62,35 @@ public class CraftWorldInfo implements WorldInfo { + public int getMaxHeight() { + return this.maxHeight; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { ++ final net.minecraft.world.level.levelgen.RandomState randomState; ++ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), ++ registryAccess.registryOrThrow(net.minecraft.core.Registry.NOISE_REGISTRY), getSeed()); ++ } else { ++ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), ++ registryAccess.registryOrThrow(net.minecraft.core.Registry.NOISE_REGISTRY), getSeed()); ++ } ++ ++ final net.minecraft.core.Registry biomeRegistry = CraftWorldInfo.this.registryAccess.registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); ++ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() ++ .map(biome -> org.bukkit.craftbukkit.block.CraftBlock.biomeBaseToBiome(biomeRegistry, biome)) ++ .toList(); ++ return new org.bukkit.generator.BiomeProvider() { ++ @Override ++ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { ++ return org.bukkit.craftbukkit.block.CraftBlock.biomeBaseToBiome(biomeRegistry, ++ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); ++ } ++ ++ @Override ++ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { ++ return possibleBiomes; ++ } ++ }; ++ } ++ // Paper end + } diff --git a/patches/server/0823-Make-water-animal-spawn-height-configurable.patch b/patches/server/0823-Make-water-animal-spawn-height-configurable.patch deleted file mode 100644 index dea45f0b70..0000000000 --- a/patches/server/0823-Make-water-animal-spawn-height-configurable.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Sat, 18 Dec 2021 08:26:55 +0100 -Subject: [PATCH] Make water animal spawn height configurable - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -index 522abd880c1e7f7c9026f0ab6457bc649f11802c..18389f46902bb9879ac6d734723e9a720724dc48 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -@@ -79,6 +79,10 @@ public abstract class WaterAnimal extends PathfinderMob { - public static boolean checkSurfaceWaterAnimalSpawnRules(EntityType type, LevelAccessor world, MobSpawnType reason, BlockPos pos, RandomSource random) { - int i = world.getSeaLevel(); - int j = i - 13; -+ // Paper start -+ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); -+ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); -+ // Paper end - return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); - } - } diff --git a/patches/server/0824-Add-config-option-for-worlds-affected-by-time-cmd.patch b/patches/server/0824-Add-config-option-for-worlds-affected-by-time-cmd.patch new file mode 100644 index 0000000000..de155852d0 --- /dev/null +++ b/patches/server/0824-Add-config-option-for-worlds-affected-by-time-cmd.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 2 Jan 2022 22:34:51 -0800 +Subject: [PATCH] Add config option for worlds affected by time cmd + + +diff --git a/src/main/java/net/minecraft/server/commands/TimeCommand.java b/src/main/java/net/minecraft/server/commands/TimeCommand.java +index e1908c2fcad3d1505bdcd65ba7ceb3dfa42c5c39..f0a7a8df3caa2ea765bb0a87cfede71d0995d276 100644 +--- a/src/main/java/net/minecraft/server/commands/TimeCommand.java ++++ b/src/main/java/net/minecraft/server/commands/TimeCommand.java +@@ -51,7 +51,7 @@ public class TimeCommand { + } + + public static int setTime(CommandSourceStack source, int time) { +- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in ++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change + + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); +@@ -70,7 +70,7 @@ public class TimeCommand { + } + + public static int addTime(CommandSourceStack source, int time) { +- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in ++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change + + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); diff --git a/patches/server/0824-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0824-Expose-vanilla-BiomeProvider-from-WorldInfo.patch deleted file mode 100644 index 7a4e27a8cd..0000000000 --- a/patches/server/0824-Expose-vanilla-BiomeProvider-from-WorldInfo.patch +++ /dev/null @@ -1,139 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Thu, 6 Jan 2022 15:59:06 -0800 -Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 857af8ed37c10723d6cd81d7f3c2ba6169021050..28e2f22cc2e5b6dd47705cdd5095ff2394979c8f 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -562,7 +562,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop iregistry = worlddata.worldGenSettings().dimensions(); - LevelStem worlddimension = (LevelStem) iregistry.get(actualDimension); - -- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.typeHolder().value()); -+ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.typeHolder().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - if (biomeProvider == null && generator != null) { - biomeProvider = generator.getDefaultBiomeProvider(worldInfo); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 5138182d8004aec69848d10b8cc63453c1e8808f..7916426a9d7953c2cc15a319adea8d982b63b773 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -200,6 +200,30 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public int getPlayerCount() { - return world.players().size(); - } -+ -+ @Override -+ public BiomeProvider vanillaBiomeProvider() { -+ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource; -+ -+ final net.minecraft.world.level.biome.BiomeSource biomeSource = serverCache.getGenerator().getBiomeSource(); -+ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler(); -+ final net.minecraft.core.Registry biomeRegistry = this.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); -+ -+ final List possibleBiomes = biomeSource.possibleBiomes().stream() -+ .map(biome -> CraftBlock.biomeBaseToBiome(biomeRegistry, biome)) -+ .toList(); -+ return new BiomeProvider() { -+ @Override -+ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) { -+ return CraftBlock.biomeBaseToBiome(biomeRegistry, biomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler)); -+ } -+ -+ @Override -+ public List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } - // Paper end - - private static final Random rand = new Random(); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -index 3795dc9b12d62113146e803554283acd8d0e5db9..b9af0c68bfa877314de0d45741a54795b581d9b8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java -@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo { - private final long seed; - private final int minHeight; - private final int maxHeight; -+ // Paper start -+ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator; -+ private final net.minecraft.core.RegistryAccess.Frozen registryAccess; - -- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) { -+ public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) { -+ this.registryAccess = registryAccess; -+ this.vanillaChunkGenerator = chunkGenerator; -+ // Paper end - this.name = worldDataServer.getLevelName(); - this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile()); - this.environment = environment; -@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo { - this.maxHeight = dimensionManager.minY() + dimensionManager.height(); - } - -- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) { -- this.name = name; -- this.uuid = uuid; -- this.environment = environment; -- this.seed = seed; -- this.minHeight = minHeight; -- this.maxHeight = maxHeight; -- } -- - @Override - public String getName() { - return this.name; -@@ -65,4 +62,35 @@ public class CraftWorldInfo implements WorldInfo { - public int getMaxHeight() { - return this.maxHeight; - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() { -+ final net.minecraft.world.level.levelgen.RandomState randomState; -+ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), -+ registryAccess.registryOrThrow(net.minecraft.core.Registry.NOISE_REGISTRY), getSeed()); -+ } else { -+ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(), -+ registryAccess.registryOrThrow(net.minecraft.core.Registry.NOISE_REGISTRY), getSeed()); -+ } -+ -+ final net.minecraft.core.Registry biomeRegistry = CraftWorldInfo.this.registryAccess.registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY); -+ final java.util.List possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream() -+ .map(biome -> org.bukkit.craftbukkit.block.CraftBlock.biomeBaseToBiome(biomeRegistry, biome)) -+ .toList(); -+ return new org.bukkit.generator.BiomeProvider() { -+ @Override -+ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { -+ return org.bukkit.craftbukkit.block.CraftBlock.biomeBaseToBiome(biomeRegistry, -+ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler())); -+ } -+ -+ @Override -+ public java.util.List getBiomes(final org.bukkit.generator.WorldInfo worldInfo) { -+ return possibleBiomes; -+ } -+ }; -+ } -+ // Paper end - } diff --git a/patches/server/0825-Add-config-option-for-worlds-affected-by-time-cmd.patch b/patches/server/0825-Add-config-option-for-worlds-affected-by-time-cmd.patch deleted file mode 100644 index de155852d0..0000000000 --- a/patches/server/0825-Add-config-option-for-worlds-affected-by-time-cmd.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 2 Jan 2022 22:34:51 -0800 -Subject: [PATCH] Add config option for worlds affected by time cmd - - -diff --git a/src/main/java/net/minecraft/server/commands/TimeCommand.java b/src/main/java/net/minecraft/server/commands/TimeCommand.java -index e1908c2fcad3d1505bdcd65ba7ceb3dfa42c5c39..f0a7a8df3caa2ea765bb0a87cfede71d0995d276 100644 ---- a/src/main/java/net/minecraft/server/commands/TimeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/TimeCommand.java -@@ -51,7 +51,7 @@ public class TimeCommand { - } - - public static int setTime(CommandSourceStack source, int time) { -- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in -+ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change - - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); -@@ -70,7 +70,7 @@ public class TimeCommand { - } - - public static int addTime(CommandSourceStack source, int time) { -- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in -+ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change - - while (iterator.hasNext()) { - ServerLevel worldserver = (ServerLevel) iterator.next(); diff --git a/patches/server/0825-Add-new-overload-to-PersistentDataContainer-has.patch b/patches/server/0825-Add-new-overload-to-PersistentDataContainer-has.patch new file mode 100644 index 0000000000..d19f7179e1 --- /dev/null +++ b/patches/server/0825-Add-new-overload-to-PersistentDataContainer-has.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Mon, 3 Jan 2022 23:32:42 -0500 +Subject: [PATCH] Add new overload to PersistentDataContainer#has + +Adds the new overload: PersistentDataContainer#has(NamespacedKey key) + +diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +index 2c59f09a9261a1690951161fd856a5848d9885b7..f0588bec9be09cb8273c310fb3de8bfe72dee9e5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java +@@ -161,5 +161,12 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { + public void clear() { + this.customDataTags.clear(); + } ++ ++ @Override ++ public boolean has(NamespacedKey key) { ++ Validate.notNull(key, "The provided key for the custom value was null"); ++ ++ return this.customDataTags.containsKey(key.toString()); ++ } + // Paper end + } diff --git a/patches/server/0826-Add-new-overload-to-PersistentDataContainer-has.patch b/patches/server/0826-Add-new-overload-to-PersistentDataContainer-has.patch deleted file mode 100644 index d19f7179e1..0000000000 --- a/patches/server/0826-Add-new-overload-to-PersistentDataContainer-has.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Mon, 3 Jan 2022 23:32:42 -0500 -Subject: [PATCH] Add new overload to PersistentDataContainer#has - -Adds the new overload: PersistentDataContainer#has(NamespacedKey key) - -diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -index 2c59f09a9261a1690951161fd856a5848d9885b7..f0588bec9be09cb8273c310fb3de8bfe72dee9e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java -@@ -161,5 +161,12 @@ public class CraftPersistentDataContainer implements PersistentDataContainer { - public void clear() { - this.customDataTags.clear(); - } -+ -+ @Override -+ public boolean has(NamespacedKey key) { -+ Validate.notNull(key, "The provided key for the custom value was null"); -+ -+ return this.customDataTags.containsKey(key.toString()); -+ } - // Paper end - } diff --git a/patches/server/0826-Multiple-Entries-with-Scoreboards.patch b/patches/server/0826-Multiple-Entries-with-Scoreboards.patch new file mode 100644 index 0000000000..b644176ac9 --- /dev/null +++ b/patches/server/0826-Multiple-Entries-with-Scoreboards.patch @@ -0,0 +1,126 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Tue, 21 Sep 2021 18:17:33 -0500 +Subject: [PATCH] Multiple Entries with Scoreboards + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +index ee37ec0de1ca969144824427ae42b0c81434a1b4..4ebe22ac20f1a98694cc3bec570ef5bbf06f00aa 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java +@@ -42,6 +42,12 @@ public class ClientboundSetPlayerTeamPacket implements Packet players, ClientboundSetPlayerTeamPacket.Action operation) { ++ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players); ++ } ++ // Paper end ++ + public ClientboundSetPlayerTeamPacket(FriendlyByteBuf buf) { + this.name = buf.readUtf(); + this.method = buf.readByte(); +diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java +index 610d312b9c8f6c8d1f102e8ba2fe9fc2cc3e98c5..3a4a0727ad44322e3ba85512cd077808dab080b7 100644 +--- a/src/main/java/net/minecraft/server/ServerScoreboard.java ++++ b/src/main/java/net/minecraft/server/ServerScoreboard.java +@@ -92,6 +92,25 @@ public class ServerScoreboard extends Scoreboard { + } + } + ++ // Paper start ++ public boolean addPlayersToTeam(java.util.Collection players, PlayerTeam team) { ++ boolean anyAdded = false; ++ for (String playerName : players) { ++ if (super.addPlayerToTeam(playerName, team)) { ++ anyAdded = true; ++ } ++ } ++ ++ if (anyAdded) { ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD)); ++ this.setDirty(); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Paper end ++ + @Override + public void removePlayerFromTeam(String playerName, PlayerTeam team) { + super.removePlayerFromTeam(playerName, team); +@@ -99,6 +118,17 @@ public class ServerScoreboard extends Scoreboard { + this.setDirty(); + } + ++ // Paper start ++ public void removePlayersFromTeam(java.util.Collection players, PlayerTeam team) { ++ for (String playerName : players) { ++ super.removePlayerFromTeam(playerName, team); ++ } ++ ++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE)); ++ this.setDirty(); ++ } ++ // Paper end ++ + @Override + public void onObjectiveAdded(Objective objective) { + super.onObjectiveAdded(objective); +diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +index 4b64d6c5c987e127d1ed5edad0a78f7172370b9f..67efb0d38ae369ff5254f7b1ec85d32d4eee8291 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java ++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java +@@ -234,6 +234,21 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + scoreboard.board.addPlayerToTeam(entry, team); + } + ++ // Paper start ++ @Override ++ public void addEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { ++ this.addEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); ++ } ++ ++ @Override ++ public void addEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { ++ Validate.notNull(entries, "Entries cannot be null"); ++ CraftScoreboard scoreboard = this.checkState(); ++ ++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).addPlayersToTeam(entries, this.team); ++ } ++ // Paper end ++ + @Override + public boolean removePlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException { + Validate.notNull(player, "OfflinePlayer cannot be null"); +@@ -253,6 +268,28 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { + return true; + } + ++ // Paper start ++ @Override ++ public boolean removeEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { ++ return this.removeEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); ++ } ++ ++ @Override ++ public boolean removeEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { ++ Validate.notNull(entries, "Entry cannot be null"); ++ CraftScoreboard scoreboard = this.checkState(); ++ ++ for (String entry : entries) { ++ if (this.team.getPlayers().contains(entry)) { ++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).removePlayersFromTeam(entries, this.team); ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Paper end ++ + @Override + public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { + Validate.notNull(player, "OfflinePlayer cannot be null"); diff --git a/patches/server/0827-Multiple-Entries-with-Scoreboards.patch b/patches/server/0827-Multiple-Entries-with-Scoreboards.patch deleted file mode 100644 index b644176ac9..0000000000 --- a/patches/server/0827-Multiple-Entries-with-Scoreboards.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Cryptite -Date: Tue, 21 Sep 2021 18:17:33 -0500 -Subject: [PATCH] Multiple Entries with Scoreboards - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -index ee37ec0de1ca969144824427ae42b0c81434a1b4..4ebe22ac20f1a98694cc3bec570ef5bbf06f00aa 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java -@@ -42,6 +42,12 @@ public class ClientboundSetPlayerTeamPacket implements Packet players, ClientboundSetPlayerTeamPacket.Action operation) { -+ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players); -+ } -+ // Paper end -+ - public ClientboundSetPlayerTeamPacket(FriendlyByteBuf buf) { - this.name = buf.readUtf(); - this.method = buf.readByte(); -diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java -index 610d312b9c8f6c8d1f102e8ba2fe9fc2cc3e98c5..3a4a0727ad44322e3ba85512cd077808dab080b7 100644 ---- a/src/main/java/net/minecraft/server/ServerScoreboard.java -+++ b/src/main/java/net/minecraft/server/ServerScoreboard.java -@@ -92,6 +92,25 @@ public class ServerScoreboard extends Scoreboard { - } - } - -+ // Paper start -+ public boolean addPlayersToTeam(java.util.Collection players, PlayerTeam team) { -+ boolean anyAdded = false; -+ for (String playerName : players) { -+ if (super.addPlayerToTeam(playerName, team)) { -+ anyAdded = true; -+ } -+ } -+ -+ if (anyAdded) { -+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD)); -+ this.setDirty(); -+ return true; -+ } else { -+ return false; -+ } -+ } -+ // Paper end -+ - @Override - public void removePlayerFromTeam(String playerName, PlayerTeam team) { - super.removePlayerFromTeam(playerName, team); -@@ -99,6 +118,17 @@ public class ServerScoreboard extends Scoreboard { - this.setDirty(); - } - -+ // Paper start -+ public void removePlayersFromTeam(java.util.Collection players, PlayerTeam team) { -+ for (String playerName : players) { -+ super.removePlayerFromTeam(playerName, team); -+ } -+ -+ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE)); -+ this.setDirty(); -+ } -+ // Paper end -+ - @Override - public void onObjectiveAdded(Objective objective) { - super.onObjectiveAdded(objective); -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -index 4b64d6c5c987e127d1ed5edad0a78f7172370b9f..67efb0d38ae369ff5254f7b1ec85d32d4eee8291 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java -@@ -234,6 +234,21 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { - scoreboard.board.addPlayerToTeam(entry, team); - } - -+ // Paper start -+ @Override -+ public void addEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { -+ this.addEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); -+ } -+ -+ @Override -+ public void addEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { -+ Validate.notNull(entries, "Entries cannot be null"); -+ CraftScoreboard scoreboard = this.checkState(); -+ -+ ((net.minecraft.server.ServerScoreboard) scoreboard.board).addPlayersToTeam(entries, this.team); -+ } -+ // Paper end -+ - @Override - public boolean removePlayer(OfflinePlayer player) throws IllegalStateException, IllegalArgumentException { - Validate.notNull(player, "OfflinePlayer cannot be null"); -@@ -253,6 +268,28 @@ final class CraftTeam extends CraftScoreboardComponent implements Team { - return true; - } - -+ // Paper start -+ @Override -+ public boolean removeEntities(java.util.Collection entities) throws IllegalStateException, IllegalArgumentException { -+ return this.removeEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList()); -+ } -+ -+ @Override -+ public boolean removeEntries(java.util.Collection entries) throws IllegalStateException, IllegalArgumentException { -+ Validate.notNull(entries, "Entry cannot be null"); -+ CraftScoreboard scoreboard = this.checkState(); -+ -+ for (String entry : entries) { -+ if (this.team.getPlayers().contains(entry)) { -+ ((net.minecraft.server.ServerScoreboard) scoreboard.board).removePlayersFromTeam(entries, this.team); -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ // Paper end -+ - @Override - public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException { - Validate.notNull(player, "OfflinePlayer cannot be null"); diff --git a/patches/server/0827-Reset-placed-block-on-exception.patch b/patches/server/0827-Reset-placed-block-on-exception.patch new file mode 100644 index 0000000000..3881c0d6de --- /dev/null +++ b/patches/server/0827-Reset-placed-block-on-exception.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 7 Jan 2022 11:45:15 +0100 +Subject: [PATCH] Reset placed block on exception + + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 78cac63e5bd7c84f59b8e00ee40899be78e8cdb1..00d31df5ba7e147b4b969a89cfc2b5088a988169 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -78,6 +78,7 @@ public class BlockItem extends Item { + if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { + blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); + } ++ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper + // CraftBukkit end + + if (iblockdata == null) { +@@ -93,7 +94,19 @@ public class BlockItem extends Item { + + if (iblockdata1.is(iblockdata.getBlock())) { + iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1); ++ // Paper start - reset block on exception ++ try { + this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1); ++ } catch (Exception e) { ++ oldBlockstate.update(true, false); ++ if (entityhuman instanceof ServerPlayer player) { ++ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e); ++ player.getBukkitEntity().kickPlayer("Packet processing error"); ++ return InteractionResult.FAIL; ++ } ++ throw e; // Rethrow exception if not placed by a player ++ } ++ // Paper end + iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack); + // CraftBukkit start + if (blockstate != null) { diff --git a/patches/server/0828-Add-configurable-height-for-slime-spawn.patch b/patches/server/0828-Add-configurable-height-for-slime-spawn.patch new file mode 100644 index 0000000000..a05bae35ee --- /dev/null +++ b/patches/server/0828-Add-configurable-height-for-slime-spawn.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Mon, 2 Aug 2021 11:24:39 -0400 +Subject: [PATCH] Add configurable height for slime spawn + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index 15d5a8290be35c2caebf8e296300e8f32cb597c7..fa79316adb11ab39cf921475e12a50058fd82a87 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -320,7 +320,11 @@ public class Slime extends Mob implements Enemy { + + public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { + if (world.getDifficulty() != Difficulty.PEACEFUL) { +- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper start - Replace rules for Height in Swamp Biome ++ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; ++ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; ++ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { ++ // Paper end + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + +@@ -331,7 +335,10 @@ public class Slime extends Mob implements Enemy { + ChunkPos chunkcoordintpair = new ChunkPos(pos); + boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper + +- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { ++ // Paper start - Replace rules for Height in Slime Chunks ++ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; ++ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { ++ // Paper end + return checkMobSpawnRules(type, world, spawnReason, pos, random); + } + } diff --git a/patches/server/0828-Reset-placed-block-on-exception.patch b/patches/server/0828-Reset-placed-block-on-exception.patch deleted file mode 100644 index 3881c0d6de..0000000000 --- a/patches/server/0828-Reset-placed-block-on-exception.patch +++ /dev/null @@ -1,38 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 7 Jan 2022 11:45:15 +0100 -Subject: [PATCH] Reset placed block on exception - - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 78cac63e5bd7c84f59b8e00ee40899be78e8cdb1..00d31df5ba7e147b4b969a89cfc2b5088a988169 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -78,6 +78,7 @@ public class BlockItem extends Item { - if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { - blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); - } -+ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - // CraftBukkit end - - if (iblockdata == null) { -@@ -93,7 +94,19 @@ public class BlockItem extends Item { - - if (iblockdata1.is(iblockdata.getBlock())) { - iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1); -+ // Paper start - reset block on exception -+ try { - this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1); -+ } catch (Exception e) { -+ oldBlockstate.update(true, false); -+ if (entityhuman instanceof ServerPlayer player) { -+ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e); -+ player.getBukkitEntity().kickPlayer("Packet processing error"); -+ return InteractionResult.FAIL; -+ } -+ throw e; // Rethrow exception if not placed by a player -+ } -+ // Paper end - iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack); - // CraftBukkit start - if (blockstate != null) { diff --git a/patches/server/0829-Add-configurable-height-for-slime-spawn.patch b/patches/server/0829-Add-configurable-height-for-slime-spawn.patch deleted file mode 100644 index a05bae35ee..0000000000 --- a/patches/server/0829-Add-configurable-height-for-slime-spawn.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Mon, 2 Aug 2021 11:24:39 -0400 -Subject: [PATCH] Add configurable height for slime spawn - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index 15d5a8290be35c2caebf8e296300e8f32cb597c7..fa79316adb11ab39cf921475e12a50058fd82a87 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -320,7 +320,11 @@ public class Slime extends Mob implements Enemy { - - public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - if (world.getDifficulty() != Difficulty.PEACEFUL) { -- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { -+ // Paper start - Replace rules for Height in Swamp Biome -+ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; -+ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; -+ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { -+ // Paper end - return checkMobSpawnRules(type, world, spawnReason, pos, random); - } - -@@ -331,7 +335,10 @@ public class Slime extends Mob implements Enemy { - ChunkPos chunkcoordintpair = new ChunkPos(pos); - boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper - -- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) { -+ // Paper start - Replace rules for Height in Slime Chunks -+ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum; -+ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) { -+ // Paper end - return checkMobSpawnRules(type, world, spawnReason, pos, random); - } - } diff --git a/patches/server/0829-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch b/patches/server/0829-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch new file mode 100644 index 0000000000..eb758a5555 --- /dev/null +++ b/patches/server/0829-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MCMDEV +Date: Fri, 24 Sep 2021 17:59:21 +0200 +Subject: [PATCH] Added getHostname to AsyncPlayerPreLoginEvent + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 553eb8e437b07376dbfc54b0018bcc3ff93e4461..51ff7ab5d8740e755cc893723f659c8481c1ec89 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -421,7 +421,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + + // Paper start + com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(ServerLoginPacketListenerImpl.this.gameProfile); +- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile); // Paper - add rawAddress ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile, ServerLoginPacketListenerImpl.this.hostname); // Paper - add rawAddress & hostname + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); + profile.complete(true); // Paper - setPlayerProfileAPI diff --git a/patches/server/0830-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch b/patches/server/0830-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch deleted file mode 100644 index eb758a5555..0000000000 --- a/patches/server/0830-Added-getHostname-to-AsyncPlayerPreLoginEvent.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MCMDEV -Date: Fri, 24 Sep 2021 17:59:21 +0200 -Subject: [PATCH] Added getHostname to AsyncPlayerPreLoginEvent - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 553eb8e437b07376dbfc54b0018bcc3ff93e4461..51ff7ab5d8740e755cc893723f659c8481c1ec89 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -421,7 +421,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - - // Paper start - com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(ServerLoginPacketListenerImpl.this.gameProfile); -- AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile); // Paper - add rawAddress -+ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, profile, ServerLoginPacketListenerImpl.this.hostname); // Paper - add rawAddress & hostname - server.getPluginManager().callEvent(asyncEvent); - profile = asyncEvent.getPlayerProfile(); - profile.complete(true); // Paper - setPlayerProfileAPI diff --git a/patches/server/0830-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0830-Fix-xp-reward-for-baby-zombies.patch new file mode 100644 index 0000000000..42920471c5 --- /dev/null +++ b/patches/server/0830-Fix-xp-reward-for-baby-zombies.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 16 Jan 2022 10:34:02 -0800 +Subject: [PATCH] Fix xp reward for baby zombies + +The field that tracks the xpReward was not +getting reset if the death was cancelled +so this resets it after each call to +Zombie#getExperienceReward + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +index 3e1fa4336cc18eaeb048d0a1d592cf74820ff3b2..97b3082dc020043fa38d9e5e4591102f97519ed3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java +@@ -173,11 +173,16 @@ public class Zombie extends Monster { + + @Override + public int getExperienceReward() { ++ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward + if (this.isBaby()) { + this.xpReward = (int) ((double) this.xpReward * 2.5D); + } + +- return super.getExperienceReward(); ++ // Paper start - only change the XP reward for the calculations in the super method ++ int reward = super.getExperienceReward(); ++ this.xpReward = previousReward; ++ return reward; ++ // Paper end + } + + @Override diff --git a/patches/server/0831-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0831-Fix-xp-reward-for-baby-zombies.patch deleted file mode 100644 index 42920471c5..0000000000 --- a/patches/server/0831-Fix-xp-reward-for-baby-zombies.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 16 Jan 2022 10:34:02 -0800 -Subject: [PATCH] Fix xp reward for baby zombies - -The field that tracks the xpReward was not -getting reset if the death was cancelled -so this resets it after each call to -Zombie#getExperienceReward - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 3e1fa4336cc18eaeb048d0a1d592cf74820ff3b2..97b3082dc020043fa38d9e5e4591102f97519ed3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -173,11 +173,16 @@ public class Zombie extends Monster { - - @Override - public int getExperienceReward() { -+ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward - if (this.isBaby()) { - this.xpReward = (int) ((double) this.xpReward * 2.5D); - } - -- return super.getExperienceReward(); -+ // Paper start - only change the XP reward for the calculations in the super method -+ int reward = super.getExperienceReward(); -+ this.xpReward = previousReward; -+ return reward; -+ // Paper end - } - - @Override diff --git a/patches/server/0831-Kick-on-main-for-illegal-chat.patch b/patches/server/0831-Kick-on-main-for-illegal-chat.patch new file mode 100644 index 0000000000..054da69477 --- /dev/null +++ b/patches/server/0831-Kick-on-main-for-illegal-chat.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 17 Jan 2022 19:47:19 +0100 +Subject: [PATCH] Kick on main for illegal chat + +Makes the PlayerKickEvent fire on the main thread for +illegal characters or chat out-of-order errors. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index f5a2299e43e4e7863aff9f0fb5ed1773b1330c7a..b82d6ebb30c0fdc49f21d993c6a1affbecc27af7 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2175,7 +2175,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // CraftBukkit end + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { ++ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause ++ }); // Paper - push to main for event firing + } else { + if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) { + // this.server.submit(() -> { // CraftBukkit - async chat +@@ -2203,7 +2205,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { ++ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper ++ }); // Paper - push to main for event firing + } else { + if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) { + this.server.submit(() -> { +@@ -2289,7 +2293,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { + if (!this.updateChatOrder(timestamp)) { + ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); ++ this.server.scheduleOnMain(() -> { // Paper - push to main + this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause ++ }); // Paper - push to main + return false; + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); diff --git a/patches/server/0832-Kick-on-main-for-illegal-chat.patch b/patches/server/0832-Kick-on-main-for-illegal-chat.patch deleted file mode 100644 index 06510268a6..0000000000 --- a/patches/server/0832-Kick-on-main-for-illegal-chat.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 17 Jan 2022 19:47:19 +0100 -Subject: [PATCH] Kick on main for illegal chat - -Makes the PlayerKickEvent fire on the main thread for -illegal characters or chat out-of-order errors. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5c9d7320536be70100f7e1d843e8c4e0c0802a19..26402aa8879e4e50c619c1e9d8e30ef49c3b8a34 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2174,7 +2174,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // CraftBukkit end - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { -+ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing - this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause -+ }); // Paper - push to main for event firing - } else { - if (this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages())) { - // this.server.submit(() -> { // CraftBukkit - async chat -@@ -2202,7 +2204,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - @Override - public void handleChatCommand(ServerboundChatCommandPacket packet) { - if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { -+ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing - this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper -+ }); // Paper - push to main for event firing - } else { - if (this.tryHandleChat(packet.command(), packet.timeStamp(), packet.lastSeenMessages())) { - this.server.submit(() -> { -@@ -2288,7 +2292,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { - if (!this.updateChatOrder(timestamp)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); -+ this.server.scheduleOnMain(() -> { // Paper - push to main - this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause -+ }); // Paper - push to main - return false; - } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales - this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); diff --git a/patches/server/0832-Multi-Block-Change-API-Implementation.patch b/patches/server/0832-Multi-Block-Change-API-Implementation.patch new file mode 100644 index 0000000000..6ae11e6a77 --- /dev/null +++ b/patches/server/0832-Multi-Block-Change-API-Implementation.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brody Beckwith +Date: Fri, 14 Jan 2022 00:41:11 -0500 +Subject: [PATCH] Multi Block Change API Implementation + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +index 82ea4fabd5732052a286d50bcff8bbcc2c4aa7d7..652bea6868a03a5315965f79c76172fb9dbb93fb 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java +@@ -54,6 +54,15 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges, boolean suppressLightUpdates) { ++ this.sectionPos = sectionPos; ++ this.positions = blockChanges.keySet().toShortArray(); ++ this.states = blockChanges.values().toArray(new BlockState[0]); ++ this.suppressLightUpdates = suppressLightUpdates; ++ } ++ // Paper end ++ + @Override + public void write(FriendlyByteBuf buf) { + buf.writeLong(this.sectionPos.asLong()); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8882651847fc9a8d2e4222f10eb389b553da48ca..86d9250ce0a49635362a2710bf3c064936d1c77f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -29,6 +29,7 @@ import java.util.logging.Logger; + import javax.annotation.Nullable; + import net.minecraft.advancements.AdvancementProgress; + import net.minecraft.core.BlockPos; ++import net.minecraft.core.SectionPos; // Paper + import net.minecraft.nbt.CompoundTag; + import net.minecraft.network.FriendlyByteBuf; + import net.minecraft.network.chat.ChatMessageContent; +@@ -882,6 +883,35 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + this.getHandle().connection.send(packet); + } + ++ // Paper start ++ @Override ++ public void sendMultiBlockChange(Map blockChanges, boolean suppressLightUpdates) { ++ if (this.getHandle().connection == null) return; ++ ++ Map> sectionMap = new HashMap<>(); ++ ++ for (Map.Entry entry : blockChanges.entrySet()) { ++ Location location = entry.getKey(); ++ if (!location.getWorld().equals(this.getWorld())) continue; ++ ++ BlockData blockData = entry.getValue(); ++ BlockPos blockPos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); ++ SectionPos sectionPos = SectionPos.of(blockPos); ++ ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); ++ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); ++ } ++ ++ for (Map.Entry> entry : sectionMap.entrySet()) { ++ SectionPos sectionPos = entry.getKey(); ++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); ++ ++ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData, suppressLightUpdates); ++ this.getHandle().connection.send(packet); ++ } ++ } ++ // Paper end ++ + @Override + public void sendBlockDamage(Location loc, float progress) { + Preconditions.checkArgument(loc != null, "loc must not be null"); diff --git a/patches/server/0833-Fix-NotePlayEvent.patch b/patches/server/0833-Fix-NotePlayEvent.patch new file mode 100644 index 0000000000..83040e523a --- /dev/null +++ b/patches/server/0833-Fix-NotePlayEvent.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kieran Wallbanks +Date: Mon, 21 Jun 2021 14:23:50 +0100 +Subject: [PATCH] Fix NotePlayEvent + + +diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +index 293affa9821bcf7c6f4c2d57818958ae2765c5de..c14eb4f7decdbcd6176d3bff95d595a947d4ec95 100644 +--- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java +@@ -63,11 +63,12 @@ public class NoteBlock extends Block { + private void playNote(@Nullable Entity entity, Level world, BlockPos blockposition, BlockState data) { // CraftBukkit + if (world.getBlockState(blockposition.above()).isAir()) { + // CraftBukkit start +- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); +- if (event.isCancelled()) { +- return; +- } ++ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); // Paper - move event into block event handler ++ // if (event.isCancelled()) { ++ // return; ++ // } + // CraftBukkit end ++ // Paper - TODO any way to cancel the game event? + world.blockEvent(blockposition, this, 0, 0); + world.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, blockposition); + } +@@ -97,10 +98,14 @@ public class NoteBlock extends Block { + + @Override + public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { +- int k = (Integer) state.getValue(NoteBlock.NOTE); ++ // Paper start - move NotePlayEvent call to fix instrument/note changes ++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(INSTRUMENT), state.getValue(NOTE)); ++ if (event.isCancelled()) return false; ++ int k = event.getNote().getId(); + float f = (float) Math.pow(2.0D, (double) (k - 12) / 12.0D); + +- world.playSound((Player) null, pos, ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); ++ world.playSound(null, pos, org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); ++ // Paper end + world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); + return true; + } diff --git a/patches/server/0833-Multi-Block-Change-API-Implementation.patch b/patches/server/0833-Multi-Block-Change-API-Implementation.patch deleted file mode 100644 index 16d48b2da6..0000000000 --- a/patches/server/0833-Multi-Block-Change-API-Implementation.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brody Beckwith -Date: Fri, 14 Jan 2022 00:41:11 -0500 -Subject: [PATCH] Multi Block Change API Implementation - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -index 82ea4fabd5732052a286d50bcff8bbcc2c4aa7d7..652bea6868a03a5315965f79c76172fb9dbb93fb 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java -@@ -54,6 +54,15 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet blockChanges, boolean suppressLightUpdates) { -+ this.sectionPos = sectionPos; -+ this.positions = blockChanges.keySet().toShortArray(); -+ this.states = blockChanges.values().toArray(new BlockState[0]); -+ this.suppressLightUpdates = suppressLightUpdates; -+ } -+ // Paper end -+ - @Override - public void write(FriendlyByteBuf buf) { - buf.writeLong(this.sectionPos.asLong()); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 67833efdd2c4babe20a01691a44ec6f153656729..c569bf167addfedcb0d8c55b2d2cfcb52e05d0a6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -29,6 +29,7 @@ import java.util.logging.Logger; - import javax.annotation.Nullable; - import net.minecraft.advancements.AdvancementProgress; - import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; // Paper - import net.minecraft.nbt.CompoundTag; - import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.chat.ChatMessageContent; -@@ -882,6 +883,35 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); - } - -+ // Paper start -+ @Override -+ public void sendMultiBlockChange(Map blockChanges, boolean suppressLightUpdates) { -+ if (this.getHandle().connection == null) return; -+ -+ Map> sectionMap = new HashMap<>(); -+ -+ for (Map.Entry entry : blockChanges.entrySet()) { -+ Location location = entry.getKey(); -+ if (!location.getWorld().equals(this.getWorld())) continue; -+ -+ BlockData blockData = entry.getValue(); -+ BlockPos blockPos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); -+ SectionPos sectionPos = SectionPos.of(blockPos); -+ -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>()); -+ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState()); -+ } -+ -+ for (Map.Entry> entry : sectionMap.entrySet()) { -+ SectionPos sectionPos = entry.getKey(); -+ it.unimi.dsi.fastutil.shorts.Short2ObjectMap blockData = entry.getValue(); -+ -+ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData, suppressLightUpdates); -+ this.getHandle().connection.send(packet); -+ } -+ } -+ // Paper end -+ - @Override - public void sendBlockDamage(Location loc, float progress) { - Preconditions.checkArgument(loc != null, "loc must not be null"); diff --git a/patches/server/0834-Fix-NotePlayEvent.patch b/patches/server/0834-Fix-NotePlayEvent.patch deleted file mode 100644 index 83040e523a..0000000000 --- a/patches/server/0834-Fix-NotePlayEvent.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kieran Wallbanks -Date: Mon, 21 Jun 2021 14:23:50 +0100 -Subject: [PATCH] Fix NotePlayEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -index 293affa9821bcf7c6f4c2d57818958ae2765c5de..c14eb4f7decdbcd6176d3bff95d595a947d4ec95 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -63,11 +63,12 @@ public class NoteBlock extends Block { - private void playNote(@Nullable Entity entity, Level world, BlockPos blockposition, BlockState data) { // CraftBukkit - if (world.getBlockState(blockposition.above()).isAir()) { - // CraftBukkit start -- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); -- if (event.isCancelled()) { -- return; -- } -+ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); // Paper - move event into block event handler -+ // if (event.isCancelled()) { -+ // return; -+ // } - // CraftBukkit end -+ // Paper - TODO any way to cancel the game event? - world.blockEvent(blockposition, this, 0, 0); - world.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, blockposition); - } -@@ -97,10 +98,14 @@ public class NoteBlock extends Block { - - @Override - public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { -- int k = (Integer) state.getValue(NoteBlock.NOTE); -+ // Paper start - move NotePlayEvent call to fix instrument/note changes -+ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(INSTRUMENT), state.getValue(NOTE)); -+ if (event.isCancelled()) return false; -+ int k = event.getNote().getId(); - float f = (float) Math.pow(2.0D, (double) (k - 12) / 12.0D); - -- world.playSound((Player) null, pos, ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); -+ world.playSound(null, pos, org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); -+ // Paper end - world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); - return true; - } diff --git a/patches/server/0834-Freeze-Tick-Lock-API.patch b/patches/server/0834-Freeze-Tick-Lock-API.patch new file mode 100644 index 0000000000..7705d00533 --- /dev/null +++ b/patches/server/0834-Freeze-Tick-Lock-API.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 20:27:43 -0500 +Subject: [PATCH] Freeze Tick Lock API + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c6f203f95f70ca6286946e2ccc40de05d8eabc49..f1e471fee49d0213a97251be1b6793d5fb2165f2 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -397,6 +397,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + private org.bukkit.util.Vector origin; + @javax.annotation.Nullable + private UUID originWorld; ++ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +@@ -825,7 +826,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + this.setRemainingFireTicks(this.remainingFireTicks - 1); + } + +- if (this.getTicksFrozen() > 0) { ++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API + this.setTicksFrozen(0); + this.level.levelEvent((Player) null, 1009, this.blockPosition, 1); + } +@@ -2261,6 +2262,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (fromNetherPortal) { + nbt.putBoolean("Paper.FromNetherPortal", true); + } ++ if (freezeLocked) { ++ nbt.putBoolean("Paper.FreezeLock", true); ++ } + // Paper end + return nbt; + } catch (Throwable throwable) { +@@ -2425,6 +2429,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (spawnReason == null) { + spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; + } ++ if (nbt.contains("Paper.FreezeLock")) { ++ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); ++ } + // Paper end + + } catch (Throwable throwable) { +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index d6ecdeab398d7bfde3d760ada0374245f03014b3..73d931489c76f2effe71362a46a69087a1a09463 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3376,7 +3376,7 @@ public abstract class LivingEntity extends Entity { + boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES); + int i; + +- if (!this.level.isClientSide && !this.isDeadOrDying()) { ++ if (!this.level.isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API + i = this.getTicksFrozen(); + if (this.isInPowderSnow && this.canFreeze()) { + this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), i + 1)); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 385368d0c6a13f9b1b907bcc48d1fb2a845262ef..c4ffccddce33cf461d9b04ccbb90026544f16b7d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -644,6 +644,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return this.getHandle().isFullyFrozen(); + } + ++ // Paper Start - Freeze Tick Lock API ++ @Override ++ public boolean isFreezeTickingLocked() { ++ return this.entity.freezeLocked; ++ } ++ ++ @Override ++ public void lockFreezeTicks(boolean locked) { ++ this.entity.freezeLocked = locked; ++ } ++ // Paper end - Freeze Tick Lock API + @Override + public void remove() { + this.entity.discard(); diff --git a/patches/server/0835-Dolphin-API.patch b/patches/server/0835-Dolphin-API.patch new file mode 100644 index 0000000000..4bb12a6785 --- /dev/null +++ b/patches/server/0835-Dolphin-API.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Tue, 7 Dec 2021 19:34:23 -0500 +Subject: [PATCH] Dolphin API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +index 938e141f161acf5de5d3361382b514caea02c6fb..75919bf87b6f5cad06ca76888e284e2548594f00 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +@@ -24,4 +24,34 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { + public EntityType getType() { + return EntityType.DOLPHIN; + } ++ ++ @Override ++ public int getMoistness() { ++ return this.getHandle().getMoistnessLevel(); ++ } ++ ++ @Override ++ public void setMoistness(int moistness) { ++ this.getHandle().setMoisntessLevel(moistness); ++ } ++ ++ @Override ++ public void setHasFish(boolean hasFish) { ++ this.getHandle().setGotFish(hasFish); ++ } ++ ++ @Override ++ public boolean hasFish() { ++ return this.getHandle().gotFish(); ++ } ++ ++ @Override ++ public org.bukkit.Location getTreasureLocation() { ++ return net.minecraft.server.MCUtil.toLocation(this.getHandle().level, this.getHandle().getTreasurePos()); ++ } ++ ++ @Override ++ public void setTreasureLocation(org.bukkit.Location location) { ++ this.getHandle().setTreasurePos(net.minecraft.server.MCUtil.toBlockPosition(location)); ++ } + } diff --git a/patches/server/0835-Freeze-Tick-Lock-API.patch b/patches/server/0835-Freeze-Tick-Lock-API.patch deleted file mode 100644 index bb6de3cbf8..0000000000 --- a/patches/server/0835-Freeze-Tick-Lock-API.patch +++ /dev/null @@ -1,82 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 20:27:43 -0500 -Subject: [PATCH] Freeze Tick Lock API - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index ccef7676eee4024885fee3c6350410100a97c748..473b7c0bd9c49192041e3a6e4c8a9d760344a44a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -397,6 +397,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - private org.bukkit.util.Vector origin; - @javax.annotation.Nullable - private UUID originWorld; -+ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -825,7 +826,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - this.setRemainingFireTicks(this.remainingFireTicks - 1); - } - -- if (this.getTicksFrozen() > 0) { -+ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API - this.setTicksFrozen(0); - this.level.levelEvent((Player) null, 1009, this.blockPosition, 1); - } -@@ -2247,6 +2248,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (fromNetherPortal) { - nbt.putBoolean("Paper.FromNetherPortal", true); - } -+ if (freezeLocked) { -+ nbt.putBoolean("Paper.FreezeLock", true); -+ } - // Paper end - return nbt; - } catch (Throwable throwable) { -@@ -2411,6 +2415,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (spawnReason == null) { - spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; - } -+ if (nbt.contains("Paper.FreezeLock")) { -+ freezeLocked = nbt.getBoolean("Paper.FreezeLock"); -+ } - // Paper end - - } catch (Throwable throwable) { -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 8dc54061802f0253193bda79bded1d5265591519..44c0f77bdeeb9061b1dfcd904ed2c63910e30e42 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3354,7 +3354,7 @@ public abstract class LivingEntity extends Entity { - boolean flag1 = this.getType().is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES); - int i; - -- if (!this.level.isClientSide && !this.isDeadOrDying()) { -+ if (!this.level.isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API - i = this.getTicksFrozen(); - if (this.isInPowderSnow && this.canFreeze()) { - this.setTicksFrozen(Math.min(this.getTicksRequiredToFreeze(), i + 1)); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index df3c8e5342f9284970371d81f84c5ccabaaa00c2..0bae967bb9830784d98c2a1cccca512660c6324a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -642,6 +642,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return this.getHandle().isFullyFrozen(); - } - -+ // Paper Start - Freeze Tick Lock API -+ @Override -+ public boolean isFreezeTickingLocked() { -+ return this.entity.freezeLocked; -+ } -+ -+ @Override -+ public void lockFreezeTicks(boolean locked) { -+ this.entity.freezeLocked = locked; -+ } -+ // Paper end - Freeze Tick Lock API - @Override - public void remove() { - this.entity.discard(); diff --git a/patches/server/0836-Dolphin-API.patch b/patches/server/0836-Dolphin-API.patch deleted file mode 100644 index 4bb12a6785..0000000000 --- a/patches/server/0836-Dolphin-API.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 7 Dec 2021 19:34:23 -0500 -Subject: [PATCH] Dolphin API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -index 938e141f161acf5de5d3361382b514caea02c6fb..75919bf87b6f5cad06ca76888e284e2548594f00 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -@@ -24,4 +24,34 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { - public EntityType getType() { - return EntityType.DOLPHIN; - } -+ -+ @Override -+ public int getMoistness() { -+ return this.getHandle().getMoistnessLevel(); -+ } -+ -+ @Override -+ public void setMoistness(int moistness) { -+ this.getHandle().setMoisntessLevel(moistness); -+ } -+ -+ @Override -+ public void setHasFish(boolean hasFish) { -+ this.getHandle().setGotFish(hasFish); -+ } -+ -+ @Override -+ public boolean hasFish() { -+ return this.getHandle().gotFish(); -+ } -+ -+ @Override -+ public org.bukkit.Location getTreasureLocation() { -+ return net.minecraft.server.MCUtil.toLocation(this.getHandle().level, this.getHandle().getTreasurePos()); -+ } -+ -+ @Override -+ public void setTreasureLocation(org.bukkit.Location location) { -+ this.getHandle().setTreasurePos(net.minecraft.server.MCUtil.toBlockPosition(location)); -+ } - } diff --git a/patches/server/0836-More-PotionEffectType-API.patch b/patches/server/0836-More-PotionEffectType-API.patch new file mode 100644 index 0000000000..5d8c4415d3 --- /dev/null +++ b/patches/server/0836-More-PotionEffectType-API.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 27 May 2021 21:58:24 -0700 +Subject: [PATCH] More PotionEffectType API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java +index a220102c6e981e4c7d03039340423f05b8c2aeb9..29830a30a886f88254a6d0e7c5245fa14f44bd09 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java +@@ -104,4 +104,46 @@ public class CraftPotionEffectType extends PotionEffectType { + public Color getColor() { + return Color.fromRGB(this.handle.getColor()); + } ++ // Paper start ++ @Override ++ public org.bukkit.NamespacedKey getKey() { ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.MOB_EFFECT.getKey(this.handle)); ++ } ++ ++ @Override ++ public java.util.Map getEffectAttributes() { ++ // re-create map each time because a nms MobEffect can have its attributes modified ++ final java.util.Map attributeMap = new java.util.HashMap<>(); ++ this.handle.getAttributeModifiers().forEach((attribute, attributeModifier) -> { ++ attributeMap.put(org.bukkit.craftbukkit.attribute.CraftAttributeMap.fromMinecraft(attribute.toString()), org.bukkit.craftbukkit.attribute.CraftAttributeInstance.convert(attributeModifier)); ++ }); ++ return java.util.Map.copyOf(attributeMap); ++ } ++ ++ @Override ++ public double getAttributeModifierAmount(org.bukkit.attribute.Attribute attribute, int effectAmplifier) { ++ com.google.common.base.Preconditions.checkArgument(effectAmplifier >= 0, "effectAmplifier must be greater than or equal to 0"); ++ net.minecraft.world.entity.ai.attributes.Attribute nmsAttribute = org.bukkit.craftbukkit.attribute.CraftAttributeMap.toMinecraft(attribute); ++ com.google.common.base.Preconditions.checkArgument(this.handle.getAttributeModifiers().containsKey(nmsAttribute), attribute + " is not present on " + this.getKey()); ++ return this.handle.getAttributeModifierValue(effectAmplifier, this.handle.getAttributeModifiers().get(nmsAttribute)); ++ } ++ ++ @Override ++ public PotionEffectType.Category getEffectCategory() { ++ return fromNMS(handle.getCategory()); ++ } ++ ++ @Override ++ public String translationKey() { ++ return this.handle.getDescriptionId(); ++ } ++ ++ public static PotionEffectType.Category fromNMS(net.minecraft.world.effect.MobEffectCategory mobEffectInfo) { ++ return switch (mobEffectInfo) { ++ case BENEFICIAL -> PotionEffectType.Category.BENEFICIAL; ++ case HARMFUL -> PotionEffectType.Category.HARMFUL; ++ case NEUTRAL -> PotionEffectType.Category.NEUTRAL; ++ }; ++ } ++ // Paper end + } +diff --git a/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5012bc0469ba03cde66749a11f4e7d93206bfd7 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java +@@ -0,0 +1,28 @@ ++package io.papermc.paper.effects; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import net.minecraft.world.effect.MobEffectCategory; ++import org.bukkit.craftbukkit.potion.CraftPotionEffectType; ++import org.bukkit.potion.PotionEffectType; ++import org.junit.Test; ++ ++import static org.junit.Assert.assertEquals; ++import static org.junit.Assert.assertNotNull; ++ ++public class EffectCategoryTest { ++ ++ @Test ++ public void testEffectCategoriesExist() { ++ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) { ++ assertNotNull(mobEffectInfo + " is missing a bukkit equivalent", CraftPotionEffectType.fromNMS(mobEffectInfo)); ++ } ++ } ++ ++ @Test ++ public void testCategoryHasEquivalentColors() { ++ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) { ++ PotionEffectType.Category bukkitEffectCategory = CraftPotionEffectType.fromNMS(mobEffectInfo); ++ assertEquals(mobEffectInfo.getTooltipFormatting().name() + " doesn't equal " + bukkitEffectCategory.getColor(), bukkitEffectCategory.getColor(), PaperAdventure.asAdventure(mobEffectInfo.getTooltipFormatting())); ++ } ++ } ++} diff --git a/patches/server/0837-More-PotionEffectType-API.patch b/patches/server/0837-More-PotionEffectType-API.patch deleted file mode 100644 index 5d8c4415d3..0000000000 --- a/patches/server/0837-More-PotionEffectType-API.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 27 May 2021 21:58:24 -0700 -Subject: [PATCH] More PotionEffectType API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java -index a220102c6e981e4c7d03039340423f05b8c2aeb9..29830a30a886f88254a6d0e7c5245fa14f44bd09 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java -@@ -104,4 +104,46 @@ public class CraftPotionEffectType extends PotionEffectType { - public Color getColor() { - return Color.fromRGB(this.handle.getColor()); - } -+ // Paper start -+ @Override -+ public org.bukkit.NamespacedKey getKey() { -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(net.minecraft.core.Registry.MOB_EFFECT.getKey(this.handle)); -+ } -+ -+ @Override -+ public java.util.Map getEffectAttributes() { -+ // re-create map each time because a nms MobEffect can have its attributes modified -+ final java.util.Map attributeMap = new java.util.HashMap<>(); -+ this.handle.getAttributeModifiers().forEach((attribute, attributeModifier) -> { -+ attributeMap.put(org.bukkit.craftbukkit.attribute.CraftAttributeMap.fromMinecraft(attribute.toString()), org.bukkit.craftbukkit.attribute.CraftAttributeInstance.convert(attributeModifier)); -+ }); -+ return java.util.Map.copyOf(attributeMap); -+ } -+ -+ @Override -+ public double getAttributeModifierAmount(org.bukkit.attribute.Attribute attribute, int effectAmplifier) { -+ com.google.common.base.Preconditions.checkArgument(effectAmplifier >= 0, "effectAmplifier must be greater than or equal to 0"); -+ net.minecraft.world.entity.ai.attributes.Attribute nmsAttribute = org.bukkit.craftbukkit.attribute.CraftAttributeMap.toMinecraft(attribute); -+ com.google.common.base.Preconditions.checkArgument(this.handle.getAttributeModifiers().containsKey(nmsAttribute), attribute + " is not present on " + this.getKey()); -+ return this.handle.getAttributeModifierValue(effectAmplifier, this.handle.getAttributeModifiers().get(nmsAttribute)); -+ } -+ -+ @Override -+ public PotionEffectType.Category getEffectCategory() { -+ return fromNMS(handle.getCategory()); -+ } -+ -+ @Override -+ public String translationKey() { -+ return this.handle.getDescriptionId(); -+ } -+ -+ public static PotionEffectType.Category fromNMS(net.minecraft.world.effect.MobEffectCategory mobEffectInfo) { -+ return switch (mobEffectInfo) { -+ case BENEFICIAL -> PotionEffectType.Category.BENEFICIAL; -+ case HARMFUL -> PotionEffectType.Category.HARMFUL; -+ case NEUTRAL -> PotionEffectType.Category.NEUTRAL; -+ }; -+ } -+ // Paper end - } -diff --git a/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a5012bc0469ba03cde66749a11f4e7d93206bfd7 ---- /dev/null -+++ b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java -@@ -0,0 +1,28 @@ -+package io.papermc.paper.effects; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.minecraft.world.effect.MobEffectCategory; -+import org.bukkit.craftbukkit.potion.CraftPotionEffectType; -+import org.bukkit.potion.PotionEffectType; -+import org.junit.Test; -+ -+import static org.junit.Assert.assertEquals; -+import static org.junit.Assert.assertNotNull; -+ -+public class EffectCategoryTest { -+ -+ @Test -+ public void testEffectCategoriesExist() { -+ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) { -+ assertNotNull(mobEffectInfo + " is missing a bukkit equivalent", CraftPotionEffectType.fromNMS(mobEffectInfo)); -+ } -+ } -+ -+ @Test -+ public void testCategoryHasEquivalentColors() { -+ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) { -+ PotionEffectType.Category bukkitEffectCategory = CraftPotionEffectType.fromNMS(mobEffectInfo); -+ assertEquals(mobEffectInfo.getTooltipFormatting().name() + " doesn't equal " + bukkitEffectCategory.getColor(), bukkitEffectCategory.getColor(), PaperAdventure.asAdventure(mobEffectInfo.getTooltipFormatting())); -+ } -+ } -+} diff --git a/patches/server/0837-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0837-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch new file mode 100644 index 0000000000..02ee15a8e1 --- /dev/null +++ b/patches/server/0837-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Mon, 12 Jul 2021 12:28:29 +0100 +Subject: [PATCH] Use a CHM for StructureTemplate.Pallete cache + +fixes a CME due to this collection being shared across threads + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index 9c45e49f480ccd72f6d6c94e5dec41c4becc76cf..4aac9be67a073e60272a68b52c2cda026d4ee28f 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -832,7 +832,7 @@ public class StructureTemplate { + public static final class Palette { + + private final List blocks; +- private final Map> cache = Maps.newHashMap(); ++ private final Map> cache = Maps.newConcurrentMap(); // Paper + + Palette(List infos) { + this.blocks = infos; diff --git a/patches/server/0838-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0838-API-for-creating-command-sender-which-forwards-feedb.patch new file mode 100644 index 0000000000..1e348f430f --- /dev/null +++ b/patches/server/0838-API-for-creating-command-sender-which-forwards-feedb.patch @@ -0,0 +1,178 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 1 Feb 2022 15:51:55 -0700 +Subject: [PATCH] API for creating command sender which forwards feedback + + +diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +new file mode 100644 +index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java +@@ -0,0 +1,111 @@ ++package io.papermc.paper.commands; ++ ++import io.papermc.paper.adventure.PaperAdventure; ++import java.util.UUID; ++import java.util.function.Consumer; ++import net.kyori.adventure.audience.MessageType; ++import net.kyori.adventure.identity.Identity; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; ++import net.minecraft.commands.CommandSource; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.phys.Vec2; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.command.ServerCommandSender; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class FeedbackForwardingSender extends ServerCommandSender { ++ private final Consumer feedback; ++ private final CraftServer server; ++ ++ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { ++ super(((ServerCommandSender) server.getConsoleSender()).perm); ++ this.server = server; ++ this.feedback = feedback; ++ } ++ ++ @Override ++ public void sendMessage(final String message) { ++ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); ++ } ++ ++ @Override ++ public void sendMessage(final String... messages) { ++ for (final String message : messages) { ++ this.sendMessage(message); ++ } ++ } ++ ++ @Override ++ public void sendMessage(final Identity identity, final Component message, final MessageType type) { ++ this.feedback.accept(message); ++ } ++ ++ @Override ++ public String getName() { ++ return "FeedbackForwardingSender"; ++ } ++ ++ @Override ++ public Component name() { ++ return Component.text(this.getName()); ++ } ++ ++ @Override ++ public boolean isOp() { ++ return true; ++ } ++ ++ @Override ++ public void setOp(final boolean value) { ++ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); ++ } ++ ++ public CommandSourceStack asVanilla() { ++ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); ++ return new CommandSourceStack( ++ new Source(this), ++ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), ++ Vec2.ZERO, ++ overworld, ++ 4, ++ this.getName(), ++ net.minecraft.network.chat.Component.literal(this.getName()), ++ this.server.getServer(), ++ null ++ ); ++ } ++ ++ private record Source(FeedbackForwardingSender sender) implements CommandSource { ++ @Override ++ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { ++ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); ++ } ++ ++ @Override ++ public boolean acceptsSuccess() { ++ return true; ++ } ++ ++ @Override ++ public boolean acceptsFailure() { ++ return true; ++ } ++ ++ @Override ++ public boolean shouldInformAdmins() { ++ return false; ++ } ++ ++ @Override ++ public CommandSender getBukkitSender(final CommandSourceStack stack) { ++ return this.sender; ++ } ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 2b57b60a3ecb5ac170f2d70532dc8439c5a3459f..169f0162479a4a52a6637a9d16eef13c098c8d1a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1990,6 +1990,13 @@ public final class CraftServer implements Server { + return console.console; + } + ++ // Paper start ++ @Override ++ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { ++ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); ++ } ++ // Paper end ++ + public EntityMetadataStore getEntityMetadata() { + return this.entityMetadata; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +index eaff8df6c8c12c64e005a68a02e2e35ed88f757c..1c638a4b1f2c841928d8b2a7ae43e4ebb1f7eac7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +@@ -13,9 +13,15 @@ import org.bukkit.plugin.Plugin; + + public abstract class ServerCommandSender implements CommandSender { + private static PermissibleBase blockPermInst; +- private final PermissibleBase perm; ++ public final PermissibleBase perm; // Paper + private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers + ++ // Paper start ++ public ServerCommandSender(final PermissibleBase permissibleBase) { ++ this.perm = permissibleBase; ++ } ++ // Paper end ++ + public ServerCommandSender() { + if (this instanceof CraftBlockCommandSender) { + if (ServerCommandSender.blockPermInst == null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +index 6df44aa60d2b41b95fe79ed4cf92a6d3369400ea..6035af2cf08353b3d3801220d8116d8611a0cd37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java ++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +@@ -84,6 +84,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { + if (sender instanceof ProxiedCommandSender) { + return ((ProxiedNativeCommandSender) sender).getHandle(); + } ++ // Paper start ++ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { ++ return feedback.asVanilla(); ++ } ++ // Paper end + + throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); + } diff --git a/patches/server/0838-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0838-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch deleted file mode 100644 index 02ee15a8e1..0000000000 --- a/patches/server/0838-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Mon, 12 Jul 2021 12:28:29 +0100 -Subject: [PATCH] Use a CHM for StructureTemplate.Pallete cache - -fixes a CME due to this collection being shared across threads - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index 9c45e49f480ccd72f6d6c94e5dec41c4becc76cf..4aac9be67a073e60272a68b52c2cda026d4ee28f 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -832,7 +832,7 @@ public class StructureTemplate { - public static final class Palette { - - private final List blocks; -- private final Map> cache = Maps.newHashMap(); -+ private final Map> cache = Maps.newConcurrentMap(); // Paper - - Palette(List infos) { - this.blocks = infos; diff --git a/patches/server/0839-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0839-API-for-creating-command-sender-which-forwards-feedb.patch deleted file mode 100644 index ca623b5026..0000000000 --- a/patches/server/0839-API-for-creating-command-sender-which-forwards-feedb.patch +++ /dev/null @@ -1,178 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 1 Feb 2022 15:51:55 -0700 -Subject: [PATCH] API for creating command sender which forwards feedback - - -diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java -@@ -0,0 +1,111 @@ -+package io.papermc.paper.commands; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import java.util.UUID; -+import java.util.function.Consumer; -+import net.kyori.adventure.audience.MessageType; -+import net.kyori.adventure.identity.Identity; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -+import net.minecraft.commands.CommandSource; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.phys.Vec2; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.command.ServerCommandSender; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class FeedbackForwardingSender extends ServerCommandSender { -+ private final Consumer feedback; -+ private final CraftServer server; -+ -+ public FeedbackForwardingSender(final Consumer feedback, final CraftServer server) { -+ super(((ServerCommandSender) server.getConsoleSender()).perm); -+ this.server = server; -+ this.feedback = feedback; -+ } -+ -+ @Override -+ public void sendMessage(final String message) { -+ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); -+ } -+ -+ @Override -+ public void sendMessage(final String... messages) { -+ for (final String message : messages) { -+ this.sendMessage(message); -+ } -+ } -+ -+ @Override -+ public void sendMessage(final Identity identity, final Component message, final MessageType type) { -+ this.feedback.accept(message); -+ } -+ -+ @Override -+ public String getName() { -+ return "FeedbackForwardingSender"; -+ } -+ -+ @Override -+ public Component name() { -+ return Component.text(this.getName()); -+ } -+ -+ @Override -+ public boolean isOp() { -+ return true; -+ } -+ -+ @Override -+ public void setOp(final boolean value) { -+ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName()); -+ } -+ -+ public CommandSourceStack asVanilla() { -+ final @Nullable ServerLevel overworld = this.server.getServer().overworld(); -+ return new CommandSourceStack( -+ new Source(this), -+ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()), -+ Vec2.ZERO, -+ overworld, -+ 4, -+ this.getName(), -+ net.minecraft.network.chat.Component.literal(this.getName()), -+ this.server.getServer(), -+ null -+ ); -+ } -+ -+ private record Source(FeedbackForwardingSender sender) implements CommandSource { -+ @Override -+ public void sendSystemMessage(final net.minecraft.network.chat.Component message) { -+ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message)); -+ } -+ -+ @Override -+ public boolean acceptsSuccess() { -+ return true; -+ } -+ -+ @Override -+ public boolean acceptsFailure() { -+ return true; -+ } -+ -+ @Override -+ public boolean shouldInformAdmins() { -+ return false; -+ } -+ -+ @Override -+ public CommandSender getBukkitSender(final CommandSourceStack stack) { -+ return this.sender; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 3fe38a9a6f4c25b91a25bc2320a7d7fcbe446d9d..17d4afb04a0a767283fcccb0cc00ab0043b49e22 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1990,6 +1990,13 @@ public final class CraftServer implements Server { - return console.console; - } - -+ // Paper start -+ @Override -+ public CommandSender createCommandSender(final java.util.function.Consumer feedback) { -+ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this); -+ } -+ // Paper end -+ - public EntityMetadataStore getEntityMetadata() { - return this.entityMetadata; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -index eaff8df6c8c12c64e005a68a02e2e35ed88f757c..1c638a4b1f2c841928d8b2a7ae43e4ebb1f7eac7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java -@@ -13,9 +13,15 @@ import org.bukkit.plugin.Plugin; - - public abstract class ServerCommandSender implements CommandSender { - private static PermissibleBase blockPermInst; -- private final PermissibleBase perm; -+ public final PermissibleBase perm; // Paper - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers - -+ // Paper start -+ public ServerCommandSender(final PermissibleBase permissibleBase) { -+ this.perm = permissibleBase; -+ } -+ // Paper end -+ - public ServerCommandSender() { - if (this instanceof CraftBlockCommandSender) { - if (ServerCommandSender.blockPermInst == null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -index 6df44aa60d2b41b95fe79ed4cf92a6d3369400ea..6035af2cf08353b3d3801220d8116d8611a0cd37 100644 ---- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java -@@ -84,6 +84,11 @@ public final class VanillaCommandWrapper extends BukkitCommand { - if (sender instanceof ProxiedCommandSender) { - return ((ProxiedNativeCommandSender) sender).getHandle(); - } -+ // Paper start -+ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) { -+ return feedback.asVanilla(); -+ } -+ // Paper end - - throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener"); - } diff --git a/patches/server/0839-Add-config-for-stronghold-seed.patch b/patches/server/0839-Add-config-for-stronghold-seed.patch new file mode 100644 index 0000000000..5118cd7c4b --- /dev/null +++ b/patches/server/0839-Add-config-for-stronghold-seed.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 13 Jan 2022 23:05:53 -0800 +Subject: [PATCH] Add config for stronghold seed + + +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 bdcfa5aac4cd0bd5841922295cc8fbb6ca69bd68..19ffd93b7bc745d9a6822f1e5642d2f640f61df7 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -236,7 +236,13 @@ public abstract class ChunkGenerator { + HolderSet holderset = concentricringsstructureplacement.preferredBiomes(); + RandomSource randomsource = RandomSource.create(); + ++ // Paper start ++ if (this.conf.strongholdSeed != null && this.structureSets.getResourceKey(holder).orElse(null) == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS) { ++ randomsource.setSeed(this.conf.strongholdSeed); ++ } else { + randomsource.setSeed(this instanceof FlatLevelSource ? 0L : randomstate.legacyLevelSeed()); ++ } ++ // Paper end + double d0 = randomsource.nextDouble() * 3.141592653589793D * 2.0D; + int l = 0; + int i1 = 0; +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index cf96f9fdc4ae561f01d44503b9851c60140e4ea7..bbf15fbb889670e57bd86377590a1b3abe80b96d 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -366,6 +366,7 @@ public class SpigotWorldConfig + public int mansionSeed; + public int fossilSeed; + public int portalSeed; ++ public Long strongholdSeed; // Paper + private void initWorldGenSeeds() + { + this.villageSeed = this.getInt( "seed-village", 10387312 ); +@@ -383,6 +384,10 @@ public class SpigotWorldConfig + this.mansionSeed = this.getInt( "seed-mansion", 10387319 ); + this.fossilSeed = this.getInt( "seed-fossil", 14357921 ); + this.portalSeed = this.getInt( "seed-portal", 34222645 ); ++ // Paper start ++ final String strongholdSeedString = this.getString("seed-stronghold", "default"); ++ this.strongholdSeed = org.apache.commons.lang3.math.NumberUtils.isParsable(strongholdSeedString) ? Long.parseLong(strongholdSeedString) : null; ++ // Paper end + this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed + + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed ); + } diff --git a/patches/server/0840-Add-config-for-stronghold-seed.patch b/patches/server/0840-Add-config-for-stronghold-seed.patch deleted file mode 100644 index 5118cd7c4b..0000000000 --- a/patches/server/0840-Add-config-for-stronghold-seed.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 13 Jan 2022 23:05:53 -0800 -Subject: [PATCH] Add config for stronghold seed - - -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 bdcfa5aac4cd0bd5841922295cc8fbb6ca69bd68..19ffd93b7bc745d9a6822f1e5642d2f640f61df7 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -236,7 +236,13 @@ public abstract class ChunkGenerator { - HolderSet holderset = concentricringsstructureplacement.preferredBiomes(); - RandomSource randomsource = RandomSource.create(); - -+ // Paper start -+ if (this.conf.strongholdSeed != null && this.structureSets.getResourceKey(holder).orElse(null) == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS) { -+ randomsource.setSeed(this.conf.strongholdSeed); -+ } else { - randomsource.setSeed(this instanceof FlatLevelSource ? 0L : randomstate.legacyLevelSeed()); -+ } -+ // Paper end - double d0 = randomsource.nextDouble() * 3.141592653589793D * 2.0D; - int l = 0; - int i1 = 0; -diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index cf96f9fdc4ae561f01d44503b9851c60140e4ea7..bbf15fbb889670e57bd86377590a1b3abe80b96d 100644 ---- a/src/main/java/org/spigotmc/SpigotWorldConfig.java -+++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -366,6 +366,7 @@ public class SpigotWorldConfig - public int mansionSeed; - public int fossilSeed; - public int portalSeed; -+ public Long strongholdSeed; // Paper - private void initWorldGenSeeds() - { - this.villageSeed = this.getInt( "seed-village", 10387312 ); -@@ -383,6 +384,10 @@ public class SpigotWorldConfig - this.mansionSeed = this.getInt( "seed-mansion", 10387319 ); - this.fossilSeed = this.getInt( "seed-fossil", 14357921 ); - this.portalSeed = this.getInt( "seed-portal", 34222645 ); -+ // Paper start -+ final String strongholdSeedString = this.getString("seed-stronghold", "default"); -+ this.strongholdSeed = org.apache.commons.lang3.math.NumberUtils.isParsable(strongholdSeedString) ? Long.parseLong(strongholdSeedString) : null; -+ // Paper end - this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed - + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed ); - } diff --git a/patches/server/0840-Implement-regenerateChunk.patch b/patches/server/0840-Implement-regenerateChunk.patch new file mode 100644 index 0000000000..ba57a50388 --- /dev/null +++ b/patches/server/0840-Implement-regenerateChunk.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 31 Jan 2022 11:21:50 +0100 +Subject: [PATCH] Implement regenerateChunk + +Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 7916426a9d7953c2cc15a319adea8d982b63b773..73e7181655b78f5bff90d07edfe6c5408cc08235 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -139,6 +139,7 @@ import org.bukkit.util.Vector; + public class CraftWorld extends CraftRegionAccessor implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); ++ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.LIQUID_CARVERS, ChunkStatus.FEATURES}; // Paper - implement regenerate chunk method + + private final ServerLevel world; + private WorldBorder worldBorder; +@@ -419,27 +420,62 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean regenerateChunk(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot +- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); +- /* +- if (!unloadChunk0(x, z, false)) { +- return false; +- } +- +- final long chunkKey = ChunkCoordIntPair.pair(x, z); +- world.getChunkProvider().unloadQueue.remove(chunkKey); ++ // Paper start - implement regenerateChunk method ++ final ServerLevel serverLevel = this.world; ++ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); ++ final ChunkPos chunkPos = new ChunkPos(x, z); ++ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ levelChunk.removeBlockEntity(blockPos); ++ serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16); ++ } ++ ++ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) { ++ final List list = new ArrayList<>(); ++ final int range = Math.max(1, chunkStatus.getRange()); ++ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) { ++ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) { ++ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true); ++ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) { ++ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true); ++ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) { ++ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true); ++ } ++ list.add(chunkAccess); ++ } ++ } + +- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); +- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); +- if (playerChunk != null) { +- playerChunk.chunk = chunk; ++ final java.util.concurrent.CompletableFuture> future = chunkStatus.generate( ++ Runnable::run, ++ serverLevel, ++ serverChunkCache.getGenerator(), ++ serverLevel.getStructureManager(), ++ serverChunkCache.getLightEngine(), ++ chunk -> { ++ throw new UnsupportedOperationException("Not creating full chunks here"); ++ }, ++ list, ++ true ++ ); ++ serverChunkCache.mainThreadProcessor.managedBlock(future::isDone); ++ if (chunkStatus == ChunkStatus.NOISE) { ++ future.join().left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); ++ } + } + +- if (chunk != null) { +- refreshChunk(x, z); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ serverChunkCache.blockChanged(blockPos); + } + +- return chunk != null; +- */ ++ final Set chunksToRelight = new HashSet<>(9); ++ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) { ++ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) { ++ chunksToRelight.add(new ChunkPos(chunkX, chunkZ)); ++ } ++ } ++ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); ++ return true; ++ // Paper end + } + + @Override diff --git a/patches/server/0841-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0841-Fix-cancelled-powdered-snow-bucket-placement.patch new file mode 100644 index 0000000000..c6754ee3e6 --- /dev/null +++ b/patches/server/0841-Fix-cancelled-powdered-snow-bucket-placement.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 8 Oct 2021 13:12:58 -0700 +Subject: [PATCH] Fix cancelled powdered snow bucket placement + +Cancelling the placement of powdered snow from the powdered +snow bucket didn't revert grass that became snowy because of the +placement. + +diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java +index 00d31df5ba7e147b4b969a89cfc2b5088a988169..62d6c5b7590ff4faef5d8c7a8be03155b7338480 100644 +--- a/src/main/java/net/minecraft/world/item/BlockItem.java ++++ b/src/main/java/net/minecraft/world/item/BlockItem.java +@@ -115,6 +115,7 @@ public class BlockItem extends Item { + blockstate.update(true, false); + + if (this instanceof SolidBucketItem) { ++ ((ServerPlayer) entityhuman).connection.send(new net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket(world, blockposition.below())); // Paper - update block below + ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 + } + return InteractionResult.FAIL; +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 5c987e863a6ef257caebf8321fa3048dfc7a93c5..67626e7faa4d0854d31b41c0a702edbeb6ce4270 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -337,7 +337,7 @@ public final class ItemStack { + int oldCount = this.getCount(); + ServerLevel world = (ServerLevel) itemactioncontext.getLevel(); + +- if (!(this.getItem() instanceof BucketItem || this.getItem() instanceof SolidBucketItem)) { // if not bucket ++ if (!(this.getItem() instanceof BucketItem/* || this.getItem() instanceof SolidBucketItem*/)) { // if not bucket // Paper - capture block states for snow buckets + world.captureBlockStates = true; + // special case bonemeal + if (this.getItem() == Items.BONE_MEAL) { +@@ -391,7 +391,7 @@ public final class ItemStack { + world.capturedBlockStates.clear(); + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); +- } else if (blocks.size() == 1) { ++ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - don't call event twice for snow buckets + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); + } + diff --git a/patches/server/0841-Implement-regenerateChunk.patch b/patches/server/0841-Implement-regenerateChunk.patch deleted file mode 100644 index ba57a50388..0000000000 --- a/patches/server/0841-Implement-regenerateChunk.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 31 Jan 2022 11:21:50 +0100 -Subject: [PATCH] Implement regenerateChunk - -Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 7916426a9d7953c2cc15a319adea8d982b63b773..73e7181655b78f5bff90d07edfe6c5408cc08235 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -139,6 +139,7 @@ import org.bukkit.util.Vector; - public class CraftWorld extends CraftRegionAccessor implements World { - public static final int CUSTOM_DIMENSION_OFFSET = 10; - private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); -+ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.LIQUID_CARVERS, ChunkStatus.FEATURES}; // Paper - implement regenerate chunk method - - private final ServerLevel world; - private WorldBorder worldBorder; -@@ -419,27 +420,62 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean regenerateChunk(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot -- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); -- /* -- if (!unloadChunk0(x, z, false)) { -- return false; -- } -- -- final long chunkKey = ChunkCoordIntPair.pair(x, z); -- world.getChunkProvider().unloadQueue.remove(chunkKey); -+ // Paper start - implement regenerateChunk method -+ final ServerLevel serverLevel = this.world; -+ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); -+ final ChunkPos chunkPos = new ChunkPos(x, z); -+ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true); -+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { -+ levelChunk.removeBlockEntity(blockPos); -+ serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16); -+ } -+ -+ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) { -+ final List list = new ArrayList<>(); -+ final int range = Math.max(1, chunkStatus.getRange()); -+ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) { -+ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) { -+ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true); -+ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) { -+ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true); -+ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) { -+ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true); -+ } -+ list.add(chunkAccess); -+ } -+ } - -- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); -- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); -- if (playerChunk != null) { -- playerChunk.chunk = chunk; -+ final java.util.concurrent.CompletableFuture> future = chunkStatus.generate( -+ Runnable::run, -+ serverLevel, -+ serverChunkCache.getGenerator(), -+ serverLevel.getStructureManager(), -+ serverChunkCache.getLightEngine(), -+ chunk -> { -+ throw new UnsupportedOperationException("Not creating full chunks here"); -+ }, -+ list, -+ true -+ ); -+ serverChunkCache.mainThreadProcessor.managedBlock(future::isDone); -+ if (chunkStatus == ChunkStatus.NOISE) { -+ future.join().left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); -+ } - } - -- if (chunk != null) { -- refreshChunk(x, z); -+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { -+ serverChunkCache.blockChanged(blockPos); - } - -- return chunk != null; -- */ -+ final Set chunksToRelight = new HashSet<>(9); -+ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) { -+ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) { -+ chunksToRelight.add(new ChunkPos(chunkX, chunkZ)); -+ } -+ } -+ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); -+ return true; -+ // Paper end - } - - @Override diff --git a/patches/server/0842-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0842-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch new file mode 100644 index 0000000000..6a270423c8 --- /dev/null +++ b/patches/server/0842-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 12 Feb 2022 12:40:50 -0700 +Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit + +Copies appropriate checks from CraftWorld#getSpawnLimit + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 169f0162479a4a52a6637a9d16eef13c098c8d1a..b1703293e46fcc3abe9b994657288b3dc41f034a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2164,6 +2164,8 @@ public final class CraftServer implements Server { + @Override + public int getSpawnLimit(SpawnCategory spawnCategory) { + // Paper start ++ Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); ++ Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); + return this.getSpawnLimitUnsafe(spawnCategory); + } + public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0842-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0842-Fix-cancelled-powdered-snow-bucket-placement.patch deleted file mode 100644 index c6754ee3e6..0000000000 --- a/patches/server/0842-Fix-cancelled-powdered-snow-bucket-placement.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 8 Oct 2021 13:12:58 -0700 -Subject: [PATCH] Fix cancelled powdered snow bucket placement - -Cancelling the placement of powdered snow from the powdered -snow bucket didn't revert grass that became snowy because of the -placement. - -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 00d31df5ba7e147b4b969a89cfc2b5088a988169..62d6c5b7590ff4faef5d8c7a8be03155b7338480 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -115,6 +115,7 @@ public class BlockItem extends Item { - blockstate.update(true, false); - - if (this instanceof SolidBucketItem) { -+ ((ServerPlayer) entityhuman).connection.send(new net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket(world, blockposition.below())); // Paper - update block below - ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541 - } - return InteractionResult.FAIL; -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 5c987e863a6ef257caebf8321fa3048dfc7a93c5..67626e7faa4d0854d31b41c0a702edbeb6ce4270 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -337,7 +337,7 @@ public final class ItemStack { - int oldCount = this.getCount(); - ServerLevel world = (ServerLevel) itemactioncontext.getLevel(); - -- if (!(this.getItem() instanceof BucketItem || this.getItem() instanceof SolidBucketItem)) { // if not bucket -+ if (!(this.getItem() instanceof BucketItem/* || this.getItem() instanceof SolidBucketItem*/)) { // if not bucket // Paper - capture block states for snow buckets - world.captureBlockStates = true; - // special case bonemeal - if (this.getItem() == Items.BONE_MEAL) { -@@ -391,7 +391,7 @@ public final class ItemStack { - world.capturedBlockStates.clear(); - if (blocks.size() > 1) { - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); -- } else if (blocks.size() == 1) { -+ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - don't call event twice for snow buckets - placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ()); - } - diff --git a/patches/server/0843-Add-GameEvent-tags.patch b/patches/server/0843-Add-GameEvent-tags.patch new file mode 100644 index 0000000000..71d11d1e66 --- /dev/null +++ b/patches/server/0843-Add-GameEvent-tags.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 3 Jan 2021 20:03:35 -0800 +Subject: [PATCH] Add GameEvent tags + + +diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java +new file mode 100644 +index 0000000000000000000000000000000000000000..cb78a3d4e21376ea24347187478525d5f0c24079 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java +@@ -0,0 +1,34 @@ ++package io.papermc.paper; ++ ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.tags.TagKey; ++import org.bukkit.GameEvent; ++import org.bukkit.craftbukkit.tag.CraftTag; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.Collections; ++import java.util.IdentityHashMap; ++import java.util.Map; ++import java.util.Objects; ++import java.util.Set; ++import java.util.stream.Collectors; ++ ++public class CraftGameEventTag extends CraftTag { ++ ++ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { ++ super(registry, tag); ++ } ++ ++ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); ++ @Override ++ public boolean isTagged(@NotNull GameEvent gameEvent) { ++ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registry.GAME_EVENT_REGISTRY, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); ++ } ++ ++ @Override ++ public @NotNull Set getValues() { ++ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(Registry.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); ++ } ++} +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index b1703293e46fcc3abe9b994657288b3dc41f034a..989dcf684ae448e6ff678bd2e72601005a770803 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2575,6 +2575,15 @@ public final class CraftServer implements Server { + return (org.bukkit.Tag) new CraftEntityTag(net.minecraft.core.Registry.ENTITY_TYPE, entityTagKey); + } + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); ++ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.Registry.GAME_EVENT_REGISTRY, key); ++ if (net.minecraft.core.Registry.GAME_EVENT.isKnownTagName(gameEventTagKey)) { ++ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.Registry.GAME_EVENT, gameEventTagKey); ++ } ++ } ++ // Paper end + default -> throw new IllegalArgumentException(); + } + +@@ -2607,6 +2616,13 @@ public final class CraftServer implements Server { + net.minecraft.core.Registry> entityTags = net.minecraft.core.Registry.ENTITY_TYPE; + return entityTags.getTags().map(pair -> (org.bukkit.Tag) new CraftEntityTag(entityTags, pair.getFirst())).collect(ImmutableList.toImmutableList()); + } ++ // Paper start ++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { ++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); ++ net.minecraft.core.Registry gameEvents = net.minecraft.core.Registry.GAME_EVENT; ++ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); ++ // Paper end ++ } + default -> throw new IllegalArgumentException(); + } + } diff --git a/patches/server/0843-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0843-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch deleted file mode 100644 index 2539ce04f9..0000000000 --- a/patches/server/0843-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 12 Feb 2022 12:40:50 -0700 -Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit - -Copies appropriate checks from CraftWorld#getSpawnLimit - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 17d4afb04a0a767283fcccb0cc00ab0043b49e22..8669584f7e89e89145631f86bf123b8a82c38e97 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2164,6 +2164,8 @@ public final class CraftServer implements Server { - @Override - public int getSpawnLimit(SpawnCategory spawnCategory) { - // Paper start -+ Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); -+ Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit."); - return this.getSpawnLimitUnsafe(spawnCategory); - } - public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { diff --git a/patches/server/0844-Add-GameEvent-tags.patch b/patches/server/0844-Add-GameEvent-tags.patch deleted file mode 100644 index c4848660ea..0000000000 --- a/patches/server/0844-Add-GameEvent-tags.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 3 Jan 2021 20:03:35 -0800 -Subject: [PATCH] Add GameEvent tags - - -diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cb78a3d4e21376ea24347187478525d5f0c24079 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java -@@ -0,0 +1,34 @@ -+package io.papermc.paper; -+ -+import net.minecraft.core.Registry; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.tags.TagKey; -+import org.bukkit.GameEvent; -+import org.bukkit.craftbukkit.tag.CraftTag; -+import org.bukkit.craftbukkit.util.CraftNamespacedKey; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.Collections; -+import java.util.IdentityHashMap; -+import java.util.Map; -+import java.util.Objects; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public class CraftGameEventTag extends CraftTag { -+ -+ public CraftGameEventTag(net.minecraft.core.Registry registry, TagKey tag) { -+ super(registry, tag); -+ } -+ -+ private static final Map> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>()); -+ @Override -+ public boolean isTagged(@NotNull GameEvent gameEvent) { -+ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registry.GAME_EVENT_REGISTRY, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag); -+ } -+ -+ @Override -+ public @NotNull Set getValues() { -+ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(Registry.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet()); -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 8669584f7e89e89145631f86bf123b8a82c38e97..ac8f105371e61a93eb416e086ece0243bd251625 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -2575,6 +2575,15 @@ public final class CraftServer implements Server { - return (org.bukkit.Tag) new CraftEntityTag(net.minecraft.core.Registry.ENTITY_TYPE, entityTagKey); - } - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type"); -+ TagKey gameEventTagKey = TagKey.create(net.minecraft.core.Registry.GAME_EVENT_REGISTRY, key); -+ if (net.minecraft.core.Registry.GAME_EVENT.isKnownTagName(gameEventTagKey)) { -+ return (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.Registry.GAME_EVENT, gameEventTagKey); -+ } -+ } -+ // Paper end - default -> throw new IllegalArgumentException(); - } - -@@ -2607,6 +2616,13 @@ public final class CraftServer implements Server { - net.minecraft.core.Registry> entityTags = net.minecraft.core.Registry.ENTITY_TYPE; - return entityTags.getTags().map(pair -> (org.bukkit.Tag) new CraftEntityTag(entityTags, pair.getFirst())).collect(ImmutableList.toImmutableList()); - } -+ // Paper start -+ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { -+ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); -+ net.minecraft.core.Registry gameEvents = net.minecraft.core.Registry.GAME_EVENT; -+ return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); -+ // Paper end -+ } - default -> throw new IllegalArgumentException(); - } - } diff --git a/patches/server/0844-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0844-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch new file mode 100644 index 0000000000..89a4c1af23 --- /dev/null +++ b/patches/server/0844-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 28 Dec 2021 07:19:01 -0800 +Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next + tick + +Currently, only the first world would have had tasks executed. +This might result in chunks loading far slower in the nether, +for example. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 28e2f22cc2e5b6dd47705cdd5095ff2394979c8f..98654c77b2925228458fae447510ec1d6b217370 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1314,6 +1314,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Tue, 28 Dec 2021 07:19:01 -0800 -Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next - tick - -Currently, only the first world would have had tasks executed. -This might result in chunks loading far slower in the nether, -for example. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index a4433426efd0823cd8145a50b38127f63e90adc9..1444391ded50ec10e9bfeb073b01c885c429b8bd 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1314,6 +1314,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sun, 21 Mar 2021 16:25:42 -0700 +Subject: [PATCH] Replace ticket level propagator + +Mojang's propagator is slow, and this isn't surprising +given it's built on the same utilities the vanilla light engine +is built on. The simple propagator I wrote is approximately 4x +faster when simulating player movement. For a long time timing +reports have shown this function take up significant tick, ( +approx 10% or more), and async sampling data shows the level +propagation alone takes up a significant amount. So this +should help with that. A big side effect is that mid-tick +will be more effective, since more time will be allocated +to actually processing chunk tasks vs the ticket level updates. + +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 06e4d3a02e0d1326b7029157856476db4ef3575e..f581a9f79b2357118d912a15344ff94df3b0c50e 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; + import net.minecraft.world.level.chunk.LevelChunk; + import org.slf4j.Logger; + ++import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper + public abstract class DistanceManager { + + static final Logger LOGGER = LogUtils.getLogger(); +@@ -48,7 +49,7 @@ public abstract class DistanceManager { + private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33; + final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); +- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); ++ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator + public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final TickingTracker tickingTicketsTracker = new TickingTracker(); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); +@@ -83,6 +84,46 @@ public abstract class DistanceManager { + this.chunkMap = chunkMap; // Paper + } + ++ // Paper start - replace ticket level propagator ++ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { ++ @Override ++ protected void rehash(int newN) { ++ // no downsizing allowed ++ if (newN < this.n) { ++ return; ++ } ++ super.rehash(newN); ++ } ++ }; ++ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D( ++ (long coordinate, byte oldLevel, byte newLevel) -> { ++ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); ++ } ++ ); ++ // function for converting between ticket levels and propagator levels and vice versa ++ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects ++ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator ++ // and the levels we get out of the propagator ++ ++ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on ++ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded ++ public static int convertBetweenTicketLevels(final int level) { ++ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1; ++ } ++ ++ protected final int getPropagatedTicketLevel(final long coordinate) { ++ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate)); ++ } ++ ++ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { ++ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) { ++ this.ticketLevelPropagator.removeSource(coordinate); ++ } else { ++ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); ++ } ++ } ++ // Paper end - replace ticket level propagator ++ + protected void purgeStaleTickets() { + ++this.ticketTickCounter; + ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); +@@ -117,7 +158,7 @@ public abstract class DistanceManager { + } + + if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); ++ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator + } + + if (((SortedArraySet) entry.getValue()).isEmpty()) { +@@ -140,61 +181,94 @@ public abstract class DistanceManager { + @Nullable + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + ++ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator + public boolean runAllUpdates(ChunkMap chunkStorage) { + //this.f.a(); // Paper - no longer used + this.tickingTicketsTracker.runAllUpdates(); + org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper + this.playerTicketManager.runAllUpdates(); +- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); +- boolean flag = i != 0; ++ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator + + if (flag) { + ; + } + +- // Paper start +- if (!this.pendingChunkUpdates.isEmpty()) { +- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority +- while(!this.pendingChunkUpdates.isEmpty()) { +- ChunkHolder remove = this.pendingChunkUpdates.remove(); +- remove.isUpdateQueued = false; +- remove.updateFutures(chunkStorage, this.mainThreadExecutor); +- } +- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority +- // Paper end +- return true; +- } else { +- if (!this.ticketsToRelease.isEmpty()) { +- LongIterator longiterator = this.ticketsToRelease.iterator(); ++ // Paper start - replace level propagator ++ ticket_update_loop: ++ while (!this.ticketLevelUpdates.isEmpty()) { ++ flag = true; + +- while (longiterator.hasNext()) { +- long j = longiterator.nextLong(); ++ boolean oldPolling = this.pollingPendingChunkUpdates; ++ this.pollingPendingChunkUpdates = true; ++ try { ++ for (java.util.Iterator iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ Long2IntMap.Entry entry = iterator.next(); ++ long key = entry.getLongKey(); ++ int newLevel = entry.getIntValue(); ++ ChunkHolder chunk = this.getChunk(key); ++ ++ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) { ++ // not loaded and it shouldn't be loaded! ++ continue; ++ } + +- if (this.getTickets(j).stream().anyMatch((ticket) -> { +- return ticket.getType() == TicketType.PLAYER; +- })) { +- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); ++ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel(); + +- if (playerchunk == null) { +- throw new IllegalStateException(); ++ if (currentLevel == newLevel) { ++ // nothing to do ++ continue; ++ } ++ ++ this.updateChunkScheduling(key, newLevel, chunk, currentLevel); ++ } ++ ++ long recursiveCheck = ++this.ticketLevelUpdateCount; ++ while (!this.ticketLevelUpdates.isEmpty()) { ++ long key = this.ticketLevelUpdates.firstLongKey(); ++ int newLevel = this.ticketLevelUpdates.removeFirstInt(); ++ ChunkHolder chunk = this.getChunk(key); ++ ++ if (chunk == null) { ++ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) { ++ throw new IllegalStateException("Expected chunk holder to be created"); + } ++ // not loaded and it shouldn't be loaded! ++ continue; ++ } + +- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); ++ int currentLevel = chunk.oldTicketLevel; + +- completablefuture.thenAccept((either) -> { +- this.mainThreadExecutor.execute(() -> { +- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { +- }, j, false)); +- }); +- }); ++ if (currentLevel == newLevel) { ++ // nothing to do ++ continue; ++ } ++ ++ chunk.updateFutures(chunkStorage, this.mainThreadExecutor); ++ if (recursiveCheck != this.ticketLevelUpdateCount) { ++ // back to the start, we must create player chunks and update the ticket level fields before ++ // processing the actual level updates ++ continue ticket_update_loop; + } + } + +- this.ticketsToRelease.clear(); +- } ++ for (;;) { ++ if (recursiveCheck != this.ticketLevelUpdateCount) { ++ continue ticket_update_loop; ++ } ++ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll(); ++ if (pendingUpdate == null) { ++ break; ++ } + +- return flag; ++ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor); ++ } ++ } finally { ++ this.pollingPendingChunkUpdates = oldPolling; ++ } + } ++ ++ return flag; ++ // Paper end - replace level propagator + } + boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority + +@@ -206,7 +280,7 @@ public abstract class DistanceManager { + + ticket1.setCreatedTick(this.ticketTickCounter); + if (ticket.getTicketLevel() < j) { +- this.ticketTracker.update(i, ticket.getTicketLevel(), true); ++ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator + } + + return ticket == ticket1; // CraftBukkit +@@ -250,7 +324,7 @@ public abstract class DistanceManager { + // Paper start - Chunk priority + int newLevel = getTicketLevelAt(arraysetsorted); + if (newLevel > oldLevel) { +- this.ticketTracker.update(i, newLevel, false); ++ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator + } + // Paper end + return removed; // CraftBukkit +@@ -564,7 +638,7 @@ public abstract class DistanceManager { + } + + if (flag) { +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); ++ this.updateTicketLevel(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue())); // Paper - replace ticket level propagator + } + + if (((SortedArraySet) entry.getValue()).isEmpty()) { +@@ -587,7 +661,7 @@ public abstract class DistanceManager { + SortedArraySet> tickets = entry.getValue(); + if (tickets.remove(target)) { + // copied from removeTicket +- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); ++ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator + + // can't use entry after it's removed + if (tickets.isEmpty()) { diff --git a/patches/server/0846-Furnace-RecipesUsed-API.patch b/patches/server/0846-Furnace-RecipesUsed-API.patch new file mode 100644 index 0000000000..2e6cf1c2fe --- /dev/null +++ b/patches/server/0846-Furnace-RecipesUsed-API.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 13 Jan 2022 15:20:47 -0800 +Subject: [PATCH] Furnace RecipesUsed API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +index 3da4616c904d47bbecae0d4cb6970496fbec9a8b..f49bb90e6c30dd928b352c867819b687e4557893 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java +@@ -93,5 +93,37 @@ public abstract class CraftFurnace extends + snapshot.cookSpeedMultiplier = multiplier; + snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot.recipeType, snapshot, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier + } ++ ++ @Override ++ public int getRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { ++ return this.getSnapshot().getRecipesUsed().getInt(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); ++ } ++ ++ @Override ++ public boolean hasRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { ++ return this.getSnapshot().getRecipesUsed().containsKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); ++ } ++ ++ @Override ++ public void setRecipeUsedCount(org.bukkit.inventory.CookingRecipe furnaceRecipe, int count) { ++ final net.minecraft.resources.ResourceLocation location = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe.getKey()); ++ java.util.Optional> nmsRecipe = (this.isPlaced() ? this.world.getHandle().getRecipeManager() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).byKey(location); ++ com.google.common.base.Preconditions.checkArgument(nmsRecipe.isPresent() && nmsRecipe.get() instanceof net.minecraft.world.item.crafting.AbstractCookingRecipe, furnaceRecipe.getKey() + " is not recognized as a valid and registered furnace recipe"); ++ if (count > 0) { ++ this.getSnapshot().getRecipesUsed().put(location, count); ++ } else { ++ this.getSnapshot().getRecipesUsed().removeInt(location); ++ } ++ } ++ ++ @Override ++ public void setRecipesUsed(java.util.Map, Integer> recipesUsed) { ++ this.getSnapshot().getRecipesUsed().clear(); ++ recipesUsed.forEach((recipe, integer) -> { ++ if (integer != null) { ++ this.setRecipeUsedCount(recipe, integer); ++ } ++ }); ++ } + // Paper end + } diff --git a/patches/server/0846-Replace-ticket-level-propagator.patch b/patches/server/0846-Replace-ticket-level-propagator.patch deleted file mode 100644 index 8959b46f3f..0000000000 --- a/patches/server/0846-Replace-ticket-level-propagator.patch +++ /dev/null @@ -1,258 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 21 Mar 2021 16:25:42 -0700 -Subject: [PATCH] Replace ticket level propagator - -Mojang's propagator is slow, and this isn't surprising -given it's built on the same utilities the vanilla light engine -is built on. The simple propagator I wrote is approximately 4x -faster when simulating player movement. For a long time timing -reports have shown this function take up significant tick, ( -approx 10% or more), and async sampling data shows the level -propagation alone takes up a significant amount. So this -should help with that. A big side effect is that mid-tick -will be more effective, since more time will be allocated -to actually processing chunk tasks vs the ticket level updates. - -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 06e4d3a02e0d1326b7029157856476db4ef3575e..f581a9f79b2357118d912a15344ff94df3b0c50e 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; - import net.minecraft.world.level.chunk.LevelChunk; - import org.slf4j.Logger; - -+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper - public abstract class DistanceManager { - - static final Logger LOGGER = LogUtils.getLogger(); -@@ -48,7 +49,7 @@ public abstract class DistanceManager { - private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); - public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); -- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); -+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator - public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used - private final TickingTracker tickingTicketsTracker = new TickingTracker(); - private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); -@@ -83,6 +84,46 @@ public abstract class DistanceManager { - this.chunkMap = chunkMap; // Paper - } - -+ // Paper start - replace ticket level propagator -+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { -+ @Override -+ protected void rehash(int newN) { -+ // no downsizing allowed -+ if (newN < this.n) { -+ return; -+ } -+ super.rehash(newN); -+ } -+ }; -+ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D( -+ (long coordinate, byte oldLevel, byte newLevel) -> { -+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); -+ } -+ ); -+ // function for converting between ticket levels and propagator levels and vice versa -+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects -+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator -+ // and the levels we get out of the propagator -+ -+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on -+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded -+ public static int convertBetweenTicketLevels(final int level) { -+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1; -+ } -+ -+ protected final int getPropagatedTicketLevel(final long coordinate) { -+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate)); -+ } -+ -+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { -+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) { -+ this.ticketLevelPropagator.removeSource(coordinate); -+ } else { -+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); -+ } -+ } -+ // Paper end - replace ticket level propagator -+ - protected void purgeStaleTickets() { - ++this.ticketTickCounter; - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -@@ -117,7 +158,7 @@ public abstract class DistanceManager { - } - - if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator - } - - if (((SortedArraySet) entry.getValue()).isEmpty()) { -@@ -140,61 +181,94 @@ public abstract class DistanceManager { - @Nullable - protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); - -+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator - public boolean runAllUpdates(ChunkMap chunkStorage) { - //this.f.a(); // Paper - no longer used - this.tickingTicketsTracker.runAllUpdates(); - org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper - this.playerTicketManager.runAllUpdates(); -- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); -- boolean flag = i != 0; -+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator - - if (flag) { - ; - } - -- // Paper start -- if (!this.pendingChunkUpdates.isEmpty()) { -- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority -- while(!this.pendingChunkUpdates.isEmpty()) { -- ChunkHolder remove = this.pendingChunkUpdates.remove(); -- remove.isUpdateQueued = false; -- remove.updateFutures(chunkStorage, this.mainThreadExecutor); -- } -- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority -- // Paper end -- return true; -- } else { -- if (!this.ticketsToRelease.isEmpty()) { -- LongIterator longiterator = this.ticketsToRelease.iterator(); -+ // Paper start - replace level propagator -+ ticket_update_loop: -+ while (!this.ticketLevelUpdates.isEmpty()) { -+ flag = true; - -- while (longiterator.hasNext()) { -- long j = longiterator.nextLong(); -+ boolean oldPolling = this.pollingPendingChunkUpdates; -+ this.pollingPendingChunkUpdates = true; -+ try { -+ for (java.util.Iterator iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ Long2IntMap.Entry entry = iterator.next(); -+ long key = entry.getLongKey(); -+ int newLevel = entry.getIntValue(); -+ ChunkHolder chunk = this.getChunk(key); -+ -+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) { -+ // not loaded and it shouldn't be loaded! -+ continue; -+ } - -- if (this.getTickets(j).stream().anyMatch((ticket) -> { -- return ticket.getType() == TicketType.PLAYER; -- })) { -- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j); -+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel(); - -- if (playerchunk == null) { -- throw new IllegalStateException(); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ continue; -+ } -+ -+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel); -+ } -+ -+ long recursiveCheck = ++this.ticketLevelUpdateCount; -+ while (!this.ticketLevelUpdates.isEmpty()) { -+ long key = this.ticketLevelUpdates.firstLongKey(); -+ int newLevel = this.ticketLevelUpdates.removeFirstInt(); -+ ChunkHolder chunk = this.getChunk(key); -+ -+ if (chunk == null) { -+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) { -+ throw new IllegalStateException("Expected chunk holder to be created"); - } -+ // not loaded and it shouldn't be loaded! -+ continue; -+ } - -- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); -+ int currentLevel = chunk.oldTicketLevel; - -- completablefuture.thenAccept((either) -> { -- this.mainThreadExecutor.execute(() -> { -- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -- }, j, false)); -- }); -- }); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ continue; -+ } -+ -+ chunk.updateFutures(chunkStorage, this.mainThreadExecutor); -+ if (recursiveCheck != this.ticketLevelUpdateCount) { -+ // back to the start, we must create player chunks and update the ticket level fields before -+ // processing the actual level updates -+ continue ticket_update_loop; - } - } - -- this.ticketsToRelease.clear(); -- } -+ for (;;) { -+ if (recursiveCheck != this.ticketLevelUpdateCount) { -+ continue ticket_update_loop; -+ } -+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll(); -+ if (pendingUpdate == null) { -+ break; -+ } - -- return flag; -+ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor); -+ } -+ } finally { -+ this.pollingPendingChunkUpdates = oldPolling; -+ } - } -+ -+ return flag; -+ // Paper end - replace level propagator - } - boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority - -@@ -206,7 +280,7 @@ public abstract class DistanceManager { - - ticket1.setCreatedTick(this.ticketTickCounter); - if (ticket.getTicketLevel() < j) { -- this.ticketTracker.update(i, ticket.getTicketLevel(), true); -+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator - } - - return ticket == ticket1; // CraftBukkit -@@ -250,7 +324,7 @@ public abstract class DistanceManager { - // Paper start - Chunk priority - int newLevel = getTicketLevelAt(arraysetsorted); - if (newLevel > oldLevel) { -- this.ticketTracker.update(i, newLevel, false); -+ this.updateTicketLevel(i, newLevel); // Paper // Paper - replace ticket level propagator - } - // Paper end - return removed; // CraftBukkit -@@ -564,7 +638,7 @@ public abstract class DistanceManager { - } - - if (flag) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -+ this.updateTicketLevel(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue())); // Paper - replace ticket level propagator - } - - if (((SortedArraySet) entry.getValue()).isEmpty()) { -@@ -587,7 +661,7 @@ public abstract class DistanceManager { - SortedArraySet> tickets = entry.getValue(); - if (tickets.remove(target)) { - // copied from removeTicket -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); -+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator - - // can't use entry after it's removed - if (tickets.isEmpty()) { diff --git a/patches/server/0847-Configurable-sculk-sensor-listener-range.patch b/patches/server/0847-Configurable-sculk-sensor-listener-range.patch new file mode 100644 index 0000000000..f19796285a --- /dev/null +++ b/patches/server/0847-Configurable-sculk-sensor-listener-range.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 19 Aug 2021 18:45:42 -0700 +Subject: [PATCH] Configurable sculk sensor listener range + + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +index b7b70a77615dde3f22a9153e17ec2d8edbdcdc7a..579a96d2d6c99d6587ea182f52b6df918f595f17 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +@@ -36,9 +36,11 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList + this.listener = listener; + }); + } ++ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) this.listener.listenerRange = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); // Paper + + } + ++ private static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper + @Override + protected void saveAdditional(CompoundTag nbt) { + super.saveAdditional(nbt); +@@ -46,6 +48,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList + VibrationListener.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(LOGGER::error).ifPresent((listenerNbt) -> { + nbt.put("listener", listenerNbt); + }); ++ if (this.listener.listenerRange != ((SculkSensorBlock) net.minecraft.world.level.block.Blocks.SCULK_SENSOR).getListenerRange()) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.listener.listenerRange); // Paper - only save if it's different from the default + } + + public VibrationListener getListener() { +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java +index 2733154f569002e426690dfcf362ff20da8cba72..34362768f38fb3122abcbd5e63fee38a631b9ee3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java +@@ -21,4 +21,16 @@ public class CraftSculkSensor extends CraftBlockEntityState 0, "Vibration listener range must be greater than 0"); ++ this.getSnapshot().getListener().listenerRange = range; ++ } ++ // Paper end + } diff --git a/patches/server/0847-Furnace-RecipesUsed-API.patch b/patches/server/0847-Furnace-RecipesUsed-API.patch deleted file mode 100644 index 2e6cf1c2fe..0000000000 --- a/patches/server/0847-Furnace-RecipesUsed-API.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 13 Jan 2022 15:20:47 -0800 -Subject: [PATCH] Furnace RecipesUsed API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -index 3da4616c904d47bbecae0d4cb6970496fbec9a8b..f49bb90e6c30dd928b352c867819b687e4557893 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java -@@ -93,5 +93,37 @@ public abstract class CraftFurnace extends - snapshot.cookSpeedMultiplier = multiplier; - snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot.recipeType, snapshot, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier - } -+ -+ @Override -+ public int getRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { -+ return this.getSnapshot().getRecipesUsed().getInt(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); -+ } -+ -+ @Override -+ public boolean hasRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) { -+ return this.getSnapshot().getRecipesUsed().containsKey(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe)); -+ } -+ -+ @Override -+ public void setRecipeUsedCount(org.bukkit.inventory.CookingRecipe furnaceRecipe, int count) { -+ final net.minecraft.resources.ResourceLocation location = org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(furnaceRecipe.getKey()); -+ java.util.Optional> nmsRecipe = (this.isPlaced() ? this.world.getHandle().getRecipeManager() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).byKey(location); -+ com.google.common.base.Preconditions.checkArgument(nmsRecipe.isPresent() && nmsRecipe.get() instanceof net.minecraft.world.item.crafting.AbstractCookingRecipe, furnaceRecipe.getKey() + " is not recognized as a valid and registered furnace recipe"); -+ if (count > 0) { -+ this.getSnapshot().getRecipesUsed().put(location, count); -+ } else { -+ this.getSnapshot().getRecipesUsed().removeInt(location); -+ } -+ } -+ -+ @Override -+ public void setRecipesUsed(java.util.Map, Integer> recipesUsed) { -+ this.getSnapshot().getRecipesUsed().clear(); -+ recipesUsed.forEach((recipe, integer) -> { -+ if (integer != null) { -+ this.setRecipeUsedCount(recipe, integer); -+ } -+ }); -+ } - // Paper end - } diff --git a/patches/server/0848-Add-missing-block-data-mins-and-maxes.patch b/patches/server/0848-Add-missing-block-data-mins-and-maxes.patch new file mode 100644 index 0000000000..83ba5f6bc1 --- /dev/null +++ b/patches/server/0848-Add-missing-block-data-mins-and-maxes.patch @@ -0,0 +1,147 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 16 Oct 2021 22:57:31 -0700 +Subject: [PATCH] Add missing block data mins and maxes + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java +index 83f86725c00f0e175cb46c7e27705ca777f413ba..24d16825c10edfed6d22e8e37ddb9fd804b716c4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java +@@ -31,6 +31,12 @@ public final class CraftCandle extends org.bukkit.craftbukkit.block.data.CraftBl + public int getMaximumCandles() { + return getMax(CraftCandle.CANDLES); + } ++ // Paper start ++ @Override ++ public int getMinimumCandles() { ++ return getMin(CraftCandle.CANDLES); ++ } ++ // Paper end + + // org.bukkit.craftbukkit.block.data.CraftLightable + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java +index 780b6a29592571f4a730a858734256f69519cca7..ef97e77b25562a8aed35d68d42ced4825d43a29d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java +@@ -31,4 +31,11 @@ public final class CraftComposter extends org.bukkit.craftbukkit.block.data.Craf + public int getMaximumLevel() { + return getMax(CraftComposter.LEVEL); + } ++ ++ // Paper start ++ @Override ++ public int getMinimumLevel() { ++ return getMin(CraftComposter.LEVEL); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java +index f083cf727e7fd55f0749e85e3d034b5606121110..e40cda2f23d63e9d2029a8c8818103b6eeb6a925 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java +@@ -31,4 +31,11 @@ public final class CraftFluids extends org.bukkit.craftbukkit.block.data.CraftBl + public int getMaximumLevel() { + return getMax(CraftFluids.LEVEL); + } ++ ++ // Paper start ++ @Override ++ public int getMinimumLevel() { ++ return getMin(CraftFluids.LEVEL); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java +index 0d08c81dd8582ef4f259f0e0db88e1b85d79f2a1..5b96ec73bf7bd4d90ce77cfe8ffec82580b20d2b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java +@@ -31,4 +31,11 @@ public final class CraftLayeredCauldron extends org.bukkit.craftbukkit.block.dat + public int getMaximumLevel() { + return getMax(CraftLayeredCauldron.LEVEL); + } ++ ++ // Paper start ++ @Override ++ public int getMinimumLevel() { ++ return getMin(CraftLayeredCauldron.LEVEL); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java +index 2775bcc7d61806764a121d45621a8928f5e301e7..8cb0f9bf8a2e264e089a0278d5fb4b157844f6e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java +@@ -51,4 +51,16 @@ public final class CraftLeaves extends org.bukkit.craftbukkit.block.data.CraftBl + public void setWaterlogged(boolean waterlogged) { + set(CraftLeaves.WATERLOGGED, waterlogged); + } ++ ++ // Paper start ++ @Override ++ public int getMaximumDistance() { ++ return getMax(CraftLeaves.DISTANCE); ++ } ++ ++ @Override ++ public int getMinimumDistance() { ++ return getMin(CraftLeaves.DISTANCE); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java +index de882af105fae1166aced908cfe45b826a07f418..0d430382a05dfc0802a2569816c5ec876a053f16 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java +@@ -32,6 +32,13 @@ public final class CraftLight extends org.bukkit.craftbukkit.block.data.CraftBlo + return getMax(CraftLight.LEVEL); + } + ++ // Paper start ++ @Override ++ public int getMinimumLevel() { ++ return getMin(CraftLight.LEVEL); ++ } ++ // Paper end ++ + // org.bukkit.craftbukkit.block.data.CraftWaterlogged + + private static final net.minecraft.world.level.block.state.properties.BooleanProperty WATERLOGGED = getBoolean(net.minecraft.world.level.block.LightBlock.class, "waterlogged"); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java +index f070c35833b33ad2d7cfa629d8702a95e9bc6d04..5a021dc1931d35cbe235ab399aa98f3c7e93ded3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java +@@ -51,4 +51,16 @@ public final class CraftMangroveLeaves extends org.bukkit.craftbukkit.block.data + public void setWaterlogged(boolean waterlogged) { + set(CraftMangroveLeaves.WATERLOGGED, waterlogged); + } ++ ++ // Paper start ++ @Override ++ public int getMinimumDistance() { ++ return getMin(CraftMangroveLeaves.DISTANCE); ++ } ++ ++ @Override ++ public int getMaximumDistance() { ++ return getMax(CraftMangroveLeaves.DISTANCE); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java +index c6bd91bdf6bf64701ffc69619174cc3b43b72d88..c6289306f0f933b67ff1f6db63ef976df7aa5438 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java +@@ -31,4 +31,11 @@ public final class CraftPowderSnowCauldron extends org.bukkit.craftbukkit.block. + public int getMaximumLevel() { + return getMax(CraftPowderSnowCauldron.LEVEL); + } ++ ++ // Paper start ++ @Override ++ public int getMinimumLevel() { ++ return getMin(CraftPowderSnowCauldron.LEVEL); ++ } ++ // Paper end + } diff --git a/patches/server/0848-Configurable-sculk-sensor-listener-range.patch b/patches/server/0848-Configurable-sculk-sensor-listener-range.patch deleted file mode 100644 index f19796285a..0000000000 --- a/patches/server/0848-Configurable-sculk-sensor-listener-range.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 19 Aug 2021 18:45:42 -0700 -Subject: [PATCH] Configurable sculk sensor listener range - - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -index b7b70a77615dde3f22a9153e17ec2d8edbdcdc7a..579a96d2d6c99d6587ea182f52b6df918f595f17 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java -@@ -36,9 +36,11 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList - this.listener = listener; - }); - } -+ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) this.listener.listenerRange = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); // Paper - - } - -+ private static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - @Override - protected void saveAdditional(CompoundTag nbt) { - super.saveAdditional(nbt); -@@ -46,6 +48,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList - VibrationListener.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(LOGGER::error).ifPresent((listenerNbt) -> { - nbt.put("listener", listenerNbt); - }); -+ if (this.listener.listenerRange != ((SculkSensorBlock) net.minecraft.world.level.block.Blocks.SCULK_SENSOR).getListenerRange()) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.listener.listenerRange); // Paper - only save if it's different from the default - } - - public VibrationListener getListener() { -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -index 2733154f569002e426690dfcf362ff20da8cba72..34362768f38fb3122abcbd5e63fee38a631b9ee3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java -@@ -21,4 +21,16 @@ public class CraftSculkSensor extends CraftBlockEntityState 0, "Vibration listener range must be greater than 0"); -+ this.getSnapshot().getListener().listenerRange = range; -+ } -+ // Paper end - } diff --git a/patches/server/0849-Add-missing-block-data-mins-and-maxes.patch b/patches/server/0849-Add-missing-block-data-mins-and-maxes.patch deleted file mode 100644 index 83ba5f6bc1..0000000000 --- a/patches/server/0849-Add-missing-block-data-mins-and-maxes.patch +++ /dev/null @@ -1,147 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 16 Oct 2021 22:57:31 -0700 -Subject: [PATCH] Add missing block data mins and maxes - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java -index 83f86725c00f0e175cb46c7e27705ca777f413ba..24d16825c10edfed6d22e8e37ddb9fd804b716c4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java -@@ -31,6 +31,12 @@ public final class CraftCandle extends org.bukkit.craftbukkit.block.data.CraftBl - public int getMaximumCandles() { - return getMax(CraftCandle.CANDLES); - } -+ // Paper start -+ @Override -+ public int getMinimumCandles() { -+ return getMin(CraftCandle.CANDLES); -+ } -+ // Paper end - - // org.bukkit.craftbukkit.block.data.CraftLightable - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java -index 780b6a29592571f4a730a858734256f69519cca7..ef97e77b25562a8aed35d68d42ced4825d43a29d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java -@@ -31,4 +31,11 @@ public final class CraftComposter extends org.bukkit.craftbukkit.block.data.Craf - public int getMaximumLevel() { - return getMax(CraftComposter.LEVEL); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftComposter.LEVEL); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java -index f083cf727e7fd55f0749e85e3d034b5606121110..e40cda2f23d63e9d2029a8c8818103b6eeb6a925 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java -@@ -31,4 +31,11 @@ public final class CraftFluids extends org.bukkit.craftbukkit.block.data.CraftBl - public int getMaximumLevel() { - return getMax(CraftFluids.LEVEL); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftFluids.LEVEL); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java -index 0d08c81dd8582ef4f259f0e0db88e1b85d79f2a1..5b96ec73bf7bd4d90ce77cfe8ffec82580b20d2b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java -@@ -31,4 +31,11 @@ public final class CraftLayeredCauldron extends org.bukkit.craftbukkit.block.dat - public int getMaximumLevel() { - return getMax(CraftLayeredCauldron.LEVEL); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftLayeredCauldron.LEVEL); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java -index 2775bcc7d61806764a121d45621a8928f5e301e7..8cb0f9bf8a2e264e089a0278d5fb4b157844f6e4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java -@@ -51,4 +51,16 @@ public final class CraftLeaves extends org.bukkit.craftbukkit.block.data.CraftBl - public void setWaterlogged(boolean waterlogged) { - set(CraftLeaves.WATERLOGGED, waterlogged); - } -+ -+ // Paper start -+ @Override -+ public int getMaximumDistance() { -+ return getMax(CraftLeaves.DISTANCE); -+ } -+ -+ @Override -+ public int getMinimumDistance() { -+ return getMin(CraftLeaves.DISTANCE); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java -index de882af105fae1166aced908cfe45b826a07f418..0d430382a05dfc0802a2569816c5ec876a053f16 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java -@@ -32,6 +32,13 @@ public final class CraftLight extends org.bukkit.craftbukkit.block.data.CraftBlo - return getMax(CraftLight.LEVEL); - } - -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftLight.LEVEL); -+ } -+ // Paper end -+ - // org.bukkit.craftbukkit.block.data.CraftWaterlogged - - private static final net.minecraft.world.level.block.state.properties.BooleanProperty WATERLOGGED = getBoolean(net.minecraft.world.level.block.LightBlock.class, "waterlogged"); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java -index f070c35833b33ad2d7cfa629d8702a95e9bc6d04..5a021dc1931d35cbe235ab399aa98f3c7e93ded3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java -@@ -51,4 +51,16 @@ public final class CraftMangroveLeaves extends org.bukkit.craftbukkit.block.data - public void setWaterlogged(boolean waterlogged) { - set(CraftMangroveLeaves.WATERLOGGED, waterlogged); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumDistance() { -+ return getMin(CraftMangroveLeaves.DISTANCE); -+ } -+ -+ @Override -+ public int getMaximumDistance() { -+ return getMax(CraftMangroveLeaves.DISTANCE); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java -index c6bd91bdf6bf64701ffc69619174cc3b43b72d88..c6289306f0f933b67ff1f6db63ef976df7aa5438 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPowderSnowCauldron.java -@@ -31,4 +31,11 @@ public final class CraftPowderSnowCauldron extends org.bukkit.craftbukkit.block. - public int getMaximumLevel() { - return getMax(CraftPowderSnowCauldron.LEVEL); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftPowderSnowCauldron.LEVEL); -+ } -+ // Paper end - } diff --git a/patches/server/0849-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0849-Option-to-have-default-CustomSpawners-in-custom-worl.patch new file mode 100644 index 0000000000..b1bb942ed4 --- /dev/null +++ b/patches/server/0849-Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 19 Feb 2022 20:15:41 -0800 +Subject: [PATCH] Option to have default CustomSpawners in custom worlds + +By default, only LevelStem's that specifically match the ResourceKey for +OVERWORLD will have the 5 (currently) impls of CustomSpawner (for +phantoms, wandering traders, etc.). This adds an option to instead of +just looking at the LevelStem key, look at the DimensionType key which +is one level below that. Defaults to off to keep vanilla behavior. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 98654c77b2925228458fae447510ec1d6b217370..d53972b69322e03d7e8054a2dcdbdb963055988c 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -589,7 +589,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryHolder.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(worlddimension.typeHolder().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { ++ spawners = list; ++ } else { ++ spawners = Collections.emptyList(); ++ } ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ // Paper end + } + + worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0850-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0850-Option-to-have-default-CustomSpawners-in-custom-worl.patch deleted file mode 100644 index 24a1373012..0000000000 --- a/patches/server/0850-Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 19 Feb 2022 20:15:41 -0800 -Subject: [PATCH] Option to have default CustomSpawners in custom worlds - -By default, only LevelStem's that specifically match the ResourceKey for -OVERWORLD will have the 5 (currently) impls of CustomSpawner (for -phantoms, wandering traders, etc.). This adds an option to instead of -just looking at the LevelStem key, look at the DimensionType key which -is one level below that. Defaults to off to keep vanilla behavior. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1444391ded50ec10e9bfeb073b01c885c429b8bd..faa380a0119d5827735a45104beef9880992240a 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -589,7 +589,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop spawners; -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryHolder.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(worlddimension.typeHolder().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { -+ spawners = list; -+ } else { -+ spawners = Collections.emptyList(); -+ } -+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); -+ // Paper end - } - - worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified()); diff --git a/patches/server/0850-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0850-Put-world-into-worldlist-before-initing-the-world.patch new file mode 100644 index 0000000000..7f81f976a5 --- /dev/null +++ b/patches/server/0850-Put-world-into-worldlist-before-initing-the-world.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 22 Feb 2022 14:21:35 -0800 +Subject: [PATCH] Put world into worldlist before initing the world + +Some parts of legacy conversion will need the overworld +to get the legacy structure data storage + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index d53972b69322e03d7e8054a2dcdbdb963055988c..5f3592f570fae791f2bd55d05d02c8bb5ecc0f85 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -601,9 +601,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 23 Dec 2021 23:59:12 -0500 +Subject: [PATCH] Fix Entity Position Desync + +If entities were teleported in the first tick it would not be send to the client. + +This excludes hanging entities, as this fix caused problematic behavior due to them having their own +position field. + +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 919758363c7b703cb200582768e68c97ce5c807a..3b144c820531122eb37d41be06c182b5f5dc0724 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -158,7 +158,7 @@ public class ServerEntity { + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; + +- if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { ++ if (!(this.entity instanceof net.minecraft.world.entity.decoration.HangingEntity) || this.tickCount > 0 || this.entity instanceof AbstractArrow) { // Paper - Always update position + long k = this.positionCodec.encodeX(vec3d); + long l = this.positionCodec.encodeY(vec3d); + long i1 = this.positionCodec.encodeZ(vec3d); diff --git a/patches/server/0851-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0851-Put-world-into-worldlist-before-initing-the-world.patch deleted file mode 100644 index af8347ac93..0000000000 --- a/patches/server/0851-Put-world-into-worldlist-before-initing-the-world.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Tue, 22 Feb 2022 14:21:35 -0800 -Subject: [PATCH] Put world into worldlist before initing the world - -Some parts of legacy conversion will need the overworld -to get the legacy structure data storage - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index d53972b69322e03d7e8054a2dcdbdb963055988c..5f3592f570fae791f2bd55d05d02c8bb5ecc0f85 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -601,9 +601,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Thu, 7 Oct 2021 14:34:55 -0700 +Subject: [PATCH] Custom Potion Mixes + + +diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6b0bed550763f34e18c9e92f9a47ec0c945b2c8b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java +@@ -0,0 +1,13 @@ ++package io.papermc.paper.potion; ++ ++import net.minecraft.world.item.ItemStack; ++import net.minecraft.world.item.crafting.Ingredient; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.inventory.CraftRecipe; ++ ++public record PaperPotionMix(ItemStack result, Ingredient input, Ingredient ingredient) { ++ ++ public PaperPotionMix(PotionMix potionMix) { ++ this(CraftItemStack.asNMSCopy(potionMix.getResult()), CraftRecipe.toIngredient(potionMix.getInput(), true), CraftRecipe.toIngredient(potionMix.getIngredient(), true)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 5f3592f570fae791f2bd55d05d02c8bb5ecc0f85..df08b7afcf19ce694a87c25e8589c0c72521c5db 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -2049,6 +2049,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> POTION_MIXES = Lists.newArrayList(); + private static final List> CONTAINER_MIXES = Lists.newArrayList(); ++ private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper + private static final List ALLOWED_CONTAINERS = Lists.newArrayList(); + private static final Predicate ALLOWED_CONTAINER = (stack) -> { + for(Ingredient ingredient : ALLOWED_CONTAINERS) { +@@ -26,7 +27,7 @@ public class PotionBrewing { + }; + + public static boolean isIngredient(ItemStack stack) { +- return isContainerIngredient(stack) || isPotionIngredient(stack); ++ return isContainerIngredient(stack) || isPotionIngredient(stack) || isCustomIngredient(stack); // Paper + } + + protected static boolean isContainerIngredient(ItemStack stack) { +@@ -66,6 +67,11 @@ public class PotionBrewing { + } + + public static boolean hasMix(ItemStack input, ItemStack ingredient) { ++ // Paper start ++ if (hasCustomMix(input, ingredient)) { ++ return true; ++ } ++ // Paper end + if (!ALLOWED_CONTAINER.test(input)) { + return false; + } else { +@@ -103,6 +109,13 @@ public class PotionBrewing { + + public static ItemStack mix(ItemStack ingredient, ItemStack input) { + if (!input.isEmpty()) { ++ // Paper start ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return mix.result().copy(); ++ } ++ } ++ // Paper end + Potion potion = PotionUtils.getPotion(input); + Item item = input.getItem(); + int i = 0; +@@ -127,6 +140,54 @@ public class PotionBrewing { + return input; + } + ++ // Paper start ++ public static boolean isCustomIngredient(ItemStack stack) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.ingredient().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static boolean isCustomInput(ItemStack stack) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(stack)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ private static boolean hasCustomMix(ItemStack input, ItemStack ingredient) { ++ for (var mix : CUSTOM_MIXES.values()) { ++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static void addPotionMix(io.papermc.paper.potion.PotionMix mix) { ++ if (CUSTOM_MIXES.containsKey(mix.getKey())) { ++ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); ++ } ++ CUSTOM_MIXES.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); ++ } ++ ++ public static boolean removePotionMix(org.bukkit.NamespacedKey key) { ++ return CUSTOM_MIXES.remove(key) != null; ++ } ++ ++ public static void reload() { ++ POTION_MIXES.clear(); ++ CONTAINER_MIXES.clear(); ++ ALLOWED_CONTAINERS.clear(); ++ CUSTOM_MIXES.clear(); ++ bootStrap(); ++ } ++ // Paper end ++ + public static void bootStrap() { + addContainer(Items.POTION); + addContainer(Items.SPLASH_POTION); +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +index 3d688e334c7287f41460bd866bfd1155e8bb55d2..55006724ccec9f3de828ec18693728e9741ff65f 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +@@ -335,7 +335,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { +- return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty()); ++ return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty()); // Paper + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 5b13c0b6e7c582e4858515889a46ac510ed29c74..669fb0206a09377a8682325bc4bd744d285791f6 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -285,6 +285,7 @@ public final class CraftServer implements Server { + private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper + public static Exception excessiveVelEx; // Paper - Velocity warnings + private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper ++ private final CraftPotionBrewer potionBrewer = new CraftPotionBrewer(); // Paper + + static { + ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); +@@ -311,7 +312,7 @@ public final class CraftServer implements Server { + Enchantments.SHARPNESS.getClass(); + org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations(); + +- Potion.setPotionBrewer(new CraftPotionBrewer()); ++ Potion.setPotionBrewer(potionBrewer); // Paper + MobEffects.BLINDNESS.getClass(); + PotionEffectType.stopAcceptingRegistrations(); + // Ugly hack :( +@@ -2896,5 +2897,10 @@ public final class CraftServer implements Server { + return datapackManager; + } + ++ @Override ++ public CraftPotionBrewer getPotionBrewer() { ++ return this.potionBrewer; ++ } ++ + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +index 10ace7bb17c36bfefc584a6322841ab6ea4c866f..71486c08db28caf89f2366e082f6f6fab5609b71 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +@@ -13,6 +13,11 @@ public interface CraftRecipe extends Recipe { + void addToCraftingManager(); + + default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper start ++ return toIngredient(bukkit, requireNotEmpty); ++ } ++ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { ++ // Paper end + Ingredient stack; + + if (bukkit == null) { +diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +index 8fdc9a3bb2f1b6bdc6c2c96f8ade7e9cd88ea4e0..a0b0c64b819b8f713eeea78210e276664e30e66e 100644 +--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java ++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java +@@ -49,4 +49,21 @@ public class CraftPotionBrewer implements PotionBrewer { + public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) { + return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier); + } ++ ++ // Paper start ++ @Override ++ public void addPotionMix(io.papermc.paper.potion.PotionMix potionMix) { ++ net.minecraft.world.item.alchemy.PotionBrewing.addPotionMix(potionMix); ++ } ++ ++ @Override ++ public void removePotionMix(org.bukkit.NamespacedKey key) { ++ net.minecraft.world.item.alchemy.PotionBrewing.removePotionMix(key); ++ } ++ ++ @Override ++ public void resetPotionMixes() { ++ net.minecraft.world.item.alchemy.PotionBrewing.reload(); ++ } ++ // Paper end + } diff --git a/patches/server/0852-Fix-Entity-Position-Desync.patch b/patches/server/0852-Fix-Entity-Position-Desync.patch deleted file mode 100644 index f4b0a0dc54..0000000000 --- a/patches/server/0852-Fix-Entity-Position-Desync.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 23 Dec 2021 23:59:12 -0500 -Subject: [PATCH] Fix Entity Position Desync - -If entities were teleported in the first tick it would not be send to the client. - -This excludes hanging entities, as this fix caused problematic behavior due to them having their own -position field. - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 919758363c7b703cb200582768e68c97ce5c807a..3b144c820531122eb37d41be06c182b5f5dc0724 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -158,7 +158,7 @@ public class ServerEntity { - boolean flag2 = flag1 || this.tickCount % 60 == 0; - boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; - -- if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { -+ if (!(this.entity instanceof net.minecraft.world.entity.decoration.HangingEntity) || this.tickCount > 0 || this.entity instanceof AbstractArrow) { // Paper - Always update position - long k = this.positionCodec.encodeX(vec3d); - long l = this.positionCodec.encodeY(vec3d); - long i1 = this.positionCodec.encodeZ(vec3d); diff --git a/patches/server/0853-Custom-Potion-Mixes.patch b/patches/server/0853-Custom-Potion-Mixes.patch deleted file mode 100644 index 8336b16432..0000000000 --- a/patches/server/0853-Custom-Potion-Mixes.patch +++ /dev/null @@ -1,239 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 7 Oct 2021 14:34:55 -0700 -Subject: [PATCH] Custom Potion Mixes - - -diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6b0bed550763f34e18c9e92f9a47ec0c945b2c8b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java -@@ -0,0 +1,13 @@ -+package io.papermc.paper.potion; -+ -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.crafting.Ingredient; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.inventory.CraftRecipe; -+ -+public record PaperPotionMix(ItemStack result, Ingredient input, Ingredient ingredient) { -+ -+ public PaperPotionMix(PotionMix potionMix) { -+ this(CraftItemStack.asNMSCopy(potionMix.getResult()), CraftRecipe.toIngredient(potionMix.getInput(), true), CraftRecipe.toIngredient(potionMix.getIngredient(), true)); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 5f3592f570fae791f2bd55d05d02c8bb5ecc0f85..df08b7afcf19ce694a87c25e8589c0c72521c5db 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2049,6 +2049,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> POTION_MIXES = Lists.newArrayList(); - private static final List> CONTAINER_MIXES = Lists.newArrayList(); -+ private static final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap CUSTOM_MIXES = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - private static final List ALLOWED_CONTAINERS = Lists.newArrayList(); - private static final Predicate ALLOWED_CONTAINER = (stack) -> { - for(Ingredient ingredient : ALLOWED_CONTAINERS) { -@@ -26,7 +27,7 @@ public class PotionBrewing { - }; - - public static boolean isIngredient(ItemStack stack) { -- return isContainerIngredient(stack) || isPotionIngredient(stack); -+ return isContainerIngredient(stack) || isPotionIngredient(stack) || isCustomIngredient(stack); // Paper - } - - protected static boolean isContainerIngredient(ItemStack stack) { -@@ -66,6 +67,11 @@ public class PotionBrewing { - } - - public static boolean hasMix(ItemStack input, ItemStack ingredient) { -+ // Paper start -+ if (hasCustomMix(input, ingredient)) { -+ return true; -+ } -+ // Paper end - if (!ALLOWED_CONTAINER.test(input)) { - return false; - } else { -@@ -103,6 +109,13 @@ public class PotionBrewing { - - public static ItemStack mix(ItemStack ingredient, ItemStack input) { - if (!input.isEmpty()) { -+ // Paper start -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return mix.result().copy(); -+ } -+ } -+ // Paper end - Potion potion = PotionUtils.getPotion(input); - Item item = input.getItem(); - int i = 0; -@@ -127,6 +140,54 @@ public class PotionBrewing { - return input; - } - -+ // Paper start -+ public static boolean isCustomIngredient(ItemStack stack) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.ingredient().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static boolean isCustomInput(ItemStack stack) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(stack)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ private static boolean hasCustomMix(ItemStack input, ItemStack ingredient) { -+ for (var mix : CUSTOM_MIXES.values()) { -+ if (mix.input().test(input) && mix.ingredient().test(ingredient)) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static void addPotionMix(io.papermc.paper.potion.PotionMix mix) { -+ if (CUSTOM_MIXES.containsKey(mix.getKey())) { -+ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey()); -+ } -+ CUSTOM_MIXES.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix)); -+ } -+ -+ public static boolean removePotionMix(org.bukkit.NamespacedKey key) { -+ return CUSTOM_MIXES.remove(key) != null; -+ } -+ -+ public static void reload() { -+ POTION_MIXES.clear(); -+ CONTAINER_MIXES.clear(); -+ ALLOWED_CONTAINERS.clear(); -+ CUSTOM_MIXES.clear(); -+ bootStrap(); -+ } -+ // Paper end -+ - public static void bootStrap() { - addContainer(Items.POTION); - addContainer(Items.SPLASH_POTION); -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -index 3d688e334c7287f41460bd866bfd1155e8bb55d2..55006724ccec9f3de828ec18693728e9741ff65f 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java -@@ -335,7 +335,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements - - @Override - public boolean canPlaceItem(int slot, ItemStack stack) { -- return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty()); -+ return slot == 3 ? PotionBrewing.isIngredient(stack) : (slot == 4 ? stack.is(Items.BLAZE_POWDER) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || PotionBrewing.isCustomInput(stack)) && this.getItem(slot).isEmpty()); // Paper - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d5ee84449de7fe6dc5a2a6a2044a93f14901ea80..65e5f05d112f715c61698fbcc26883816cb8b6a7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -285,6 +285,7 @@ public final class CraftServer implements Server { - private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper - public static Exception excessiveVelEx; // Paper - Velocity warnings - private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper -+ private final CraftPotionBrewer potionBrewer = new CraftPotionBrewer(); // Paper - - static { - ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); -@@ -311,7 +312,7 @@ public final class CraftServer implements Server { - Enchantments.SHARPNESS.getClass(); - org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations(); - -- Potion.setPotionBrewer(new CraftPotionBrewer()); -+ Potion.setPotionBrewer(potionBrewer); // Paper - MobEffects.BLINDNESS.getClass(); - PotionEffectType.stopAcceptingRegistrations(); - // Ugly hack :( -@@ -2896,5 +2897,10 @@ public final class CraftServer implements Server { - return datapackManager; - } - -+ @Override -+ public CraftPotionBrewer getPotionBrewer() { -+ return this.potionBrewer; -+ } -+ - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 10ace7bb17c36bfefc584a6322841ab6ea4c866f..71486c08db28caf89f2366e082f6f6fab5609b71 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -13,6 +13,11 @@ public interface CraftRecipe extends Recipe { - void addToCraftingManager(); - - default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper start -+ return toIngredient(bukkit, requireNotEmpty); -+ } -+ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) { -+ // Paper end - Ingredient stack; - - if (bukkit == null) { -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -index 8fdc9a3bb2f1b6bdc6c2c96f8ade7e9cd88ea4e0..a0b0c64b819b8f713eeea78210e276664e30e66e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionBrewer.java -@@ -49,4 +49,21 @@ public class CraftPotionBrewer implements PotionBrewer { - public PotionEffect createEffect(PotionEffectType potion, int duration, int amplifier) { - return new PotionEffect(potion, potion.isInstant() ? 1 : (int) (duration * potion.getDurationModifier()), amplifier); - } -+ -+ // Paper start -+ @Override -+ public void addPotionMix(io.papermc.paper.potion.PotionMix potionMix) { -+ net.minecraft.world.item.alchemy.PotionBrewing.addPotionMix(potionMix); -+ } -+ -+ @Override -+ public void removePotionMix(org.bukkit.NamespacedKey key) { -+ net.minecraft.world.item.alchemy.PotionBrewing.removePotionMix(key); -+ } -+ -+ @Override -+ public void resetPotionMixes() { -+ net.minecraft.world.item.alchemy.PotionBrewing.reload(); -+ } -+ // Paper end - } diff --git a/patches/server/0853-Replace-player-chunk-loader-system.patch b/patches/server/0853-Replace-player-chunk-loader-system.patch new file mode 100644 index 0000000000..807c90830c --- /dev/null +++ b/patches/server/0853-Replace-player-chunk-loader-system.patch @@ -0,0 +1,2270 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 24 Jan 2021 20:27:32 -0800 +Subject: [PATCH] Replace player chunk loader system + +The old one has undebuggable problems. Rewriting seems +the most sensible option. + +This new player chunk manager will also strictly rate limit +chunk sends so that netty threads do not get overloaded, whether +it be from the anti-xray logic or the compression itself. + +Chunk loading is also rate limited in the same manner, so this +will result in a maximum responsiveness for change. + +Config: +``` +chunk-loading: + min-load-radius: 2 + max-concurrent-sends: 2 + autoconfig-send-distance: true + target-player-chunk-send-rate: 100.0 + global-max-chunk-send-rate: -1 + enable-frustum-priority: false + global-max-chunk-load-rate: -1.0 + player-max-concurrent-loads: 25.0 + global-max-concurrent-loads: 500.0 +``` + +min-load-radius - The radius of chunks around a player that +are not throttled for loading. The number of chunks +affected is actually the configured value plus one as this +config controls the chunks the client will be able to render. + +max-concurrent-sends - The maximum number of chunks that +can be queued to send at any given time. Low values +are generally going to solve server-sided networking +bottlenecks like anti-xray and chunk compression. Client +side networking is unlikely to be helped (i.e this wont help +people running off McDonald's wifi). + +autoconfig-send-distance - Whether to try to use the client's +view distance for the send view distance in the server. In the +case that no plugin has explicitly set the send distance and +the client view distance is less-than the server's send distance, +the client's view distance will be used. This will not affect +tick view distance or no-tick view distance. + +target-player-chunk-send-rate - The maximum chunk send rate +an individual player will have. -1 means no limit + +global-max-chunk-send-rate - The maximum chunk send rate for +the whole server. -1 means no limit + +enable-frustum-priority - Whether chunks in front of a player +are prioritised to load/send first. Disabled by default +because the client can bug out due to the out of order +chunk sending. + +global-max-chunk-load-rate - The maximum chunk load rate +for the whole server. -1 means no limit + +player-max-concurrent-loads and global-max-concurrent-loads +The maximum number of concurrent loads for the server is +determined by the number of players on the server multiplied by the +`player-max-concurrent-loads`. It is then limited to +whatever `global-max-concurrent-loads` is configured to. + +diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java +index 78280fb3bcd8d792a58ece6d735e0824ea4be536..06bff37e4c1fddd3be6343049a66787c63fb420c 100644 +--- a/src/main/java/co/aikar/timings/TimingsExport.java ++++ b/src/main/java/co/aikar/timings/TimingsExport.java +@@ -162,7 +162,11 @@ public class TimingsExport extends Thread { + pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { + return pair(rule, world.getWorld().getGameRuleValue(rule)); + })), +- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()) ++ // Paper start - replace chunk loader system ++ pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()), ++ pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()), ++ pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance()) ++ // Paper end - replace chunk loader system + )); + })); + +diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..b53402903eb6845df361daf6b05a668608ad7b63 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +@@ -0,0 +1,1128 @@ ++package io.papermc.paper.chunk; ++ ++import com.destroystokyo.paper.util.misc.PlayerAreaMap; ++import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; ++import io.papermc.paper.configuration.GlobalConfiguration; ++import io.papermc.paper.util.CoordinateUtils; ++import io.papermc.paper.util.IntervalledCounter; ++import io.papermc.paper.util.TickThread; ++import it.unimi.dsi.fastutil.longs.LongOpenHashSet; ++import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; ++import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; ++import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; ++import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; ++import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; ++import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.*; ++import net.minecraft.util.Mth; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.LevelChunk; ++import org.apache.commons.lang3.mutable.MutableObject; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.TreeSet; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++public final class PlayerChunkLoader { ++ ++ public static final int MIN_VIEW_DISTANCE = 2; ++ public static final int MAX_VIEW_DISTANCE = 32; ++ ++ public static final int TICK_TICKET_LEVEL = 31; ++ public static final int LOADED_TICKET_LEVEL = 33; ++ ++ public static int getTickViewDistance(final Player player) { ++ return getTickViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getTickViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level; ++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); ++ if (data == null) { ++ return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance(); ++ } ++ return data.getTargetTickViewDistance(); ++ } ++ ++ public static int getLoadViewDistance(final Player player) { ++ return getLoadViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getLoadViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level; ++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); ++ if (data == null) { ++ return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance(); ++ } ++ return data.getLoadDistance(); ++ } ++ ++ public static int getSendViewDistance(final Player player) { ++ return getSendViewDistance(((CraftPlayer)player).getHandle()); ++ } ++ ++ public static int getSendViewDistance(final ServerPlayer player) { ++ final ServerLevel level = (ServerLevel)player.level; ++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); ++ if (data == null) { ++ return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance(); ++ } ++ return data.getTargetSendViewDistance(); ++ } ++ ++ protected final ChunkMap chunkMap; ++ protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f); ++ protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f); ++ ++ protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { ++ if (p1 == p2) { ++ return 0; ++ } ++ ++ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst(); ++ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst(); ++ ++ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); ++ ++ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad); ++ ++ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) { ++ return priorityCompare; ++ } ++ ++ if (lastLoadTimeCompare != 0) { ++ return lastLoadTimeCompare; ++ } ++ ++ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); ++ ++ if (idCompare != 0) { ++ return idCompare; ++ } ++ ++ // last resort ++ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); ++ }); ++ ++ protected final TreeSet chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { ++ if (p1 == p2) { ++ return 0; ++ } ++ ++ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget); ++ if (timeCompare != 0) { ++ return timeCompare; ++ } ++ ++ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); ++ ++ if (idCompare != 0) { ++ return idCompare; ++ } ++ ++ // last resort ++ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); ++ }); ++ ++ ++ // no throttling is applied below this VD for loading ++ ++ /** ++ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded. ++ */ ++ public final PlayerAreaMap broadcastMap; ++ ++ /** ++ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded. ++ */ ++ public final PlayerAreaMap loadMap; ++ ++ /** ++ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus, ++ * this map is always representing the chunks we are actually going to load. ++ */ ++ public final PlayerAreaMap loadTicketCleanup; ++ ++ /** ++ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen. ++ */ ++ public final PlayerAreaMap tickMap; ++ ++ /** ++ * -1 if defaulting to [load distance], else always in [2, load distance] ++ */ ++ protected int rawSendDistance = -1; ++ ++ /** ++ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1] ++ */ ++ protected int rawLoadDistance = -1; ++ ++ /** ++ * Never -1, always in [2, 32] ++ */ ++ protected int rawTickDistance = -1; ++ ++ // methods to bridge for API ++ ++ public int getTargetTickViewDistance() { ++ return this.getTickDistance(); ++ } ++ ++ public void setTargetTickViewDistance(final int distance) { ++ this.setTickDistance(distance); ++ } ++ ++ public int getTargetNoTickViewDistance() { ++ return this.getLoadDistance() - 1; ++ } ++ ++ public void setTargetNoTickViewDistance(final int distance) { ++ this.setLoadDistance(distance == -1 ? -1 : distance + 1); ++ } ++ ++ public int getTargetSendDistance() { ++ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance; ++ } ++ ++ public void setTargetSendDistance(final int distance) { ++ this.setSendDistance(distance); ++ } ++ ++ // internal methods ++ ++ public int getSendDistance() { ++ final int loadDistance = this.getLoadDistance(); ++ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance); ++ } ++ ++ public void setSendDistance(final int distance) { ++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Send distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); ++ } ++ this.rawSendDistance = distance; ++ } ++ ++ public int getLoadDistance() { ++ final int tickDistance = this.getTickDistance(); ++ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance); ++ } ++ ++ public void setLoadDistance(final int distance) { ++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Load distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); ++ } ++ this.rawLoadDistance = distance; ++ } ++ ++ public int getTickDistance() { ++ return this.rawTickDistance; ++ } ++ ++ public void setTickDistance(final int distance) { ++ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) { ++ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + ", got: " + distance); ++ } ++ this.rawTickDistance = distance; ++ } ++ ++ /* ++ Players have 3 different types of view distance: ++ 1. Sending view distance ++ 2. Loading view distance ++ 3. Ticking view distance ++ ++ But for configuration purposes (and API) there are: ++ 1. No-tick view distance ++ 2. Tick view distance ++ 3. Broadcast view distance ++ ++ These aren't always the same as the types we represent internally. ++ ++ Loading view distance is always max(no-tick + 1, tick + 1) ++ - no-tick has 1 added because clients need an extra radius to render chunks ++ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking ++ ++ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means ++ it loads chunks in radius load-view-distance + 1. ++ ++ The maximum value for send view distance is the load view distance. API can set it lower. ++ */ ++ ++ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets pooledHashSets) { ++ this.chunkMap = chunkMap; ++ this.broadcastMap = new PlayerAreaMap(pooledHashSets, ++ null, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ); ++ }); ++ this.loadMap = new PlayerAreaMap(pooledHashSets, ++ null, ++ (ServerPlayer 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; ++ } ++ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ)); ++ }); ++ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets, ++ null, ++ (ServerPlayer 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; ++ } ++ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); ++ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); ++ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) { ++ --PlayerChunkLoader.this.concurrentChunkLoads; ++ } ++ }); ++ this.tickMap = new PlayerAreaMap(pooledHashSets, ++ (ServerPlayer 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; ++ } ++ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); ++ if (chunk == null || !chunk.areNeighboursLoaded(2)) { ++ return; ++ } ++ ++ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); ++ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); ++ }, ++ (ServerPlayer 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; ++ } ++ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); ++ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); ++ }); ++ } ++ ++ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); ++ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); ++ ++ public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) { ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); ++ ++ return playersInSendRange != null; ++ } ++ ++ public void onChunkPostProcessing(final int chunkX, final int chunkZ) { ++ this.onChunkSendReady(chunkX, chunkZ); ++ } ++ ++ private boolean chunkNeedsPostProcessing(final int chunkX, final int chunkZ) { ++ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); ++ ++ if (chunk == null) { ++ return false; ++ } ++ ++ final LevelChunk levelChunk = chunk.getSendingChunk(); ++ ++ return levelChunk != null && !levelChunk.isPostProcessingDone; ++ } ++ ++ // rets whether the chunk is at a loaded stage that is ready to be sent to players ++ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) { ++ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); ++ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); ++ ++ if (chunk == null) { ++ return false; ++ } ++ ++ final LevelChunk levelChunk = chunk.getSendingChunk(); ++ ++ return levelChunk != null && levelChunk.isPostProcessingDone && this.isTargetedForPlayerLoad.contains(key); ++ } ++ ++ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { ++ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); ++ } ++ ++ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerLoaderData data = this.playerMap.get(player); ++ if (data == null) { ++ return false; ++ } ++ ++ return data.hasSentChunk(chunkX, chunkZ); ++ } ++ ++ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerLoaderData data = this.playerMap.get(player); ++ if (data == null) { ++ return false; ++ } ++ ++ final boolean center = data.hasSentChunk(chunkX, chunkZ); ++ if (!center) { ++ return false; ++ } ++ ++ return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) && ++ data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1)); ++ } ++ ++ protected int getMaxConcurrentChunkSends() { ++ return GlobalConfiguration.get().chunkLoading.maxConcurrentSends; ++ } ++ ++ protected int getMaxChunkLoads() { ++ double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads; ++ double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads; ++ return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max)); ++ } ++ ++ protected long getTargetSendPerPlayerAddend() { ++ return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate); ++ } ++ ++ protected long getMaxSendAddend() { ++ return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate); ++ } ++ ++ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) { ++ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); ++ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); ++ } ++ ++ public void onChunkSendReady(final int chunkX, final int chunkZ) { ++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); ++ ++ if (playersInSendRange == null) { ++ return; ++ } ++ ++ final Object[] rawData = playersInSendRange.getBackingSet(); ++ for (int i = 0, len = rawData.length; i < len; ++i) { ++ final Object raw = rawData[i]; ++ ++ if (!(raw instanceof ServerPlayer)) { ++ continue; ++ } ++ this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ); ++ } ++ } ++ ++ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerLoaderData data = this.playerMap.get(player); ++ ++ if (data == null) { ++ return; ++ } ++ ++ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) { ++ // if we don't have player tickets, then the load logic will pick this up and queue to send ++ return; ++ } ++ ++ if (!data.chunksToBeSent.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ // don't queue to send, we don't want the chunk ++ return; ++ } ++ ++ final long playerPos = this.broadcastMap.getLastCoordinate(player); ++ final int playerChunkX = CoordinateUtils.getChunkX(playerPos); ++ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos); ++ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ); ++ ++ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0); ++ data.sendQueue.add(holder); ++ } ++ ++ public void onChunkLoad(final int chunkX, final int chunkZ) { ++ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ --this.concurrentChunkLoads; ++ } ++ } ++ ++ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) { ++ final PlayerLoaderData data = this.playerMap.get(player); ++ ++ if (data == null) { ++ return; ++ } ++ ++ data.unloadChunk(chunkX, chunkZ); ++ } ++ ++ public void addPlayer(final ServerPlayer player) { ++ TickThread.ensureTickThread("Cannot add player async"); ++ if (!player.isRealPlayer) { ++ return; ++ } ++ final PlayerLoaderData data = new PlayerLoaderData(player, this); ++ if (this.playerMap.putIfAbsent(player, data) == null) { ++ data.update(); ++ } ++ } ++ ++ public void removePlayer(final ServerPlayer player) { ++ TickThread.ensureTickThread("Cannot remove player async"); ++ if (!player.isRealPlayer) { ++ return; ++ } ++ ++ final PlayerLoaderData loaderData = this.playerMap.remove(player); ++ if (loaderData == null) { ++ return; ++ } ++ loaderData.remove(); ++ this.chunkLoadQueue.remove(loaderData); ++ this.chunkSendQueue.remove(loaderData); ++ this.chunkSendWaitQueue.remove(loaderData); ++ synchronized (this.sendingChunkCounts) { ++ final int count = this.sendingChunkCounts.removeInt(loaderData); ++ if (count != 0) { ++ concurrentChunkSends.getAndAdd(-count); ++ } ++ } ++ } ++ ++ public void updatePlayer(final ServerPlayer player) { ++ TickThread.ensureTickThread("Cannot update player async"); ++ if (!player.isRealPlayer) { ++ return; ++ } ++ final PlayerLoaderData loaderData = this.playerMap.get(player); ++ if (loaderData != null) { ++ loaderData.update(); ++ } ++ } ++ ++ public PlayerLoaderData getData(final ServerPlayer player) { ++ return this.playerMap.get(player); ++ } ++ ++ public void tick() { ++ TickThread.ensureTickThread("Cannot tick async"); ++ for (final PlayerLoaderData data : this.playerMap.values()) { ++ data.update(); ++ } ++ this.tickMidTick(); ++ } ++ ++ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); ++ protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); ++ private static long nextChunkSend; ++ private void trySendChunks() { ++ final long time = System.nanoTime(); ++ if (time < nextChunkSend) { ++ return; ++ } ++ // drain entries from wait queue ++ while (!this.chunkSendWaitQueue.isEmpty()) { ++ final PlayerLoaderData data = this.chunkSendWaitQueue.first(); ++ ++ if (data.nextChunkSendTarget > time) { ++ break; ++ } ++ ++ this.chunkSendWaitQueue.pollFirst(); ++ ++ this.chunkSendQueue.add(data); ++ } ++ ++ if (this.chunkSendQueue.isEmpty()) { ++ return; ++ } ++ ++ final int maxSends = this.getMaxConcurrentChunkSends(); ++ final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time; ++ for (;;) { ++ if (this.chunkSendQueue.isEmpty()) { ++ break; ++ } ++ final int currSends = concurrentChunkSends.get(); ++ if (currSends >= maxSends) { ++ break; ++ } ++ ++ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { ++ continue; ++ } ++ ++ // send chunk ++ ++ final PlayerLoaderData data = this.chunkSendQueue.removeFirst(); ++ ++ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); ++ if (queuedSend == null) { ++ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease ++ // stop iterating over players who have nothing to send ++ if (this.chunkSendQueue.isEmpty()) { ++ // nothing left ++ break; ++ } ++ continue; ++ } ++ ++ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { ++ throw new IllegalStateException(); ++ } ++ ++ data.nextChunkSendTarget = nextPlayerDeadline; ++ this.chunkSendWaitQueue.add(data); ++ ++ synchronized (this.sendingChunkCounts) { ++ this.sendingChunkCounts.addTo(data, 1); ++ } ++ ++ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { ++ synchronized (this.sendingChunkCounts) { ++ final int count = this.sendingChunkCounts.getInt(data); ++ if (count == 0) { ++ // disconnected, so we don't need to decrement: it will be decremented for us ++ return; ++ } ++ if (count == 1) { ++ this.sendingChunkCounts.removeInt(data); ++ } else { ++ this.sendingChunkCounts.put(data, count - 1); ++ } ++ } ++ ++ concurrentChunkSends.getAndDecrement(); ++ }); ++ ++ nextChunkSend = this.getMaxSendAddend() + time; ++ if (time < nextChunkSend) { ++ break; ++ } ++ } ++ } ++ ++ protected int concurrentChunkLoads; ++ // this interval prevents bursting a lot of chunk loads ++ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms ++ // this interval ensures the rate is kept between ticks correctly ++ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms ++ private void tryLoadChunks() { ++ if (this.chunkLoadQueue.isEmpty()) { ++ return; ++ } ++ ++ final int maxLoads = this.getMaxChunkLoads(); ++ final long time = System.nanoTime(); ++ boolean updatedCounters = false; ++ for (;;) { ++ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); ++ ++ data.lastChunkLoad = time; ++ ++ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); ++ if (queuedLoad == null) { ++ if (this.chunkLoadQueue.isEmpty()) { ++ break; ++ } ++ continue; ++ } ++ ++ if (!updatedCounters) { ++ updatedCounters = true; ++ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); ++ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); ++ data.ticketAdditionCounterShort.updateCurrentTime(time); ++ data.ticketAdditionCounterLong.updateCurrentTime(time); ++ } ++ ++ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { ++ // already loaded! ++ data.loadQueue.pollFirst(); // already loaded so we just skip ++ this.chunkLoadQueue.add(data); ++ ++ // ensure the chunk is queued to send ++ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); ++ continue; ++ } ++ ++ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); ++ ++ final double priority = queuedLoad.priority; ++ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present. ++ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the ++ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they ++ // aren't required, it bypasses the limiter system. ++ boolean unloadedTargetChunk = false; ++ unloaded_check: ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final int offX = queuedLoad.chunkX + dx; ++ final int offZ = queuedLoad.chunkZ + dz; ++ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) { ++ unloadedTargetChunk = true; ++ break unloaded_check; ++ } ++ } ++ } ++ if (unloadedTargetChunk && priority >= 0.0) { ++ // priority >= 0.0 implies rate limited chunks ++ ++ final int currentChunkLoads = this.concurrentChunkLoads; ++ if (currentChunkLoads >= maxLoads || (GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate)) ++ || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) { ++ // don't poll, we didn't load it ++ this.chunkLoadQueue.add(data); ++ break; ++ } ++ } ++ ++ // can only poll after we decide to load ++ data.loadQueue.pollFirst(); ++ ++ // now that we've polled we can re-add to load queue ++ this.chunkLoadQueue.add(data); ++ ++ // add necessary tickets to load chunk up to send-ready ++ for (int dz = -1; dz <= 1; ++dz) { ++ for (int dx = -1; dx <= 1; ++dx) { ++ final int offX = queuedLoad.chunkX + dx; ++ final int offZ = queuedLoad.chunkZ + dz; ++ final ChunkPos chunkPos = new ChunkPos(offX, offZ); ++ ++ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); ++ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) { ++ continue; ++ } ++ ++ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) { ++ // won't reach here if unloadedTargetChunk is false ++ ++this.concurrentChunkLoads; ++ TICKET_ADDITION_COUNTER_SHORT.addTime(time); ++ TICKET_ADDITION_COUNTER_LONG.addTime(time); ++ data.ticketAdditionCounterShort.addTime(time); ++ data.ticketAdditionCounterLong.addTime(time); ++ } ++ } ++ } ++ ++ // mark that we've added tickets here ++ this.isTargetedForPlayerLoad.add(chunkKey); ++ ++ // it's possible all we needed was the player tickets to queue up the send. ++ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { ++ // yup, all we needed. ++ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); ++ } else if (this.chunkNeedsPostProcessing(queuedLoad.chunkX, queuedLoad.chunkZ)) { ++ // requires post processing ++ this.chunkMap.mainThreadExecutor.execute(() -> { ++ final long key = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); ++ final ChunkHolder holder = PlayerChunkLoader.this.chunkMap.getVisibleChunkIfPresent(key); ++ ++ if (holder == null) { ++ return; ++ } ++ ++ final LevelChunk chunk = holder.getSendingChunk(); ++ ++ if (chunk != null && !chunk.isPostProcessingDone) { ++ chunk.postProcessGeneration(); ++ } ++ }); ++ } ++ } ++ } ++ ++ public void tickMidTick() { ++ // try to send more chunks ++ this.trySendChunks(); ++ ++ // try to queue more chunks to load ++ this.tryLoadChunks(); ++ } ++ ++ static final class ChunkPriorityHolder { ++ public final int chunkX; ++ public final int chunkZ; ++ public final int manhattanDistanceToPlayer; ++ public final double priority; ++ ++ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) { ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; ++ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer; ++ this.priority = priority; ++ } ++ } ++ ++ public static final class PlayerLoaderData { ++ ++ protected static final float FOV = 110.0f; ++ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0; ++ ++ // Player max sprint speed is approximately 8m/s ++ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0); ++ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f; ++ ++ protected double lastLocX = Double.NEGATIVE_INFINITY; ++ protected double lastLocZ = Double.NEGATIVE_INFINITY; ++ ++ protected int lastChunkX = Integer.MIN_VALUE; ++ protected int lastChunkZ = Integer.MIN_VALUE; ++ ++ // this is corrected so that 0 is along the positive x-axis ++ protected float lastYaw = Float.NEGATIVE_INFINITY; ++ ++ protected int lastSendDistance = Integer.MIN_VALUE; ++ protected int lastLoadDistance = Integer.MIN_VALUE; ++ protected int lastTickDistance = Integer.MIN_VALUE; ++ protected boolean usingLookingPriority; ++ ++ protected final ServerPlayer player; ++ protected final PlayerChunkLoader loader; ++ ++ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field ++ // in a comparator! ++ protected final ArrayDeque loadQueue = new ArrayDeque<>(); ++ protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); ++ protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet(); ++ ++ protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { ++ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer); ++ if (distanceCompare != 0) { ++ return distanceCompare; ++ } ++ ++ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX); ++ if (coordinateXCompare != 0) { ++ return coordinateXCompare; ++ } ++ ++ return Integer.compare(p1.chunkZ, p2.chunkZ); ++ }); ++ ++ protected int sendViewDistance = -1; ++ protected int loadViewDistance = -1; ++ protected int tickViewDistance = -1; ++ ++ protected long nextChunkSendTarget; ++ ++ // this interval prevents bursting a lot of chunk loads ++ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms ++ // this ensures the rate is kept between ticks correctly ++ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms ++ ++ public long lastChunkLoad; ++ ++ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { ++ this.player = player; ++ this.loader = loader; ++ } ++ ++ // these view distance methods are for api ++ public int getTargetSendViewDistance() { ++ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; ++ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); ++ final int clientViewDistance = this.getClientViewDistance(); ++ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); ++ return sendViewDistance; ++ } ++ ++ public void setTargetSendViewDistance(final int distance) { ++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { ++ throw new IllegalArgumentException("Send view distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); ++ } ++ this.sendViewDistance = distance; ++ } ++ ++ public int getTargetNoTickViewDistance() { ++ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1; ++ } ++ ++ public void setTargetNoTickViewDistance(final int distance) { ++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { ++ throw new IllegalArgumentException("Simulation distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); ++ } ++ this.loadViewDistance = distance == -1 ? -1 : distance + 1; ++ } ++ ++ public int getTargetTickViewDistance() { ++ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; ++ } ++ ++ public void setTargetTickViewDistance(final int distance) { ++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { ++ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); ++ } ++ this.tickViewDistance = distance; ++ } ++ ++ protected int getLoadDistance() { ++ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; ++ ++ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); ++ } ++ ++ public boolean hasSentChunk(final int chunkX, final int chunkZ) { ++ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ ++ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) { ++ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, ++ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded ++ this.player.connection.connection.execute(onChunkSend); ++ } else { ++ throw new IllegalStateException(); ++ } ++ } ++ ++ public void unloadChunk(final int chunkX, final int chunkZ) { ++ if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { ++ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, ++ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded ++ } ++ } ++ ++ protected static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, ++ final int sendRadius) { ++ // expect sendRadius to be = 1 + target viewable radius ++ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius); ++ } ++ ++ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point ++ final double p2x, final double p2z, // triangle point ++ final double p3x, final double p3z, // triangle point ++ ++ final double targetX, final double targetZ) { // point ++ // from barycentric coordinates: ++ // targetX = a*p1x + b*p2x + c*p3x ++ // targetZ = a*p1z + b*p2z + c*p3z ++ // 1.0 = a*1.0 + b*1.0 + c*1.0 ++ // where a, b, c >= 0.0 ++ // so, if any of a, b, c are less-than zero then there is no intersection. ++ ++ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z)) ++ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d ++ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d ++ // c = 1.0 - a - b ++ ++ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z); ++ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d; ++ ++ if (a < 0.0 || a > 1.0) { ++ return false; ++ } ++ ++ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d; ++ if (b < 0.0 || b > 1.0) { ++ return false; ++ } ++ ++ final double c = 1.0 - a - b; ++ ++ return c >= 0.0 && c <= 1.0; ++ } ++ ++ public void remove() { ++ this.loader.broadcastMap.remove(this.player); ++ this.loader.loadMap.remove(this.player); ++ this.loader.loadTicketCleanup.remove(this.player); ++ this.loader.tickMap.remove(this.player); ++ } ++ ++ protected int getClientViewDistance() { ++ return this.player.clientViewDistance == null ? -1 : Math.max(0, this.player.clientViewDistance.intValue()); ++ } ++ ++ public void update() { ++ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; ++ // load view cannot be less-than tick view + 1 ++ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); ++ // send view cannot be greater-than load view ++ final int clientViewDistance = this.getClientViewDistance(); ++ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); ++ ++ final double posX = this.player.getX(); ++ final double posZ = this.player.getZ(); ++ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis ++ ++ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them. ++ final boolean useLookPriority = GlobalConfiguration.get().chunkLoading.enableFrustumPriority && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD || ++ this.player.getAbilities().flying); ++ ++ // make sure we're in the send queue ++ this.loader.chunkSendWaitQueue.add(this); ++ ++ if ( ++ // has view distance stayed the same? ++ sendViewDistance == this.lastSendDistance ++ && loadViewDistance == this.lastLoadDistance ++ && tickViewDistance == this.lastTickDistance ++ ++ && (this.usingLookingPriority ? ( ++ // has our block stayed the same (this also accounts for chunk change)? ++ Mth.floor(this.lastLocX) == Mth.floor(posX) ++ && Mth.floor(this.lastLocZ) == Mth.floor(posZ) ++ ) : ( ++ // has our chunk stayed the same ++ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) ++ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4) ++ )) ++ ++ // has our decision about look priority changed? ++ && this.usingLookingPriority == useLookPriority ++ ++ // if we are currently using look priority, has our yaw stayed within recalc threshold? ++ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD) ++ ) { ++ // nothing we care about changed, so we're not re-calculating ++ return; ++ } ++ ++ final int centerChunkX = Mth.floor(posX) >> 4; ++ final int centerChunkZ = Mth.floor(posZ) >> 4; ++ ++ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ); ++ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance); ++ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance); ++ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1); ++ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance); ++ ++ if (sendViewDistance != this.lastSendDistance) { ++ // update the view radius for client ++ // note that this should be after the map calls because the client wont expect unload calls not in its VD ++ // and it's possible we decreased VD here ++ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance)); ++ } ++ if (tickViewDistance != this.lastTickDistance) { ++ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance)); ++ } ++ ++ this.lastLocX = posX; ++ this.lastLocZ = posZ; ++ this.lastYaw = yaw; ++ this.lastSendDistance = sendViewDistance; ++ this.lastLoadDistance = loadViewDistance; ++ this.lastTickDistance = tickViewDistance; ++ this.usingLookingPriority = useLookPriority; ++ ++ this.lastChunkX = centerChunkX; ++ this.lastChunkZ = centerChunkZ; ++ ++ // points for player "view" triangle: ++ ++ // obviously, the player pos is a vertex ++ final double p1x = posX; ++ final double p1z = posZ; ++ ++ // to the left of the looking direction ++ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector ++ + p1x; // offset vector ++ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector ++ + p1z; // offset vector ++ ++ // to the right of the looking direction ++ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector ++ + p1x; // offset vector ++ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector ++ + p1z; // offset vector ++ ++ // now that we have all of our points, we can recalculate the load queue ++ ++ final List loadQueue = new ArrayList<>(); ++ ++ // clear send queue, we are re-sorting ++ this.sendQueue.clear(); ++ // clear chunk want set, vd/position might have changed ++ this.chunksToBeSent.clear(); ++ ++ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance); ++ ++ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) { ++ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) { ++ final int chunkX = dx + centerChunkX; ++ final int chunkZ = dz + centerChunkZ; ++ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); ++ final boolean sendChunk = squareDistance <= sendViewDistance && wantChunkLoaded(centerChunkX, centerChunkZ, chunkX, chunkZ, sendViewDistance); ++ ++ if (this.hasSentChunk(chunkX, chunkZ)) { ++ // already sent (which means it is also loaded) ++ if (!sendChunk) { ++ // have sent the chunk, but don't want it anymore ++ // unload it now ++ this.unloadChunk(chunkX, chunkZ); ++ } ++ continue; ++ } ++ ++ final boolean loadChunk = squareDistance <= loadViewDistance; ++ ++ final boolean prioritised = useLookPriority && triangleIntersects( ++ // prioritisation triangle ++ p1x, p1z, p2x, p2z, p3x, p3z, ++ ++ // center of chunk ++ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8) ++ ); ++ ++ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); ++ ++ final double priority; ++ ++ if (squareDistance <= GlobalConfiguration.get().chunkLoading.minLoadRadius) { ++ // priority should be negative, and we also want to order it from center outwards ++ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest ++ priority = -((2 * GlobalConfiguration.get().chunkLoading.minLoadRadius + 1) - manhattanDistance); ++ } else { ++ if (prioritised) { ++ // we don't prioritise these chunks above others because we also want to make sure some chunks ++ // will be loaded if the player changes direction ++ priority = (double)manhattanDistance / 6.0; ++ } else { ++ priority = (double)manhattanDistance; ++ } ++ } ++ ++ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority); ++ ++ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) { ++ if (loadChunk) { ++ loadQueue.add(holder); ++ if (sendChunk) { ++ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ } ++ } else { ++ // loaded but not sent: so queue it! ++ if (sendChunk) { ++ this.sendQueue.add(holder); ++ } ++ } ++ } ++ } ++ ++ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { ++ return Double.compare(p1.priority, p2.priority); ++ }); ++ ++ // we're modifying loadQueue, must remove ++ this.loader.chunkLoadQueue.remove(this); ++ ++ this.loadQueue.clear(); ++ this.loadQueue.addAll(loadQueue); ++ ++ // must re-add ++ this.loader.chunkLoadQueue.add(this); ++ ++ // update the chunk center ++ // this must be done last so that the client does not ignore any of our unload chunk packets ++ if (needsChunkCenterUpdate) { ++ this.player.connection.send(new ClientboundSetChunkCacheCenterPacket(centerChunkX, centerChunkZ)); ++ } ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java +index 66afd752fd7d327e141d49b477f07e1ff3645d02..2a26d03fba2f3b37f176be9e47954ef9a6cd7b3e 100644 +--- a/src/main/java/net/minecraft/network/Connection.java ++++ b/src/main/java/net/minecraft/network/Connection.java +@@ -100,6 +100,28 @@ public class Connection extends SimpleChannelInboundHandler> { + public boolean queueImmunity = false; + public ConnectionProtocol protocol; + // Paper end ++ // Paper start - add pending task queue ++ private final Queue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ public void execute(final Runnable run) { ++ if (this.channel == null || !this.channel.isRegistered()) { ++ run.run(); ++ return; ++ } ++ final boolean queue = !this.queue.isEmpty(); ++ if (!queue) { ++ this.channel.eventLoop().execute(run); ++ } else { ++ this.pendingTasks.add(run); ++ if (this.queue.isEmpty()) { ++ // something flushed async, dump tasks now ++ Runnable r; ++ while ((r = this.pendingTasks.poll()) != null) { ++ this.channel.eventLoop().execute(r); ++ } ++ } ++ } ++ } ++ // Paper end - add pending task queue + + // Paper start - allow controlled flushing + volatile boolean canFlush = true; +@@ -488,6 +510,7 @@ public class Connection extends SimpleChannelInboundHandler> { + return false; + } + private boolean processQueue() { ++ try { // Paper - add pending task queue + if (this.queue.isEmpty()) return true; + // Paper start - make only one flush call per sendPacketQueue() call + final boolean needsFlush = this.canFlush; +@@ -519,6 +542,12 @@ public class Connection extends SimpleChannelInboundHandler> { + } + } + return true; ++ } finally { // Paper start - add pending task queue ++ Runnable r; ++ while ((r = this.pendingTasks.poll()) != null) { ++ this.channel.eventLoop().execute(r); ++ } ++ } // Paper end - add pending task queue + } + // Paper end + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index b575d73ae0ff2e4f09a6a1f6fb061ca3da2cedf1..6939ef9b1fe782980e77c351d8a385a573d6a8e6 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -636,7 +636,8 @@ public final class MCUtil { + }); + + worldData.addProperty("name", world.getWorld().getName()); +- worldData.addProperty("view-distance", world.spigotConfig.viewDistance); ++ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system ++ worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system + worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); + worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); + worldData.addProperty("visible-chunk-count", allChunks.size()); +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 73712d6b9c828427d4c066c6d8672534575f3793..a041161dee9a857d43c83fb677dba7e90a6a5d24 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -76,6 +76,17 @@ public class ChunkHolder { + public ServerLevel getWorld() { return chunkMap.level; } // Paper + boolean isUpdateQueued = false; // Paper + private final ChunkMap chunkMap; // Paper ++ // Paper start - no-tick view distance ++ public final LevelChunk getSendingChunk() { ++ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used ++ // in Chunk's neighbour callback ++ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); ++ if (ret != null && ret.areNeighboursLoaded(1)) { ++ return ret; ++ } ++ return null; ++ } ++ // Paper end - no-tick view distance + + // Paper start + public void onChunkAdd() { +@@ -273,7 +284,7 @@ public class ChunkHolder { + + public void blockChanged(BlockPos pos) { + if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks +- LevelChunk chunk = this.getTickingChunk(); ++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance + + if (chunk != null) { + int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); +@@ -289,14 +300,15 @@ public class ChunkHolder { + } + + public void sectionLightChanged(LightLayer lightType, int y) { +- Either either = (Either) this.getFutureIfPresent(ChunkStatus.FEATURES).getNow(null); // CraftBukkit - decompile error ++ // Paper start - no-tick view distance + +- if (either != null) { +- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error ++ if (true) { ++ ChunkAccess ichunkaccess = this.getAvailableChunkNow(); + + if (ichunkaccess != null) { + ichunkaccess.setUnsaved(true); +- LevelChunk chunk = this.getTickingChunk(); ++ LevelChunk chunk = this.getSendingChunk(); ++ // Paper end - no-tick view distance + + if (chunk != null) { + int j = this.lightEngine.getMinLightSection(); +@@ -399,9 +411,28 @@ public class ChunkHolder { + } + + public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { +- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> { +- entityplayer.connection.send(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.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos); ++ if (players == null) { ++ return; ++ } ++ ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { ++ continue; ++ } ++ player.connection.send(packet); ++ } ++ // Paper end - per player view distance + } + + public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index a5e74d30045a171f5ed66a115fbd429e9ab412af..47657f20652a80f50a2e46207c9c05d1a12111b4 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -218,6 +218,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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 ChunkMap#anyPlayerCloseEnoughForSpawning ++ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader + // Paper start - use distance map to optimise tracker + public static boolean isLegacyTrackingEntity(Entity entity) { + return entity.isLegacyTrackingEntity; +@@ -237,6 +238,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(ServerPlayer player) { ++ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Paper start - use distance map to optimise entity tracker +@@ -244,7 +246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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())); ++ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances + } + // Paper end - use distance map to optimise entity tracker + // Note: players need to be explicitly added to distance maps before they can be updated +@@ -274,6 +276,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMobDistanceMap.remove(player); + } + // Paper end - per player mob spawning ++ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader + } + + void updateMaps(ServerPlayer player) { +@@ -285,7 +288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + 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())); ++ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances + } + // Paper end - use distance map to optimise entity tracker + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning +@@ -295,6 +298,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); + } + // Paper end - per player mob spawning ++ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader + } + // Paper end + // Paper start +@@ -1447,11 +1451,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + completablefuture1.thenAcceptAsync((either) -> { + either.ifLeft((chunk) -> { + this.tickingGenerated.getAndIncrement(); +- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass +- +- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { +- this.playerLoadedChunk(entityplayer, mutableobject, chunk); +- }); ++ // Paper - no-tick view distance - moved to Chunk neighbour update + }); + }, (runnable) -> { + this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); +@@ -1620,33 +1620,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int k = this.viewDistance; + + this.viewDistance = j; +- this.distanceManager.updatePlayerTickets(this.viewDistance + 1); +- Iterator objectiterator = net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).iterator(); // Paper +- +- while (objectiterator.hasNext()) { +- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); +- ChunkPos chunkcoordintpair = playerchunk.getPos(); +- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass +- +- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { +- SectionPos sectionposition = entityplayer.getLastSectionPos(); +- boolean flag = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), k); +- boolean flag1 = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), this.viewDistance); +- +- this.updateChunkTracking(entityplayer, chunkcoordintpair, mutableobject, flag, flag1); +- }); +- } ++ this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system + } + + } + +- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass ++ // Paper start - replace player loader system ++ public void setTickViewDistance(int distance) { ++ this.playerChunkManager.setTickDistance(distance); ++ } ++ // Paper end - replace player loader system ++ ++ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass // Paper - public + if (player.level == this.level) { + if (newWithinViewDistance && !oldWithinViewDistance) { + ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); + + if (playerchunk != null) { +- LevelChunk chunk = playerchunk.getTickingChunk(); ++ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system + + if (chunk != null) { + this.playerLoadedChunk(player, packet, chunk); +@@ -1677,7 +1668,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + void dumpChunks(Writer writer) throws IOException { + CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); +- TickingTracker tickingtracker = this.distanceManager.tickingTracker(); ++ // Paper - replace loader system + Iterator objectbidirectionaliterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper + + while (objectbidirectionaliterator.hasNext()) { +@@ -1693,7 +1684,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit - decompile error + csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { + return chunk.getBlockEntities().size(); +- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { ++ }).orElse(0), "Use ticket level", -1000, optional1.map((chunk) -> { // Paper - replace loader system + return chunk.getBlockTicks().count(); + }).orElse(0), optional1.map((chunk) -> { + return chunk.getFluidTicks().count(); +@@ -1927,15 +1918,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.removePlayerFromDistanceMaps(player); // Paper - distance maps + } + +- for (int k = i - this.viewDistance - 1; k <= i + this.viewDistance + 1; ++k) { +- for (int l = j - this.viewDistance - 1; l <= j + this.viewDistance + 1; ++l) { +- if (ChunkMap.isChunkInRange(k, l, i, j, this.viewDistance)) { +- ChunkPos chunkcoordintpair = new ChunkPos(k, l); +- +- this.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added); +- } +- } +- } ++ // Paper - handled by player chunk loader + + } + +@@ -1943,7 +1926,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + SectionPos sectionposition = SectionPos.of((EntityAccess) player); + + player.setLastSectionPos(sectionposition); +- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); ++ //player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - handled by player chunk loader + return sectionposition; + } + +@@ -1988,65 +1971,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int k1; + int l1; + +- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) { +- k1 = Math.min(i, i1) - this.viewDistance - 1; +- l1 = Math.min(j, j1) - this.viewDistance - 1; +- int i2 = Math.max(i, i1) + this.viewDistance + 1; +- int j2 = Math.max(j, j1) + this.viewDistance + 1; +- +- for (int k2 = k1; k2 <= i2; ++k2) { +- for (int l2 = l1; l2 <= j2; ++l2) { +- boolean flag3 = ChunkMap.isChunkInRange(k2, l2, i1, j1, this.viewDistance); +- boolean flag4 = ChunkMap.isChunkInRange(k2, l2, i, j, this.viewDistance); +- +- this.updateChunkTracking(player, new ChunkPos(k2, l2), new MutableObject(), flag3, flag4); +- } +- } +- } else { +- boolean flag5; +- boolean flag6; +- +- for (k1 = i1 - this.viewDistance - 1; k1 <= i1 + this.viewDistance + 1; ++k1) { +- for (l1 = j1 - this.viewDistance - 1; l1 <= j1 + this.viewDistance + 1; ++l1) { +- if (ChunkMap.isChunkInRange(k1, l1, i1, j1, this.viewDistance)) { +- flag5 = true; +- flag6 = false; +- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), true, false); +- } +- } +- } +- +- for (k1 = i - this.viewDistance - 1; k1 <= i + this.viewDistance + 1; ++k1) { +- for (l1 = j - this.viewDistance - 1; l1 <= j + this.viewDistance + 1; ++l1) { +- if (ChunkMap.isChunkInRange(k1, l1, i, j, this.viewDistance)) { +- flag5 = false; +- flag6 = true; +- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), false, true); +- } +- } +- } +- } ++ // Paper - replaced by PlayerChunkLoader + + this.updateMaps(player); // Paper - distance maps ++ this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately + + } + + @Override + public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { +- Set set = this.playerMap.getPlayers(chunkPos.toLong()); +- Builder builder = ImmutableList.builder(); +- Iterator iterator = set.iterator(); ++ // 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. ++ List ret = new java.util.ArrayList<>(4); + +- while (iterator.hasNext()) { +- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +- SectionPos sectionposition = entityplayer.getLastSectionPos(); ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos); ++ if (players == null) { ++ return ret; ++ } + +- if (onlyOnWatchDistanceEdge && ChunkMap.isChunkOnRangeBorder(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance) || !onlyOnWatchDistanceEdge && ChunkMap.isChunkInRange(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance)) { +- builder.add(entityplayer); ++ Object[] backingSet = players.getBackingSet(); ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object temp = backingSet[i]; ++ if (!(temp instanceof ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)temp; ++ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) { ++ continue; + } ++ ret.add(player); + } + +- return builder.build(); ++ return ret; ++ // Paper end - per player view distance + } + + public void addEntity(Entity entity) { +@@ -2415,7 +2373,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + double vec3d_dx = player.getX() - this.entity.getX(); + double vec3d_dz = player.getZ() - this.entity.getZ(); + // Paper end - remove allocation of Vec3D here +- double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); ++ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance + double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper + double d2 = d0 * d0; + boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index f581a9f79b2357118d912a15344ff94df3b0c50e..d1b5c25b7455174e908cd6ed66789fa700190604 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -51,8 +51,8 @@ public abstract class DistanceManager { + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator + public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used +- private final TickingTracker tickingTicketsTracker = new TickingTracker(); +- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); ++ //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used ++ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used + // Paper start use a queue, but still keep unique requirement + public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { + @Override +@@ -133,7 +133,7 @@ public abstract class DistanceManager { + java.util.function.Predicate> removeIf = (ticket) -> { + final boolean ret = ticket.timedOut(ticketCounter); + if (ret) { +- this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); ++ //this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); // Paper - no longer used + } + return ret; + }; +@@ -153,7 +153,7 @@ public abstract class DistanceManager { + if (ticket.timedOut(this.ticketTickCounter)) { + iterator.remove(); + flag = true; +- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); ++ //this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used + } + } + +@@ -184,9 +184,9 @@ public abstract class DistanceManager { + protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator + public boolean runAllUpdates(ChunkMap chunkStorage) { + //this.f.a(); // Paper - no longer used +- this.tickingTicketsTracker.runAllUpdates(); ++ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used + org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper +- this.playerTicketManager.runAllUpdates(); ++ // this.playerTicketManager.runAllUpdates(); // Paper - no longer used + boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator + + if (flag) { +@@ -351,7 +351,7 @@ public abstract class DistanceManager { + long j = chunkcoordintpair.toLong(); + + boolean added = this.addTicket(j, ticket); // CraftBukkit +- this.tickingTicketsTracker.addTicket(j, ticket); ++ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used + return added; // CraftBukkit + } + +@@ -366,7 +366,7 @@ public abstract class DistanceManager { + long j = chunkcoordintpair.toLong(); + + boolean removed = this.removeTicket(j, ticket); // CraftBukkit +- this.tickingTicketsTracker.removeTicket(j, ticket); ++ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used + return removed; // CraftBukkit + } + +@@ -488,10 +488,10 @@ public abstract class DistanceManager { + + if (forced) { + this.addTicket(i, ticket); +- this.tickingTicketsTracker.addTicket(i, ticket); ++ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used + } else { + this.removeTicket(i, ticket); +- this.tickingTicketsTracker.removeTicket(i, ticket); ++ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used + } + + } +@@ -504,8 +504,8 @@ public abstract class DistanceManager { + return new ObjectOpenHashSet(); + })).add(player); + //this.f.update(i, 0, true); // Paper - no longer used +- this.playerTicketManager.update(i, 0, true); +- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); ++ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used ++ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } + + public void removePlayer(SectionPos pos, ServerPlayer player) { +@@ -518,8 +518,8 @@ public abstract class DistanceManager { + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); + //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used +- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); +- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); ++ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used ++ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } + + } +@@ -529,11 +529,17 @@ public abstract class DistanceManager { + } + + public boolean inEntityTickingRange(long chunkPos) { +- return this.tickingTicketsTracker.getLevel(chunkPos) < 32; ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isEntityTickingReady(); ++ // Paper end - replace player chunk loader system + } + + public boolean inBlockTickingRange(long chunkPos) { +- return this.tickingTicketsTracker.getLevel(chunkPos) < 33; ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + protected String getTicketDebugString(long pos) { +@@ -543,20 +549,16 @@ public abstract class DistanceManager { + } + + protected void updatePlayerTickets(int viewDistance) { +- this.playerTicketManager.updateViewDistance(viewDistance); ++ this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager + } + + public void updateSimulationDistance(int simulationDistance) { +- if (simulationDistance != this.simulationDistance) { +- this.simulationDistance = simulationDistance; +- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel()); +- } +- ++ this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager + } + + // Paper start + public int getSimulationDistance() { +- return this.simulationDistance; ++ return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager + } + // Paper end + +@@ -613,10 +615,7 @@ public abstract class DistanceManager { + + } + +- @VisibleForTesting +- TickingTracker tickingTracker() { +- return this.tickingTicketsTracker; +- } ++ // Paper - replace player chunk loader + + public void removeTicketsOnClosing() { + ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve +@@ -633,7 +632,7 @@ public abstract class DistanceManager { + if (!immutableset.contains(ticket.getType())) { + iterator.remove(); + flag = true; +- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); ++ // this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used + } + } + +@@ -672,6 +671,7 @@ public abstract class DistanceManager { + } + // CraftBukkit end + ++ /* Paper - replace old loader system + private class ChunkTicketTracker extends ChunkTracker { + + public ChunkTicketTracker() { +@@ -890,4 +890,5 @@ public abstract class DistanceManager { + return distance <= this.viewDistance - 2; + } + } ++ */ // Paper - replace old loader system + } +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 4c82f17313e18c9dfd9b28653715b8a3242b826c..efcb80efc69a1e5ffc81b579bf535fd94e8144d7 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -667,17 +667,10 @@ public class ServerChunkCache extends ChunkSource { + // Paper end + + public boolean isPositionTicking(long pos) { +- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); +- +- if (playerchunk == null) { +- return false; +- } else if (!this.level.shouldTickBlocksAt(pos)) { +- return false; +- } else { +- Either either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error +- +- return either != null && either.left().isPresent(); +- } ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + public void save(boolean flush) { +@@ -734,6 +727,7 @@ public class ServerChunkCache extends ChunkSource { + this.level.getProfiler().popPush("chunks"); + if (tickChunks) { + this.level.timings.chunks.startTiming(); // Paper - timings ++ this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes + this.tickChunks(); + this.level.timings.chunks.stopTiming(); // Paper - timings + } +@@ -847,13 +841,13 @@ public class ServerChunkCache extends ChunkSource { + // Paper end - optimise chunk tick iteration + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning ++ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - replace player chunk loader system + chunk1.incrementInhabitedTime(j); + if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { ++ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - replace player chunk loader system + this.level.tickChunk(chunk1, k); + if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + } +@@ -1082,6 +1076,7 @@ public class ServerChunkCache extends ChunkSource { + public boolean pollTask() { + try { + boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper ++ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Paper + if (ServerChunkCache.this.runDistanceManagerUpdates()) { + return true; + } else { +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 3bb6dbdd05ed981f70556c8f905d1eeeeade30b8..e71ae32d9827d8a6fb8543abdba7627897ac9f2e 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -682,7 +682,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + gameprofilerfiller.push("checkDespawn"); + entity.checkDespawn(); + gameprofilerfiller.pop(); +- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { ++ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list + Entity entity1 = entity.getVehicle(); + + if (entity1 != null) { +@@ -715,7 +715,10 @@ public class ServerLevel extends Level implements WorldGenLevel { + + @Override + public boolean shouldTickBlocksAt(long chunkPos) { +- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); ++ // Paper start - replace player chunk loader system ++ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return holder != null && holder.isTickingReady(); ++ // Paper end - replace player chunk loader system + } + + protected void tickTime() { +@@ -2459,7 +2462,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { + // Paper start - optimize is ticking ready type functions + ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); +- return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); ++ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); // Paper - no longer need to check with chunk source + // Paper end + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index b35b36527294dd697d146d2ad817d7911145ae8c..18c3d4aecf498f78040c27336d2ea56fd911d034 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -2475,5 +2475,5 @@ public class ServerPlayer extends Player { + } + // CraftBukkit end + +- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder ++ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder + } +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 67f90c75aa4858bf1575bf7b0a62b8113de7c2ea..b588e14b2826bda5b03b4fc497efcb96b566541a 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -276,7 +276,7 @@ public abstract class PlayerList { + boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); + + // Spigot - view distance +- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); ++ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName()))); + playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); +@@ -949,8 +949,8 @@ public abstract class PlayerList { + // CraftBukkit start + LevelData worlddata = worldserver1.getLevelData(); + entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag, entityplayer1.getLastDeathLocation())); +- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot +- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot ++ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management ++ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management + entityplayer1.spawnIn(worldserver1); + entityplayer1.unsetRemoved(); + entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot())); +@@ -1519,7 +1519,7 @@ public abstract class PlayerList { + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; +- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); ++ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +@@ -1534,7 +1534,7 @@ public abstract class PlayerList { + + public void setSimulationDistance(int simulationDistance) { + this.simulationDistance = simulationDistance; +- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); ++ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index 3a6e5893181ed681099f2748abca738af45ec9c9..bb51a85b33e1701c2e445305d68d3453772f73df 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -660,7 +660,7 @@ public class EnderDragon extends Mob implements Enemy { + // 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 + for (net.minecraft.server.level.ServerPlayer player : (List) ((ServerLevel)level).players()) { +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +index b3e2e834f4f151497bf842796dd8e3a8b5143f1b..4fb40aa91e0961f1974c74c88fa68359e4ad6b16 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java ++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +@@ -278,7 +278,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob + // this.world.globalLevelEvent(1023, new BlockPosition(this), 0); + //int viewDistance = ((ServerLevel) this.level).getCraftServer().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 (ServerPlayer player : (List)this.level.players()) { // Paper +- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch ++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader + double deltaX = this.getX() - player.getX(); + double deltaZ = this.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +index 0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254..fa1ff2e79954089552974cefedfcbff2225738ec 100644 +--- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java ++++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java +@@ -62,9 +62,10 @@ public class EnderEyeItem extends Item { + + // CraftBukkit start - Use relative location for far away sounds + // world.b(1038, blockposition1.c(1, 0, 1), 0); +- int viewDistance = world.getCraftServer().getViewDistance() * 16; ++ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch + BlockPos soundPos = blockposition1.offset(1, 0, 1); + for (ServerPlayer player : world.getServer().getPlayerList().players) { ++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - apply view distance patch + double deltaX = soundPos.getX() - player.getX(); + double deltaZ = soundPos.getZ() - player.getZ(); + double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 931de769a3b7c993d151f3ee8e1038d95d3899a3..30140ae5a74a511c9031b8e772e724b25e56de3d 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -627,6 +627,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement + this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); ++ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance ++ // if copied from above ++ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management ++ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); ++ // Paper end - per player view distance + } + + if ((i & 1) != 0) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index d870cefbe5b7485f423817f4f639e3e2a304640c..2292cb0e0c1a3e0ed34b941f028136bfb0bff13e 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -191,6 +191,43 @@ public class LevelChunk extends ChunkAccess { + + protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { + ++ // Paper start - no-tick view distance ++ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource(); ++ net.minecraft.server.level.ChunkMap chunkMap = chunkProviderServer.chunkMap; ++ // this code handles the addition of ticking tickets - the distance map handles the removal ++ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { ++ if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - replace old player chunk loading system ++ // now we're ready for entity ticking ++ chunkProviderServer.mainThreadProcessor.execute(() -> { ++ // double check that this condition still holds. ++ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system ++ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - replace old player chunk ++ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update ++ } ++ }); ++ } ++ } ++ ++ // this code handles the chunk sending ++ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { ++ // Paper start - replace old player chunk loading system ++ if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) { ++ // the post processing is expensive, so we don't want to run it unless we're actually near ++ // a player. ++ chunkProviderServer.mainThreadProcessor.execute(() -> { ++ if (!LevelChunk.this.areNeighboursLoaded(1)) { ++ return; ++ } ++ LevelChunk.this.postProcessGeneration(); ++ if (!LevelChunk.this.areNeighboursLoaded(1)) { ++ return; ++ } ++ chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z); ++ }); ++ } ++ // Paper end - replace old player chunk loading system ++ } ++ // Paper end - no-tick view distance + } + + public final boolean isAnyNeighborsLoaded() { +@@ -815,6 +852,7 @@ public class LevelChunk extends ChunkAccess { + // Paper end - neighbour cache + org.bukkit.Server server = this.level.getCraftServer(); + this.level.getChunkSource().addLoadedChunk(this); // Paper ++ ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management + if (server != null) { + /* + * If it's a new world, the first few chunks are generated inside +@@ -939,7 +977,10 @@ public class LevelChunk extends ChunkAccess { + }); + } + ++ public boolean isPostProcessingDone; // Paper - replace chunk loader system ++ + public void postProcessGeneration() { ++ try { // Paper - replace chunk loader system + ChunkPos chunkcoordintpair = this.getPos(); + + for (int i = 0; i < this.postProcessing.length; ++i) { +@@ -977,6 +1018,11 @@ public class LevelChunk extends ChunkAccess { + + this.pendingBlockEntities.clear(); + this.upgradeData.upgrade(this); ++ } finally { // Paper start - replace chunk loader system ++ this.isPostProcessingDone = true; ++ this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z); ++ } ++ // Paper end - replace chunk loader system + } + + @Nullable +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 73e7181655b78f5bff90d07edfe6c5408cc08235..cf6fce4f3bddcbbae59fd128cf661e4506b9d2c5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -483,10 +483,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +- playerChunk.getTickingChunkFuture().thenAccept(either -> { +- either.left().ifPresent(chunk -> { ++ // Paper start - rewrite player chunk loader ++ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk(); ++ if (chunk == null) { ++ return false; ++ } ++ // Paper end - rewrite player chunk loader + List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); +- if (playersInRange.isEmpty()) return; ++ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader + + // Paper start - Anti-Xray - Bypass + Map refreshPackets = new HashMap<>(); +@@ -499,8 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + })); + // Paper end + } +- }); +- }); ++ // Paper - rewrite player chunk loader + + return true; + } +@@ -2234,43 +2237,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Spigot start + @Override + public int getViewDistance() { +- return world.spigotConfig.viewDistance; ++ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management + } + + @Override + public int getSimulationDistance() { +- return world.spigotConfig.simulationDistance; ++ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management + } + // Spigot end + // Paper start - view distance api + @Override + public void setViewDistance(int viewDistance) { +- throw new UnsupportedOperationException(); //TODO ++ // Paper start - replace old player chunk management ++ if (viewDistance < 2 || viewDistance > 32) { ++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); ++ } ++ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; ++ chunkMap.setViewDistance(viewDistance); ++ // Paper end - replace old player chunk management + } + ++ // Paper start - replace old player chunk management + @Override + public void setSimulationDistance(int simulationDistance) { +- throw new UnsupportedOperationException(); //TODO ++ // Paper start - replace old player chunk management ++ if (simulationDistance < 2 || simulationDistance > 32) { ++ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]"); ++ } ++ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; ++ chunkMap.setTickViewDistance(simulationDistance); + } ++ // Paper end - replace old player chunk management + + @Override + public int getNoTickViewDistance() { +- throw new UnsupportedOperationException(); //TODO ++ return this.getViewDistance(); // Paper - replace old player chunk management + } + + @Override + public void setNoTickViewDistance(int viewDistance) { +- throw new UnsupportedOperationException(); //TODO ++ this.setViewDistance(viewDistance); // Paper - replace old player chunk management + } + + @Override + public int getSendViewDistance() { +- throw new UnsupportedOperationException(); //TODO ++ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); // Paper - replace old player chunk management + } + + @Override + public void setSendViewDistance(int viewDistance) { +- throw new UnsupportedOperationException(); //TODO ++ getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); // Paper - replace old player chunk management + } + // Paper end - view distance api + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 86d9250ce0a49635362a2710bf3c064936d1c77f..16fa7bdb8cc4bcad01ed33455cf1e51b69e2f720 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -541,45 +541,80 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + } + ++ // Paper start - implement view distances + @Override + public int getViewDistance() { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ return chunkMap.playerChunkManager.getTargetNoTickViewDistance(); ++ } ++ return data.getTargetNoTickViewDistance(); + } + + @Override + public void setViewDistance(int viewDistance) { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ throw new IllegalStateException("Player is not attached to world"); ++ } ++ ++ data.setTargetNoTickViewDistance(viewDistance); + } + + @Override + public int getSimulationDistance() { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ return chunkMap.playerChunkManager.getTargetTickViewDistance(); ++ } ++ return data.getTargetTickViewDistance(); + } + + @Override + public void setSimulationDistance(int simulationDistance) { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ throw new IllegalStateException("Player is not attached to world"); ++ } ++ ++ data.setTargetTickViewDistance(simulationDistance); + } + + @Override + public int getNoTickViewDistance() { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ return this.getViewDistance(); + } + + @Override + public void setNoTickViewDistance(int viewDistance) { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ this.setViewDistance(viewDistance); + } + + @Override + public int getSendViewDistance() { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ return chunkMap.playerChunkManager.getTargetSendDistance(); ++ } ++ return data.getTargetSendViewDistance(); + } + + @Override + public void setSendViewDistance(int viewDistance) { +- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO ++ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; ++ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); ++ if (data == null) { ++ throw new IllegalStateException("Player is not attached to world"); ++ } ++ ++ data.setTargetSendViewDistance(viewDistance); + } ++ // Paper end - implement view distances + + @Override + public T getClientOption(com.destroystokyo.paper.ClientOption type) { diff --git a/patches/server/0854-Fix-Fluid-tags-isTagged-method.patch b/patches/server/0854-Fix-Fluid-tags-isTagged-method.patch new file mode 100644 index 0000000000..0ce876073c --- /dev/null +++ b/patches/server/0854-Fix-Fluid-tags-isTagged-method.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 1 Mar 2022 12:45:50 -0800 +Subject: [PATCH] Fix Fluid tags isTagged method + + +diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java +index 89cb1ec575c0f58e9934d98b056621348dbbe27a..cdd474e9b0363641839a66d3e61fec46c735879a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java +@@ -16,7 +16,7 @@ public class CraftFluidTag extends CraftTag -Date: Sun, 24 Jan 2021 20:27:32 -0800 -Subject: [PATCH] Replace player chunk loader system - -The old one has undebuggable problems. Rewriting seems -the most sensible option. - -This new player chunk manager will also strictly rate limit -chunk sends so that netty threads do not get overloaded, whether -it be from the anti-xray logic or the compression itself. - -Chunk loading is also rate limited in the same manner, so this -will result in a maximum responsiveness for change. - -Config: -``` -chunk-loading: - min-load-radius: 2 - max-concurrent-sends: 2 - autoconfig-send-distance: true - target-player-chunk-send-rate: 100.0 - global-max-chunk-send-rate: -1 - enable-frustum-priority: false - global-max-chunk-load-rate: -1.0 - player-max-concurrent-loads: 25.0 - global-max-concurrent-loads: 500.0 -``` - -min-load-radius - The radius of chunks around a player that -are not throttled for loading. The number of chunks -affected is actually the configured value plus one as this -config controls the chunks the client will be able to render. - -max-concurrent-sends - The maximum number of chunks that -can be queued to send at any given time. Low values -are generally going to solve server-sided networking -bottlenecks like anti-xray and chunk compression. Client -side networking is unlikely to be helped (i.e this wont help -people running off McDonald's wifi). - -autoconfig-send-distance - Whether to try to use the client's -view distance for the send view distance in the server. In the -case that no plugin has explicitly set the send distance and -the client view distance is less-than the server's send distance, -the client's view distance will be used. This will not affect -tick view distance or no-tick view distance. - -target-player-chunk-send-rate - The maximum chunk send rate -an individual player will have. -1 means no limit - -global-max-chunk-send-rate - The maximum chunk send rate for -the whole server. -1 means no limit - -enable-frustum-priority - Whether chunks in front of a player -are prioritised to load/send first. Disabled by default -because the client can bug out due to the out of order -chunk sending. - -global-max-chunk-load-rate - The maximum chunk load rate -for the whole server. -1 means no limit - -player-max-concurrent-loads and global-max-concurrent-loads -The maximum number of concurrent loads for the server is -determined by the number of players on the server multiplied by the -`player-max-concurrent-loads`. It is then limited to -whatever `global-max-concurrent-loads` is configured to. - -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index 78280fb3bcd8d792a58ece6d735e0824ea4be536..06bff37e4c1fddd3be6343049a66787c63fb420c 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -162,7 +162,11 @@ public class TimingsExport extends Thread { - pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> { - return pair(rule, world.getWorld().getGameRuleValue(rule)); - })), -- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()) -+ // Paper start - replace chunk loader system -+ pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()), -+ pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()), -+ pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance()) -+ // Paper end - replace chunk loader system - )); - })); - -diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b53402903eb6845df361daf6b05a668608ad7b63 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java -@@ -0,0 +1,1128 @@ -+package io.papermc.paper.chunk; -+ -+import com.destroystokyo.paper.util.misc.PlayerAreaMap; -+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.IntervalledCounter; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; -+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; -+import net.minecraft.server.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.*; -+import net.minecraft.util.Mth; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.LevelChunk; -+import org.apache.commons.lang3.mutable.MutableObject; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.TreeSet; -+import java.util.concurrent.atomic.AtomicInteger; -+ -+public final class PlayerChunkLoader { -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int LOADED_TICKET_LEVEL = 33; -+ -+ public static int getTickViewDistance(final Player player) { -+ return getTickViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getTickViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); -+ if (data == null) { -+ return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance(); -+ } -+ return data.getTargetTickViewDistance(); -+ } -+ -+ public static int getLoadViewDistance(final Player player) { -+ return getLoadViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getLoadViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); -+ if (data == null) { -+ return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance(); -+ } -+ return data.getLoadDistance(); -+ } -+ -+ public static int getSendViewDistance(final Player player) { -+ return getSendViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getSendViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player); -+ if (data == null) { -+ return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance(); -+ } -+ return data.getTargetSendViewDistance(); -+ } -+ -+ protected final ChunkMap chunkMap; -+ protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f); -+ protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f); -+ -+ protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst(); -+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst(); -+ -+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); -+ -+ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad); -+ -+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ if (lastLoadTimeCompare != 0) { -+ return lastLoadTimeCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ protected final TreeSet chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget); -+ if (timeCompare != 0) { -+ return timeCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ -+ // no throttling is applied below this VD for loading -+ -+ /** -+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap broadcastMap; -+ -+ /** -+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap loadMap; -+ -+ /** -+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus, -+ * this map is always representing the chunks we are actually going to load. -+ */ -+ public final PlayerAreaMap loadTicketCleanup; -+ -+ /** -+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen. -+ */ -+ public final PlayerAreaMap tickMap; -+ -+ /** -+ * -1 if defaulting to [load distance], else always in [2, load distance] -+ */ -+ protected int rawSendDistance = -1; -+ -+ /** -+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1] -+ */ -+ protected int rawLoadDistance = -1; -+ -+ /** -+ * Never -1, always in [2, 32] -+ */ -+ protected int rawTickDistance = -1; -+ -+ // methods to bridge for API -+ -+ public int getTargetTickViewDistance() { -+ return this.getTickDistance(); -+ } -+ -+ public void setTargetTickViewDistance(final int distance) { -+ this.setTickDistance(distance); -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return this.getLoadDistance() - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ this.setLoadDistance(distance == -1 ? -1 : distance + 1); -+ } -+ -+ public int getTargetSendDistance() { -+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance; -+ } -+ -+ public void setTargetSendDistance(final int distance) { -+ this.setSendDistance(distance); -+ } -+ -+ // internal methods -+ -+ public int getSendDistance() { -+ final int loadDistance = this.getLoadDistance(); -+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance); -+ } -+ -+ public void setSendDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); -+ } -+ this.rawSendDistance = distance; -+ } -+ -+ public int getLoadDistance() { -+ final int tickDistance = this.getTickDistance(); -+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); -+ } -+ this.rawLoadDistance = distance; -+ } -+ -+ public int getTickDistance() { -+ return this.rawTickDistance; -+ } -+ -+ public void setTickDistance(final int distance) { -+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) { -+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + ", got: " + distance); -+ } -+ this.rawTickDistance = distance; -+ } -+ -+ /* -+ Players have 3 different types of view distance: -+ 1. Sending view distance -+ 2. Loading view distance -+ 3. Ticking view distance -+ -+ But for configuration purposes (and API) there are: -+ 1. No-tick view distance -+ 2. Tick view distance -+ 3. Broadcast view distance -+ -+ These aren't always the same as the types we represent internally. -+ -+ Loading view distance is always max(no-tick + 1, tick + 1) -+ - no-tick has 1 added because clients need an extra radius to render chunks -+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking -+ -+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means -+ it loads chunks in radius load-view-distance + 1. -+ -+ The maximum value for send view distance is the load view distance. API can set it lower. -+ */ -+ -+ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets pooledHashSets) { -+ this.chunkMap = chunkMap; -+ this.broadcastMap = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ); -+ }); -+ this.loadMap = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer 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; -+ } -+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ)); -+ }); -+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer 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; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) { -+ --PlayerChunkLoader.this.concurrentChunkLoads; -+ } -+ }); -+ this.tickMap = new PlayerAreaMap(pooledHashSets, -+ (ServerPlayer 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; -+ } -+ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); -+ if (chunk == null || !chunk.areNeighboursLoaded(2)) { -+ return; -+ } -+ -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }, -+ (ServerPlayer 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; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }); -+ } -+ -+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); -+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); -+ -+ public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) { -+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); -+ -+ return playersInSendRange != null; -+ } -+ -+ public void onChunkPostProcessing(final int chunkX, final int chunkZ) { -+ this.onChunkSendReady(chunkX, chunkZ); -+ } -+ -+ private boolean chunkNeedsPostProcessing(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); -+ -+ if (chunk == null) { -+ return false; -+ } -+ -+ final LevelChunk levelChunk = chunk.getSendingChunk(); -+ -+ return levelChunk != null && !levelChunk.isPostProcessingDone; -+ } -+ -+ // rets whether the chunk is at a loaded stage that is ready to be sent to players -+ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); -+ -+ if (chunk == null) { -+ return false; -+ } -+ -+ final LevelChunk levelChunk = chunk.getSendingChunk(); -+ -+ return levelChunk != null && levelChunk.isPostProcessingDone && this.isTargetedForPlayerLoad.contains(key); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { -+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ if (data == null) { -+ return false; -+ } -+ -+ return data.hasSentChunk(chunkX, chunkZ); -+ } -+ -+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ if (data == null) { -+ return false; -+ } -+ -+ final boolean center = data.hasSentChunk(chunkX, chunkZ); -+ if (!center) { -+ return false; -+ } -+ -+ return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) && -+ data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1)); -+ } -+ -+ protected int getMaxConcurrentChunkSends() { -+ return GlobalConfiguration.get().chunkLoading.maxConcurrentSends; -+ } -+ -+ protected int getMaxChunkLoads() { -+ double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads; -+ double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads; -+ return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max)); -+ } -+ -+ protected long getTargetSendPerPlayerAddend() { -+ return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate); -+ } -+ -+ protected long getMaxSendAddend() { -+ return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate); -+ } -+ -+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) { -+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ } -+ -+ public void onChunkSendReady(final int chunkX, final int chunkZ) { -+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); -+ -+ if (playersInSendRange == null) { -+ return; -+ } -+ -+ final Object[] rawData = playersInSendRange.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ final Object raw = rawData[i]; -+ -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ); -+ } -+ } -+ -+ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ // if we don't have player tickets, then the load logic will pick this up and queue to send -+ return; -+ } -+ -+ if (!data.chunksToBeSent.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ // don't queue to send, we don't want the chunk -+ return; -+ } -+ -+ final long playerPos = this.broadcastMap.getLastCoordinate(player); -+ final int playerChunkX = CoordinateUtils.getChunkX(playerPos); -+ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos); -+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ); -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0); -+ data.sendQueue.add(holder); -+ } -+ -+ public void onChunkLoad(final int chunkX, final int chunkZ) { -+ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ --this.concurrentChunkLoads; -+ } -+ } -+ -+ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ data.unloadChunk(chunkX, chunkZ); -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot add player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData data = new PlayerLoaderData(player, this); -+ if (this.playerMap.putIfAbsent(player, data) == null) { -+ data.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot remove player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerLoaderData loaderData = this.playerMap.remove(player); -+ if (loaderData == null) { -+ return; -+ } -+ loaderData.remove(); -+ this.chunkLoadQueue.remove(loaderData); -+ this.chunkSendQueue.remove(loaderData); -+ this.chunkSendWaitQueue.remove(loaderData); -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.removeInt(loaderData); -+ if (count != 0) { -+ concurrentChunkSends.getAndAdd(-count); -+ } -+ } -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot update player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData loaderData = this.playerMap.get(player); -+ if (loaderData != null) { -+ loaderData.update(); -+ } -+ } -+ -+ public PlayerLoaderData getData(final ServerPlayer player) { -+ return this.playerMap.get(player); -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick async"); -+ for (final PlayerLoaderData data : this.playerMap.values()) { -+ data.update(); -+ } -+ this.tickMidTick(); -+ } -+ -+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); -+ protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); -+ private static long nextChunkSend; -+ private void trySendChunks() { -+ final long time = System.nanoTime(); -+ if (time < nextChunkSend) { -+ return; -+ } -+ // drain entries from wait queue -+ while (!this.chunkSendWaitQueue.isEmpty()) { -+ final PlayerLoaderData data = this.chunkSendWaitQueue.first(); -+ -+ if (data.nextChunkSendTarget > time) { -+ break; -+ } -+ -+ this.chunkSendWaitQueue.pollFirst(); -+ -+ this.chunkSendQueue.add(data); -+ } -+ -+ if (this.chunkSendQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxSends = this.getMaxConcurrentChunkSends(); -+ final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time; -+ for (;;) { -+ if (this.chunkSendQueue.isEmpty()) { -+ break; -+ } -+ final int currSends = concurrentChunkSends.get(); -+ if (currSends >= maxSends) { -+ break; -+ } -+ -+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { -+ continue; -+ } -+ -+ // send chunk -+ -+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst(); -+ -+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); -+ if (queuedSend == null) { -+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease -+ // stop iterating over players who have nothing to send -+ if (this.chunkSendQueue.isEmpty()) { -+ // nothing left -+ break; -+ } -+ continue; -+ } -+ -+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { -+ throw new IllegalStateException(); -+ } -+ -+ data.nextChunkSendTarget = nextPlayerDeadline; -+ this.chunkSendWaitQueue.add(data); -+ -+ synchronized (this.sendingChunkCounts) { -+ this.sendingChunkCounts.addTo(data, 1); -+ } -+ -+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.getInt(data); -+ if (count == 0) { -+ // disconnected, so we don't need to decrement: it will be decremented for us -+ return; -+ } -+ if (count == 1) { -+ this.sendingChunkCounts.removeInt(data); -+ } else { -+ this.sendingChunkCounts.put(data, count - 1); -+ } -+ } -+ -+ concurrentChunkSends.getAndDecrement(); -+ }); -+ -+ nextChunkSend = this.getMaxSendAddend() + time; -+ if (time < nextChunkSend) { -+ break; -+ } -+ } -+ } -+ -+ protected int concurrentChunkLoads; -+ // this interval prevents bursting a lot of chunk loads -+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms -+ // this interval ensures the rate is kept between ticks correctly -+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms -+ private void tryLoadChunks() { -+ if (this.chunkLoadQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxLoads = this.getMaxChunkLoads(); -+ final long time = System.nanoTime(); -+ boolean updatedCounters = false; -+ for (;;) { -+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); -+ -+ data.lastChunkLoad = time; -+ -+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); -+ if (queuedLoad == null) { -+ if (this.chunkLoadQueue.isEmpty()) { -+ break; -+ } -+ continue; -+ } -+ -+ if (!updatedCounters) { -+ updatedCounters = true; -+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); -+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); -+ data.ticketAdditionCounterShort.updateCurrentTime(time); -+ data.ticketAdditionCounterLong.updateCurrentTime(time); -+ } -+ -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // already loaded! -+ data.loadQueue.pollFirst(); // already loaded so we just skip -+ this.chunkLoadQueue.add(data); -+ -+ // ensure the chunk is queued to send -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ continue; -+ } -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); -+ -+ final double priority = queuedLoad.priority; -+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present. -+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the -+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they -+ // aren't required, it bypasses the limiter system. -+ boolean unloadedTargetChunk = false; -+ unloaded_check: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) { -+ unloadedTargetChunk = true; -+ break unloaded_check; -+ } -+ } -+ } -+ if (unloadedTargetChunk && priority >= 0.0) { -+ // priority >= 0.0 implies rate limited chunks -+ -+ final int currentChunkLoads = this.concurrentChunkLoads; -+ if (currentChunkLoads >= maxLoads || (GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate)) -+ || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) { -+ // don't poll, we didn't load it -+ this.chunkLoadQueue.add(data); -+ break; -+ } -+ } -+ -+ // can only poll after we decide to load -+ data.loadQueue.pollFirst(); -+ -+ // now that we've polled we can re-add to load queue -+ this.chunkLoadQueue.add(data); -+ -+ // add necessary tickets to load chunk up to send-ready -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ final ChunkPos chunkPos = new ChunkPos(offX, offZ); -+ -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) { -+ continue; -+ } -+ -+ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) { -+ // won't reach here if unloadedTargetChunk is false -+ ++this.concurrentChunkLoads; -+ TICKET_ADDITION_COUNTER_SHORT.addTime(time); -+ TICKET_ADDITION_COUNTER_LONG.addTime(time); -+ data.ticketAdditionCounterShort.addTime(time); -+ data.ticketAdditionCounterLong.addTime(time); -+ } -+ } -+ } -+ -+ // mark that we've added tickets here -+ this.isTargetedForPlayerLoad.add(chunkKey); -+ -+ // it's possible all we needed was the player tickets to queue up the send. -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // yup, all we needed. -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ } else if (this.chunkNeedsPostProcessing(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // requires post processing -+ this.chunkMap.mainThreadExecutor.execute(() -> { -+ final long key = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); -+ final ChunkHolder holder = PlayerChunkLoader.this.chunkMap.getVisibleChunkIfPresent(key); -+ -+ if (holder == null) { -+ return; -+ } -+ -+ final LevelChunk chunk = holder.getSendingChunk(); -+ -+ if (chunk != null && !chunk.isPostProcessingDone) { -+ chunk.postProcessGeneration(); -+ } -+ }); -+ } -+ } -+ } -+ -+ public void tickMidTick() { -+ // try to send more chunks -+ this.trySendChunks(); -+ -+ // try to queue more chunks to load -+ this.tryLoadChunks(); -+ } -+ -+ static final class ChunkPriorityHolder { -+ public final int chunkX; -+ public final int chunkZ; -+ public final int manhattanDistanceToPlayer; -+ public final double priority; -+ -+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) { -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer; -+ this.priority = priority; -+ } -+ } -+ -+ public static final class PlayerLoaderData { -+ -+ protected static final float FOV = 110.0f; -+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0; -+ -+ // Player max sprint speed is approximately 8m/s -+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0); -+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f; -+ -+ protected double lastLocX = Double.NEGATIVE_INFINITY; -+ protected double lastLocZ = Double.NEGATIVE_INFINITY; -+ -+ protected int lastChunkX = Integer.MIN_VALUE; -+ protected int lastChunkZ = Integer.MIN_VALUE; -+ -+ // this is corrected so that 0 is along the positive x-axis -+ protected float lastYaw = Float.NEGATIVE_INFINITY; -+ -+ protected int lastSendDistance = Integer.MIN_VALUE; -+ protected int lastLoadDistance = Integer.MIN_VALUE; -+ protected int lastTickDistance = Integer.MIN_VALUE; -+ protected boolean usingLookingPriority; -+ -+ protected final ServerPlayer player; -+ protected final PlayerChunkLoader loader; -+ -+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field -+ // in a comparator! -+ protected final ArrayDeque loadQueue = new ArrayDeque<>(); -+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet(); -+ -+ protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { -+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer); -+ if (distanceCompare != 0) { -+ return distanceCompare; -+ } -+ -+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX); -+ if (coordinateXCompare != 0) { -+ return coordinateXCompare; -+ } -+ -+ return Integer.compare(p1.chunkZ, p2.chunkZ); -+ }); -+ -+ protected int sendViewDistance = -1; -+ protected int loadViewDistance = -1; -+ protected int tickViewDistance = -1; -+ -+ protected long nextChunkSendTarget; -+ -+ // this interval prevents bursting a lot of chunk loads -+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms -+ // this ensures the rate is kept between ticks correctly -+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms -+ -+ public long lastChunkLoad; -+ -+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { -+ this.player = player; -+ this.loader = loader; -+ } -+ -+ // these view distance methods are for api -+ public int getTargetSendViewDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ return sendViewDistance; -+ } -+ -+ public void setTargetSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.sendViewDistance = distance; -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Simulation distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); -+ } -+ this.loadViewDistance = distance == -1 ? -1 : distance + 1; -+ } -+ -+ public int getTargetTickViewDistance() { -+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ } -+ -+ public void setTargetTickViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); -+ } -+ this.tickViewDistance = distance; -+ } -+ -+ protected int getLoadDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ -+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ } -+ -+ public boolean hasSentChunk(final int chunkX, final int chunkZ) { -+ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded -+ this.player.connection.connection.execute(onChunkSend); -+ } else { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ public void unloadChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded -+ } -+ } -+ -+ protected static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, -+ final int sendRadius) { -+ // expect sendRadius to be = 1 + target viewable radius -+ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius); -+ } -+ -+ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point -+ final double p2x, final double p2z, // triangle point -+ final double p3x, final double p3z, // triangle point -+ -+ final double targetX, final double targetZ) { // point -+ // from barycentric coordinates: -+ // targetX = a*p1x + b*p2x + c*p3x -+ // targetZ = a*p1z + b*p2z + c*p3z -+ // 1.0 = a*1.0 + b*1.0 + c*1.0 -+ // where a, b, c >= 0.0 -+ // so, if any of a, b, c are less-than zero then there is no intersection. -+ -+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z)) -+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d -+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d -+ // c = 1.0 - a - b -+ -+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z); -+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d; -+ -+ if (a < 0.0 || a > 1.0) { -+ return false; -+ } -+ -+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d; -+ if (b < 0.0 || b > 1.0) { -+ return false; -+ } -+ -+ final double c = 1.0 - a - b; -+ -+ return c >= 0.0 && c <= 1.0; -+ } -+ -+ public void remove() { -+ this.loader.broadcastMap.remove(this.player); -+ this.loader.loadMap.remove(this.player); -+ this.loader.loadTicketCleanup.remove(this.player); -+ this.loader.tickMap.remove(this.player); -+ } -+ -+ protected int getClientViewDistance() { -+ return this.player.clientViewDistance == null ? -1 : Math.max(0, this.player.clientViewDistance.intValue()); -+ } -+ -+ public void update() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ -+ final double posX = this.player.getX(); -+ final double posZ = this.player.getZ(); -+ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis -+ -+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them. -+ final boolean useLookPriority = GlobalConfiguration.get().chunkLoading.enableFrustumPriority && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD || -+ this.player.getAbilities().flying); -+ -+ // make sure we're in the send queue -+ this.loader.chunkSendWaitQueue.add(this); -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ && (this.usingLookingPriority ? ( -+ // has our block stayed the same (this also accounts for chunk change)? -+ Mth.floor(this.lastLocX) == Mth.floor(posX) -+ && Mth.floor(this.lastLocZ) == Mth.floor(posZ) -+ ) : ( -+ // has our chunk stayed the same -+ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) -+ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4) -+ )) -+ -+ // has our decision about look priority changed? -+ && this.usingLookingPriority == useLookPriority -+ -+ // if we are currently using look priority, has our yaw stayed within recalc threshold? -+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD) -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ final int centerChunkX = Mth.floor(posX) >> 4; -+ final int centerChunkZ = Mth.floor(posZ) >> 4; -+ -+ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ); -+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance); -+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance); -+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1); -+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance); -+ -+ if (sendViewDistance != this.lastSendDistance) { -+ // update the view radius for client -+ // note that this should be after the map calls because the client wont expect unload calls not in its VD -+ // and it's possible we decreased VD here -+ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance)); -+ } -+ if (tickViewDistance != this.lastTickDistance) { -+ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance)); -+ } -+ -+ this.lastLocX = posX; -+ this.lastLocZ = posZ; -+ this.lastYaw = yaw; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.usingLookingPriority = useLookPriority; -+ -+ this.lastChunkX = centerChunkX; -+ this.lastChunkZ = centerChunkZ; -+ -+ // points for player "view" triangle: -+ -+ // obviously, the player pos is a vertex -+ final double p1x = posX; -+ final double p1z = posZ; -+ -+ // to the left of the looking direction -+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // to the right of the looking direction -+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // now that we have all of our points, we can recalculate the load queue -+ -+ final List loadQueue = new ArrayList<>(); -+ -+ // clear send queue, we are re-sorting -+ this.sendQueue.clear(); -+ // clear chunk want set, vd/position might have changed -+ this.chunksToBeSent.clear(); -+ -+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance); -+ -+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) { -+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) { -+ final int chunkX = dx + centerChunkX; -+ final int chunkZ = dz + centerChunkZ; -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ final boolean sendChunk = squareDistance <= sendViewDistance && wantChunkLoaded(centerChunkX, centerChunkZ, chunkX, chunkZ, sendViewDistance); -+ -+ if (this.hasSentChunk(chunkX, chunkZ)) { -+ // already sent (which means it is also loaded) -+ if (!sendChunk) { -+ // have sent the chunk, but don't want it anymore -+ // unload it now -+ this.unloadChunk(chunkX, chunkZ); -+ } -+ continue; -+ } -+ -+ final boolean loadChunk = squareDistance <= loadViewDistance; -+ -+ final boolean prioritised = useLookPriority && triangleIntersects( -+ // prioritisation triangle -+ p1x, p1z, p2x, p2z, p3x, p3z, -+ -+ // center of chunk -+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8) -+ ); -+ -+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); -+ -+ final double priority; -+ -+ if (squareDistance <= GlobalConfiguration.get().chunkLoading.minLoadRadius) { -+ // priority should be negative, and we also want to order it from center outwards -+ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest -+ priority = -((2 * GlobalConfiguration.get().chunkLoading.minLoadRadius + 1) - manhattanDistance); -+ } else { -+ if (prioritised) { -+ // we don't prioritise these chunks above others because we also want to make sure some chunks -+ // will be loaded if the player changes direction -+ priority = (double)manhattanDistance / 6.0; -+ } else { -+ priority = (double)manhattanDistance; -+ } -+ } -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority); -+ -+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ if (loadChunk) { -+ loadQueue.add(holder); -+ if (sendChunk) { -+ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ } -+ } else { -+ // loaded but not sent: so queue it! -+ if (sendChunk) { -+ this.sendQueue.add(holder); -+ } -+ } -+ } -+ } -+ -+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { -+ return Double.compare(p1.priority, p2.priority); -+ }); -+ -+ // we're modifying loadQueue, must remove -+ this.loader.chunkLoadQueue.remove(this); -+ -+ this.loadQueue.clear(); -+ this.loadQueue.addAll(loadQueue); -+ -+ // must re-add -+ this.loader.chunkLoadQueue.add(this); -+ -+ // update the chunk center -+ // this must be done last so that the client does not ignore any of our unload chunk packets -+ if (needsChunkCenterUpdate) { -+ this.player.connection.send(new ClientboundSetChunkCacheCenterPacket(centerChunkX, centerChunkZ)); -+ } -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 66afd752fd7d327e141d49b477f07e1ff3645d02..2a26d03fba2f3b37f176be9e47954ef9a6cd7b3e 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -100,6 +100,28 @@ public class Connection extends SimpleChannelInboundHandler> { - public boolean queueImmunity = false; - public ConnectionProtocol protocol; - // Paper end -+ // Paper start - add pending task queue -+ private final Queue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ public void execute(final Runnable run) { -+ if (this.channel == null || !this.channel.isRegistered()) { -+ run.run(); -+ return; -+ } -+ final boolean queue = !this.queue.isEmpty(); -+ if (!queue) { -+ this.channel.eventLoop().execute(run); -+ } else { -+ this.pendingTasks.add(run); -+ if (this.queue.isEmpty()) { -+ // something flushed async, dump tasks now -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } -+ } -+ } -+ // Paper end - add pending task queue - - // Paper start - allow controlled flushing - volatile boolean canFlush = true; -@@ -488,6 +510,7 @@ public class Connection extends SimpleChannelInboundHandler> { - return false; - } - private boolean processQueue() { -+ try { // Paper - add pending task queue - if (this.queue.isEmpty()) return true; - // Paper start - make only one flush call per sendPacketQueue() call - final boolean needsFlush = this.canFlush; -@@ -519,6 +542,12 @@ public class Connection extends SimpleChannelInboundHandler> { - } - } - return true; -+ } finally { // Paper start - add pending task queue -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } // Paper end - add pending task queue - } - // Paper end - -diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index b575d73ae0ff2e4f09a6a1f6fb061ca3da2cedf1..6939ef9b1fe782980e77c351d8a385a573d6a8e6 100644 ---- a/src/main/java/net/minecraft/server/MCUtil.java -+++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -636,7 +636,8 @@ public final class MCUtil { - }); - - worldData.addProperty("name", world.getWorld().getName()); -- worldData.addProperty("view-distance", world.spigotConfig.viewDistance); -+ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system - worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); - worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); - worldData.addProperty("visible-chunk-count", allChunks.size()); -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 73712d6b9c828427d4c066c6d8672534575f3793..a041161dee9a857d43c83fb677dba7e90a6a5d24 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -76,6 +76,17 @@ public class ChunkHolder { - public ServerLevel getWorld() { return chunkMap.level; } // Paper - boolean isUpdateQueued = false; // Paper - private final ChunkMap chunkMap; // Paper -+ // Paper start - no-tick view distance -+ public final LevelChunk getSendingChunk() { -+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used -+ // in Chunk's neighbour callback -+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z); -+ if (ret != null && ret.areNeighboursLoaded(1)) { -+ return ret; -+ } -+ return null; -+ } -+ // Paper end - no-tick view distance - - // Paper start - public void onChunkAdd() { -@@ -273,7 +284,7 @@ public class ChunkHolder { - - public void blockChanged(BlockPos pos) { - if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks -- LevelChunk chunk = this.getTickingChunk(); -+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance - - if (chunk != null) { - int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); -@@ -289,14 +300,15 @@ public class ChunkHolder { - } - - public void sectionLightChanged(LightLayer lightType, int y) { -- Either either = (Either) this.getFutureIfPresent(ChunkStatus.FEATURES).getNow(null); // CraftBukkit - decompile error -+ // Paper start - no-tick view distance - -- if (either != null) { -- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error -+ if (true) { -+ ChunkAccess ichunkaccess = this.getAvailableChunkNow(); - - if (ichunkaccess != null) { - ichunkaccess.setUnsaved(true); -- LevelChunk chunk = this.getTickingChunk(); -+ LevelChunk chunk = this.getSendingChunk(); -+ // Paper end - no-tick view distance - - if (chunk != null) { - int j = this.lightEngine.getMinLightSection(); -@@ -399,9 +411,28 @@ public class ChunkHolder { - } - - public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { -- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> { -- entityplayer.connection.send(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.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos); -+ if (players == null) { -+ return; -+ } -+ -+ Object[] backingSet = players.getBackingSet(); -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object temp = backingSet[i]; -+ if (!(temp instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)temp; -+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { -+ continue; -+ } -+ player.connection.send(packet); -+ } -+ // Paper end - per player view distance - } - - public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index a5e74d30045a171f5ed66a115fbd429e9ab412af..47657f20652a80f50a2e46207c9c05d1a12111b4 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -218,6 +218,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - 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 ChunkMap#anyPlayerCloseEnoughForSpawning -+ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader - // Paper start - use distance map to optimise tracker - public static boolean isLegacyTrackingEntity(Entity entity) { - return entity.isLegacyTrackingEntity; -@@ -237,6 +238,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - use distance map to optimise tracker - - void addPlayerToDistanceMaps(ServerPlayer player) { -+ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Paper start - use distance map to optimise entity tracker -@@ -244,7 +246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - 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())); -+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances - } - // Paper end - use distance map to optimise entity tracker - // Note: players need to be explicitly added to distance maps before they can be updated -@@ -274,6 +276,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobDistanceMap.remove(player); - } - // Paper end - per player mob spawning -+ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader - } - - void updateMaps(ServerPlayer player) { -@@ -285,7 +288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - 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())); -+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances - } - // Paper end - use distance map to optimise entity tracker - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -@@ -295,6 +298,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance()); - } - // Paper end - per player mob spawning -+ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader - } - // Paper end - // Paper start -@@ -1447,11 +1451,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - completablefuture1.thenAcceptAsync((either) -> { - either.ifLeft((chunk) -> { - this.tickingGenerated.getAndIncrement(); -- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass -- -- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { -- this.playerLoadedChunk(entityplayer, mutableobject, chunk); -- }); -+ // Paper - no-tick view distance - moved to Chunk neighbour update - }); - }, (runnable) -> { - this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); -@@ -1620,33 +1620,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int k = this.viewDistance; - - this.viewDistance = j; -- this.distanceManager.updatePlayerTickets(this.viewDistance + 1); -- Iterator objectiterator = net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).iterator(); // Paper -- -- while (objectiterator.hasNext()) { -- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); -- ChunkPos chunkcoordintpair = playerchunk.getPos(); -- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass -- -- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> { -- SectionPos sectionposition = entityplayer.getLastSectionPos(); -- boolean flag = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), k); -- boolean flag1 = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), this.viewDistance); -- -- this.updateChunkTracking(entityplayer, chunkcoordintpair, mutableobject, flag, flag1); -- }); -- } -+ this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system - } - - } - -- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass -+ // Paper start - replace player loader system -+ public void setTickViewDistance(int distance) { -+ this.playerChunkManager.setTickDistance(distance); -+ } -+ // Paper end - replace player loader system -+ -+ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass // Paper - public - if (player.level == this.level) { - if (newWithinViewDistance && !oldWithinViewDistance) { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); - - if (playerchunk != null) { -- LevelChunk chunk = playerchunk.getTickingChunk(); -+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system - - if (chunk != null) { - this.playerLoadedChunk(player, packet, chunk); -@@ -1677,7 +1668,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - void dumpChunks(Writer writer) throws IOException { - CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); -- TickingTracker tickingtracker = this.distanceManager.tickingTracker(); -+ // Paper - replace loader system - Iterator objectbidirectionaliterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper - - while (objectbidirectionaliterator.hasNext()) { -@@ -1693,7 +1684,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // CraftBukkit - decompile error - csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { - return chunk.getBlockEntities().size(); -- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { -+ }).orElse(0), "Use ticket level", -1000, optional1.map((chunk) -> { // Paper - replace loader system - return chunk.getBlockTicks().count(); - }).orElse(0), optional1.map((chunk) -> { - return chunk.getFluidTicks().count(); -@@ -1927,15 +1918,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.removePlayerFromDistanceMaps(player); // Paper - distance maps - } - -- for (int k = i - this.viewDistance - 1; k <= i + this.viewDistance + 1; ++k) { -- for (int l = j - this.viewDistance - 1; l <= j + this.viewDistance + 1; ++l) { -- if (ChunkMap.isChunkInRange(k, l, i, j, this.viewDistance)) { -- ChunkPos chunkcoordintpair = new ChunkPos(k, l); -- -- this.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added); -- } -- } -- } -+ // Paper - handled by player chunk loader - - } - -@@ -1943,7 +1926,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - SectionPos sectionposition = SectionPos.of((EntityAccess) player); - - player.setLastSectionPos(sectionposition); -- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); -+ //player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - handled by player chunk loader - return sectionposition; - } - -@@ -1988,65 +1971,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int k1; - int l1; - -- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) { -- k1 = Math.min(i, i1) - this.viewDistance - 1; -- l1 = Math.min(j, j1) - this.viewDistance - 1; -- int i2 = Math.max(i, i1) + this.viewDistance + 1; -- int j2 = Math.max(j, j1) + this.viewDistance + 1; -- -- for (int k2 = k1; k2 <= i2; ++k2) { -- for (int l2 = l1; l2 <= j2; ++l2) { -- boolean flag3 = ChunkMap.isChunkInRange(k2, l2, i1, j1, this.viewDistance); -- boolean flag4 = ChunkMap.isChunkInRange(k2, l2, i, j, this.viewDistance); -- -- this.updateChunkTracking(player, new ChunkPos(k2, l2), new MutableObject(), flag3, flag4); -- } -- } -- } else { -- boolean flag5; -- boolean flag6; -- -- for (k1 = i1 - this.viewDistance - 1; k1 <= i1 + this.viewDistance + 1; ++k1) { -- for (l1 = j1 - this.viewDistance - 1; l1 <= j1 + this.viewDistance + 1; ++l1) { -- if (ChunkMap.isChunkInRange(k1, l1, i1, j1, this.viewDistance)) { -- flag5 = true; -- flag6 = false; -- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), true, false); -- } -- } -- } -- -- for (k1 = i - this.viewDistance - 1; k1 <= i + this.viewDistance + 1; ++k1) { -- for (l1 = j - this.viewDistance - 1; l1 <= j + this.viewDistance + 1; ++l1) { -- if (ChunkMap.isChunkInRange(k1, l1, i, j, this.viewDistance)) { -- flag5 = false; -- flag6 = true; -- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), false, true); -- } -- } -- } -- } -+ // Paper - replaced by PlayerChunkLoader - - this.updateMaps(player); // Paper - distance maps -+ this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately - - } - - @Override - public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { -- Set set = this.playerMap.getPlayers(chunkPos.toLong()); -- Builder builder = ImmutableList.builder(); -- Iterator iterator = set.iterator(); -+ // 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. -+ List ret = new java.util.ArrayList<>(4); - -- while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -- SectionPos sectionposition = entityplayer.getLastSectionPos(); -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos); -+ if (players == null) { -+ return ret; -+ } - -- if (onlyOnWatchDistanceEdge && ChunkMap.isChunkOnRangeBorder(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance) || !onlyOnWatchDistanceEdge && ChunkMap.isChunkInRange(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance)) { -- builder.add(entityplayer); -+ Object[] backingSet = players.getBackingSet(); -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object temp = backingSet[i]; -+ if (!(temp instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)temp; -+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) { -+ continue; - } -+ ret.add(player); - } - -- return builder.build(); -+ return ret; -+ // Paper end - per player view distance - } - - public void addEntity(Entity entity) { -@@ -2415,7 +2373,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - double vec3d_dx = player.getX() - this.entity.getX(); - double vec3d_dz = player.getZ() - this.entity.getZ(); - // Paper end - remove allocation of Vec3D here -- double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); -+ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance - double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; - boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index f581a9f79b2357118d912a15344ff94df3b0c50e..d1b5c25b7455174e908cd6ed66789fa700190604 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -51,8 +51,8 @@ public abstract class DistanceManager { - public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); - //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator - public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used -- private final TickingTracker tickingTicketsTracker = new TickingTracker(); -- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); -+ //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used -+ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used - // Paper start use a queue, but still keep unique requirement - public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { - @Override -@@ -133,7 +133,7 @@ public abstract class DistanceManager { - java.util.function.Predicate> removeIf = (ticket) -> { - final boolean ret = ticket.timedOut(ticketCounter); - if (ret) { -- this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); -+ //this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); // Paper - no longer used - } - return ret; - }; -@@ -153,7 +153,7 @@ public abstract class DistanceManager { - if (ticket.timedOut(this.ticketTickCounter)) { - iterator.remove(); - flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -+ //this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used - } - } - -@@ -184,9 +184,9 @@ public abstract class DistanceManager { - protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator - public boolean runAllUpdates(ChunkMap chunkStorage) { - //this.f.a(); // Paper - no longer used -- this.tickingTicketsTracker.runAllUpdates(); -+ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used - org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper -- this.playerTicketManager.runAllUpdates(); -+ // this.playerTicketManager.runAllUpdates(); // Paper - no longer used - boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator - - if (flag) { -@@ -351,7 +351,7 @@ public abstract class DistanceManager { - long j = chunkcoordintpair.toLong(); - - boolean added = this.addTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.addTicket(j, ticket); -+ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used - return added; // CraftBukkit - } - -@@ -366,7 +366,7 @@ public abstract class DistanceManager { - long j = chunkcoordintpair.toLong(); - - boolean removed = this.removeTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.removeTicket(j, ticket); -+ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used - return removed; // CraftBukkit - } - -@@ -488,10 +488,10 @@ public abstract class DistanceManager { - - if (forced) { - this.addTicket(i, ticket); -- this.tickingTicketsTracker.addTicket(i, ticket); -+ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used - } else { - this.removeTicket(i, ticket); -- this.tickingTicketsTracker.removeTicket(i, ticket); -+ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used - } - - } -@@ -504,8 +504,8 @@ public abstract class DistanceManager { - return new ObjectOpenHashSet(); - })).add(player); - //this.f.update(i, 0, true); // Paper - no longer used -- this.playerTicketManager.update(i, 0, true); -- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used -+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - public void removePlayer(SectionPos pos, ServerPlayer player) { -@@ -518,8 +518,8 @@ public abstract class DistanceManager { - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used -- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); -- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); -+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used -+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used - } - - } -@@ -529,11 +529,17 @@ public abstract class DistanceManager { - } - - public boolean inEntityTickingRange(long chunkPos) { -- return this.tickingTicketsTracker.getLevel(chunkPos) < 32; -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isEntityTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public boolean inBlockTickingRange(long chunkPos) { -- return this.tickingTicketsTracker.getLevel(chunkPos) < 33; -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected String getTicketDebugString(long pos) { -@@ -543,20 +549,16 @@ public abstract class DistanceManager { - } - - protected void updatePlayerTickets(int viewDistance) { -- this.playerTicketManager.updateViewDistance(viewDistance); -+ this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager - } - - public void updateSimulationDistance(int simulationDistance) { -- if (simulationDistance != this.simulationDistance) { -- this.simulationDistance = simulationDistance; -- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel()); -- } -- -+ this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager - } - - // Paper start - public int getSimulationDistance() { -- return this.simulationDistance; -+ return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager - } - // Paper end - -@@ -613,10 +615,7 @@ public abstract class DistanceManager { - - } - -- @VisibleForTesting -- TickingTracker tickingTracker() { -- return this.tickingTicketsTracker; -- } -+ // Paper - replace player chunk loader - - public void removeTicketsOnClosing() { - ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve -@@ -633,7 +632,7 @@ public abstract class DistanceManager { - if (!immutableset.contains(ticket.getType())) { - iterator.remove(); - flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -+ // this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used - } - } - -@@ -672,6 +671,7 @@ public abstract class DistanceManager { - } - // CraftBukkit end - -+ /* Paper - replace old loader system - private class ChunkTicketTracker extends ChunkTracker { - - public ChunkTicketTracker() { -@@ -890,4 +890,5 @@ public abstract class DistanceManager { - return distance <= this.viewDistance - 2; - } - } -+ */ // Paper - replace old loader system - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 4c82f17313e18c9dfd9b28653715b8a3242b826c..efcb80efc69a1e5ffc81b579bf535fd94e8144d7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -667,17 +667,10 @@ public class ServerChunkCache extends ChunkSource { - // Paper end - - public boolean isPositionTicking(long pos) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos); -- -- if (playerchunk == null) { -- return false; -- } else if (!this.level.shouldTickBlocksAt(pos)) { -- return false; -- } else { -- Either either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error -- -- return either != null && either.left().isPresent(); -- } -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - public void save(boolean flush) { -@@ -734,6 +727,7 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings -+ this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - } -@@ -847,13 +841,13 @@ public class ServerChunkCache extends ChunkSource { - // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - replace player chunk loader system - chunk1.incrementInhabitedTime(j); - if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration - NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); - } - -- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { -+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - replace player chunk loader system - this.level.tickChunk(chunk1, k); - if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper - } -@@ -1082,6 +1076,7 @@ public class ServerChunkCache extends ChunkSource { - public boolean pollTask() { - try { - boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper -+ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Paper - if (ServerChunkCache.this.runDistanceManagerUpdates()) { - return true; - } else { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 3bb6dbdd05ed981f70556c8f905d1eeeeade30b8..e71ae32d9827d8a6fb8543abdba7627897ac9f2e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -682,7 +682,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - gameprofilerfiller.push("checkDespawn"); - entity.checkDespawn(); - gameprofilerfiller.pop(); -- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { -+ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list - Entity entity1 = entity.getVehicle(); - - if (entity1 != null) { -@@ -715,7 +715,10 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @Override - public boolean shouldTickBlocksAt(long chunkPos) { -- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos); -+ // Paper start - replace player chunk loader system -+ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); -+ return holder != null && holder.isTickingReady(); -+ // Paper end - replace player chunk loader system - } - - protected void tickTime() { -@@ -2459,7 +2462,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { - // Paper start - optimize is ticking ready type functions - ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); -- return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); -+ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); // Paper - no longer need to check with chunk source - // Paper end - } - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index b35b36527294dd697d146d2ad817d7911145ae8c..18c3d4aecf498f78040c27336d2ea56fd911d034 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2475,5 +2475,5 @@ public class ServerPlayer extends Player { - } - // CraftBukkit end - -- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder -+ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 67f90c75aa4858bf1575bf7b0a62b8113de7c2ea..b588e14b2826bda5b03b4fc497efcb96b566541a 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -276,7 +276,7 @@ public abstract class PlayerList { - boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); -+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management - player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit - playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName()))); - playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -@@ -949,8 +949,8 @@ public abstract class PlayerList { - // CraftBukkit start - LevelData worlddata = worldserver1.getLevelData(); - entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag, entityplayer1.getLastDeathLocation())); -- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot -- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot -+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management -+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management - entityplayer1.spawnIn(worldserver1); - entityplayer1.unsetRemoved(); - entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot())); -@@ -1519,7 +1519,7 @@ public abstract class PlayerList { - - public void setViewDistance(int viewDistance) { - this.viewDistance = viewDistance; -- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); -+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -@@ -1534,7 +1534,7 @@ public abstract class PlayerList { - - public void setSimulationDistance(int simulationDistance) { - this.simulationDistance = simulationDistance; -- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); -+ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader - Iterator iterator = this.server.getAllLevels().iterator(); - - while (iterator.hasNext()) { -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 3a6e5893181ed681099f2748abca738af45ec9c9..bb51a85b33e1701c2e445305d68d3453772f73df 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -660,7 +660,7 @@ public class EnderDragon extends Mob implements Enemy { - // 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 - for (net.minecraft.server.level.ServerPlayer player : (List) ((ServerLevel)level).players()) { -- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch -+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader - double deltaX = this.getX() - player.getX(); - double deltaZ = this.getZ() - player.getZ(); - double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index b3e2e834f4f151497bf842796dd8e3a8b5143f1b..4fb40aa91e0961f1974c74c88fa68359e4ad6b16 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -278,7 +278,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob - // this.world.globalLevelEvent(1023, new BlockPosition(this), 0); - //int viewDistance = ((ServerLevel) this.level).getCraftServer().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 (ServerPlayer player : (List)this.level.players()) { // Paper -- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch -+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader - double deltaX = this.getX() - player.getX(); - double deltaZ = this.getZ() - player.getZ(); - double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index 0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254..fa1ff2e79954089552974cefedfcbff2225738ec 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -62,9 +62,10 @@ public class EnderEyeItem extends Item { - - // CraftBukkit start - Use relative location for far away sounds - // world.b(1038, blockposition1.c(1, 0, 1), 0); -- int viewDistance = world.getCraftServer().getViewDistance() * 16; -+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch - BlockPos soundPos = blockposition1.offset(1, 0, 1); - for (ServerPlayer player : world.getServer().getPlayerList().players) { -+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - apply view distance patch - double deltaX = soundPos.getX() - player.getX(); - double deltaZ = soundPos.getZ() - player.getZ(); - double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 17c0110fd5ccb8399af797cc892ca285b1fb7964..c118efaadd0e3e29f9adcd65c11ecabfc6d76216 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -627,6 +627,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement - this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); -+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance -+ // if copied from above -+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management -+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition); -+ // Paper end - per player view distance - } - - if ((i & 1) != 0) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index d870cefbe5b7485f423817f4f639e3e2a304640c..2292cb0e0c1a3e0ed34b941f028136bfb0bff13e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -191,6 +191,43 @@ public class LevelChunk extends ChunkAccess { - - protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { - -+ // Paper start - no-tick view distance -+ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource(); -+ net.minecraft.server.level.ChunkMap chunkMap = chunkProviderServer.chunkMap; -+ // this code handles the addition of ticking tickets - the distance map handles the removal -+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { -+ if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - replace old player chunk loading system -+ // now we're ready for entity ticking -+ chunkProviderServer.mainThreadProcessor.execute(() -> { -+ // double check that this condition still holds. -+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system -+ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - replace old player chunk -+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update -+ } -+ }); -+ } -+ } -+ -+ // this code handles the chunk sending -+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { -+ // Paper start - replace old player chunk loading system -+ if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) { -+ // the post processing is expensive, so we don't want to run it unless we're actually near -+ // a player. -+ chunkProviderServer.mainThreadProcessor.execute(() -> { -+ if (!LevelChunk.this.areNeighboursLoaded(1)) { -+ return; -+ } -+ LevelChunk.this.postProcessGeneration(); -+ if (!LevelChunk.this.areNeighboursLoaded(1)) { -+ return; -+ } -+ chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z); -+ }); -+ } -+ // Paper end - replace old player chunk loading system -+ } -+ // Paper end - no-tick view distance - } - - public final boolean isAnyNeighborsLoaded() { -@@ -815,6 +852,7 @@ public class LevelChunk extends ChunkAccess { - // Paper end - neighbour cache - org.bukkit.Server server = this.level.getCraftServer(); - this.level.getChunkSource().addLoadedChunk(this); // Paper -+ ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -939,7 +977,10 @@ public class LevelChunk extends ChunkAccess { - }); - } - -+ public boolean isPostProcessingDone; // Paper - replace chunk loader system -+ - public void postProcessGeneration() { -+ try { // Paper - replace chunk loader system - ChunkPos chunkcoordintpair = this.getPos(); - - for (int i = 0; i < this.postProcessing.length; ++i) { -@@ -977,6 +1018,11 @@ public class LevelChunk extends ChunkAccess { - - this.pendingBlockEntities.clear(); - this.upgradeData.upgrade(this); -+ } finally { // Paper start - replace chunk loader system -+ this.isPostProcessingDone = true; -+ this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z); -+ } -+ // Paper end - replace chunk loader system - } - - @Nullable -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 73e7181655b78f5bff90d07edfe6c5408cc08235..cf6fce4f3bddcbbae59fd128cf661e4506b9d2c5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -483,10 +483,14 @@ public class CraftWorld extends CraftRegionAccessor implements World { - ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); - if (playerChunk == null) return false; - -- playerChunk.getTickingChunkFuture().thenAccept(either -> { -- either.left().ifPresent(chunk -> { -+ // Paper start - rewrite player chunk loader -+ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk(); -+ if (chunk == null) { -+ return false; -+ } -+ // Paper end - rewrite player chunk loader - List playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false); -- if (playersInRange.isEmpty()) return; -+ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader - - // Paper start - Anti-Xray - Bypass - Map refreshPackets = new HashMap<>(); -@@ -499,8 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - })); - // Paper end - } -- }); -- }); -+ // Paper - rewrite player chunk loader - - return true; - } -@@ -2234,43 +2237,56 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot start - @Override - public int getViewDistance() { -- return world.spigotConfig.viewDistance; -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management - } - - @Override - public int getSimulationDistance() { -- return world.spigotConfig.simulationDistance; -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management - } - // Spigot end - // Paper start - view distance api - @Override - public void setViewDistance(int viewDistance) { -- throw new UnsupportedOperationException(); //TODO -+ // Paper start - replace old player chunk management -+ if (viewDistance < 2 || viewDistance > 32) { -+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); -+ } -+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; -+ chunkMap.setViewDistance(viewDistance); -+ // Paper end - replace old player chunk management - } - -+ // Paper start - replace old player chunk management - @Override - public void setSimulationDistance(int simulationDistance) { -- throw new UnsupportedOperationException(); //TODO -+ // Paper start - replace old player chunk management -+ if (simulationDistance < 2 || simulationDistance > 32) { -+ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]"); -+ } -+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; -+ chunkMap.setTickViewDistance(simulationDistance); - } -+ // Paper end - replace old player chunk management - - @Override - public int getNoTickViewDistance() { -- throw new UnsupportedOperationException(); //TODO -+ return this.getViewDistance(); // Paper - replace old player chunk management - } - - @Override - public void setNoTickViewDistance(int viewDistance) { -- throw new UnsupportedOperationException(); //TODO -+ this.setViewDistance(viewDistance); // Paper - replace old player chunk management - } - - @Override - public int getSendViewDistance() { -- throw new UnsupportedOperationException(); //TODO -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); // Paper - replace old player chunk management - } - - @Override - public void setSendViewDistance(int viewDistance) { -- throw new UnsupportedOperationException(); //TODO -+ getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); // Paper - replace old player chunk management - } - // Paper end - view distance api - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 86d9250ce0a49635362a2710bf3c064936d1c77f..16fa7bdb8cc4bcad01ed33455cf1e51b69e2f720 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -541,45 +541,80 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - } - -+ // Paper start - implement view distances - @Override - public int getViewDistance() { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetNoTickViewDistance(); -+ } -+ return data.getTargetNoTickViewDistance(); - } - - @Override - public void setViewDistance(int viewDistance) { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetNoTickViewDistance(viewDistance); - } - - @Override - public int getSimulationDistance() { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetTickViewDistance(); -+ } -+ return data.getTargetTickViewDistance(); - } - - @Override - public void setSimulationDistance(int simulationDistance) { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetTickViewDistance(simulationDistance); - } - - @Override - public int getNoTickViewDistance() { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ return this.getViewDistance(); - } - - @Override - public void setNoTickViewDistance(int viewDistance) { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ this.setViewDistance(viewDistance); - } - - @Override - public int getSendViewDistance() { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetSendDistance(); -+ } -+ return data.getTargetSendViewDistance(); - } - - @Override - public void setSendViewDistance(int viewDistance) { -- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetSendViewDistance(viewDistance); - } -+ // Paper end - implement view distances - - @Override - public T getClientOption(com.destroystokyo.paper.ClientOption type) { diff --git a/patches/server/0855-Fix-Fluid-tags-isTagged-method.patch b/patches/server/0855-Fix-Fluid-tags-isTagged-method.patch deleted file mode 100644 index 0ce876073c..0000000000 --- a/patches/server/0855-Fix-Fluid-tags-isTagged-method.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 1 Mar 2022 12:45:50 -0800 -Subject: [PATCH] Fix Fluid tags isTagged method - - -diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -index 89cb1ec575c0f58e9934d98b056621348dbbe27a..cdd474e9b0363641839a66d3e61fec46c735879a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -+++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -@@ -16,7 +16,7 @@ public class CraftFluidTag extends CraftTag +Date: Wed, 2 Mar 2022 09:45:56 +0100 +Subject: [PATCH] Force close world loading screen + +Dead players would be stuck in the world loading screen and other players may +miss messages and similar sent in the join event if chunk loading is slow. +Paper already circumvents falling through the world before chunks are loaded, +so we do not need that. The client only needs the chunk it is currently in to +be loaded to close the loading screen, so we just send an empty one. + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index b588e14b2826bda5b03b4fc497efcb96b566541a..5a5ea1f9d6e978916d32b170ecf7f848d2524303 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -429,6 +429,16 @@ public abstract class PlayerList { + + // Paper start - move vehicle into method so it can be called above - short circuit around that code + onPlayerJoinFinish(player, worldserver1, s1); ++ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead ++ if (player.isDeadOrDying()) { ++ net.minecraft.core.Holder plains = worldserver1.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY) ++ .getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( ++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), ++ worldserver1.getLightEngine(), null, null, true, false) ++ ); ++ } ++ // Paper end + } + private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { + // Paper end diff --git a/patches/server/0856-Fix-falling-block-spawn-methods.patch b/patches/server/0856-Fix-falling-block-spawn-methods.patch new file mode 100644 index 0000000000..c8f73c8b56 --- /dev/null +++ b/patches/server/0856-Fix-falling-block-spawn-methods.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 4 Mar 2022 20:35:19 +0100 +Subject: [PATCH] Fix falling block spawn methods + +Restores the API behavior from previous versions of the server +- Do not call API events +- Do not replace the existing block in the world + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index d1fca0e3227b5f37c11367548be362f5a49b6a71..5628940cd3c3566c5db2beda506d4f20b6e3cbae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -566,7 +566,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + // Paper end + } else if (FallingBlock.class.isAssignableFrom(clazz)) { + BlockPos pos = new BlockPos(x, y, z); +- entity = FallingBlockEntity.fall(world, pos, this.getHandle().getBlockState(pos)); ++ entity = new FallingBlockEntity(world, x, y, z, this.getHandle().getBlockState(pos)); // Paper + } else if (Projectile.class.isAssignableFrom(clazz)) { + if (Snowball.class.isAssignableFrom(clazz)) { + entity = new net.minecraft.world.entity.projectile.Snowball(world, x, y, z); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index cf6fce4f3bddcbbae59fd128cf661e4506b9d2c5..23c68b6cd26fbd05685ebcfbb5e81db4c8dedb29 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1408,7 +1408,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Validate.notNull(material, "Material cannot be null"); + Validate.isTrue(material.isBlock(), "Material must be a block"); + +- FallingBlockEntity entity = FallingBlockEntity.fall(world, new BlockPos(location.getX(), location.getY(), location.getZ()), CraftMagicNumbers.getBlock(material).defaultBlockState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftMagicNumbers.getBlock(material).defaultBlockState()); // Paper ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end + return (FallingBlock) entity.getBukkitEntity(); + } + +@@ -1417,7 +1422,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + Validate.notNull(location, "Location cannot be null"); + Validate.notNull(data, "BlockData cannot be null"); + +- FallingBlockEntity entity = FallingBlockEntity.fall(world, new BlockPos(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); ++ // Paper start - restore API behavior for spawning falling blocks ++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); ++ entity.time = 1; ++ ++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); ++ // Paper end + return (FallingBlock) entity.getBukkitEntity(); + } + diff --git a/patches/server/0856-Force-close-world-loading-screen.patch b/patches/server/0856-Force-close-world-loading-screen.patch deleted file mode 100644 index 1f75753d30..0000000000 --- a/patches/server/0856-Force-close-world-loading-screen.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Wed, 2 Mar 2022 09:45:56 +0100 -Subject: [PATCH] Force close world loading screen - -Dead players would be stuck in the world loading screen and other players may -miss messages and similar sent in the join event if chunk loading is slow. -Paper already circumvents falling through the world before chunks are loaded, -so we do not need that. The client only needs the chunk it is currently in to -be loaded to close the loading screen, so we just send an empty one. - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index b588e14b2826bda5b03b4fc497efcb96b566541a..5a5ea1f9d6e978916d32b170ecf7f848d2524303 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -429,6 +429,16 @@ public abstract class PlayerList { - - // Paper start - move vehicle into method so it can be called above - short circuit around that code - onPlayerJoinFinish(player, worldserver1, s1); -+ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead -+ if (player.isDeadOrDying()) { -+ net.minecraft.core.Holder plains = worldserver1.registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY) -+ .getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( -+ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), -+ worldserver1.getLightEngine(), null, null, true, false) -+ ); -+ } -+ // Paper end - } - private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, CompoundTag nbttagcompound) { - // Paper end diff --git a/patches/server/0857-Expose-furnace-minecart-push-values.patch b/patches/server/0857-Expose-furnace-minecart-push-values.patch new file mode 100644 index 0000000000..4086d94249 --- /dev/null +++ b/patches/server/0857-Expose-furnace-minecart-push-values.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: EpicKnarvik97 +Date: Sat, 5 Mar 2022 20:58:46 +0100 +Subject: [PATCH] Expose furnace minecart push values + +Adds methods for getting and setting a furnace minecart's push values + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +index 49ceb730b6e68b7c70799fca80dc32da4a12c545..b8378d5f3c2a08ab565dcb8cb200822b581c7dba 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java +@@ -28,6 +28,28 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca + this.getHandle().fuel = fuel; + } + ++ // Paper start ++ @Override ++ public double getPushX() { ++ return getHandle().xPush; ++ } ++ ++ @Override ++ public double getPushZ() { ++ return getHandle().zPush; ++ } ++ ++ @Override ++ public void setPushX(double xPush) { ++ getHandle().xPush = xPush; ++ } ++ ++ @Override ++ public void setPushZ(double zPush) { ++ getHandle().zPush = zPush; ++ } ++ // Paper end ++ + @Override + public String toString() { + return "CraftMinecartFurnace"; diff --git a/patches/server/0857-Fix-falling-block-spawn-methods.patch b/patches/server/0857-Fix-falling-block-spawn-methods.patch deleted file mode 100644 index c8f73c8b56..0000000000 --- a/patches/server/0857-Fix-falling-block-spawn-methods.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 4 Mar 2022 20:35:19 +0100 -Subject: [PATCH] Fix falling block spawn methods - -Restores the API behavior from previous versions of the server -- Do not call API events -- Do not replace the existing block in the world - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index d1fca0e3227b5f37c11367548be362f5a49b6a71..5628940cd3c3566c5db2beda506d4f20b6e3cbae 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -566,7 +566,7 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - // Paper end - } else if (FallingBlock.class.isAssignableFrom(clazz)) { - BlockPos pos = new BlockPos(x, y, z); -- entity = FallingBlockEntity.fall(world, pos, this.getHandle().getBlockState(pos)); -+ entity = new FallingBlockEntity(world, x, y, z, this.getHandle().getBlockState(pos)); // Paper - } else if (Projectile.class.isAssignableFrom(clazz)) { - if (Snowball.class.isAssignableFrom(clazz)) { - entity = new net.minecraft.world.entity.projectile.Snowball(world, x, y, z); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index cf6fce4f3bddcbbae59fd128cf661e4506b9d2c5..23c68b6cd26fbd05685ebcfbb5e81db4c8dedb29 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1408,7 +1408,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Validate.notNull(material, "Material cannot be null"); - Validate.isTrue(material.isBlock(), "Material must be a block"); - -- FallingBlockEntity entity = FallingBlockEntity.fall(world, new BlockPos(location.getX(), location.getY(), location.getZ()), CraftMagicNumbers.getBlock(material).defaultBlockState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftMagicNumbers.getBlock(material).defaultBlockState()); // Paper -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - return (FallingBlock) entity.getBukkitEntity(); - } - -@@ -1417,7 +1422,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - Validate.notNull(location, "Location cannot be null"); - Validate.notNull(data, "BlockData cannot be null"); - -- FallingBlockEntity entity = FallingBlockEntity.fall(world, new BlockPos(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM); -+ // Paper start - restore API behavior for spawning falling blocks -+ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState()); -+ entity.time = 1; -+ -+ this.world.addFreshEntity(entity, SpawnReason.CUSTOM); -+ // Paper end - return (FallingBlock) entity.getBukkitEntity(); - } - diff --git a/patches/server/0858-Expose-furnace-minecart-push-values.patch b/patches/server/0858-Expose-furnace-minecart-push-values.patch deleted file mode 100644 index 4086d94249..0000000000 --- a/patches/server/0858-Expose-furnace-minecart-push-values.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: EpicKnarvik97 -Date: Sat, 5 Mar 2022 20:58:46 +0100 -Subject: [PATCH] Expose furnace minecart push values - -Adds methods for getting and setting a furnace minecart's push values - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -index 49ceb730b6e68b7c70799fca80dc32da4a12c545..b8378d5f3c2a08ab565dcb8cb200822b581c7dba 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java -@@ -28,6 +28,28 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca - this.getHandle().fuel = fuel; - } - -+ // Paper start -+ @Override -+ public double getPushX() { -+ return getHandle().xPush; -+ } -+ -+ @Override -+ public double getPushZ() { -+ return getHandle().zPush; -+ } -+ -+ @Override -+ public void setPushX(double xPush) { -+ getHandle().xPush = xPush; -+ } -+ -+ @Override -+ public void setPushZ(double zPush) { -+ getHandle().zPush = zPush; -+ } -+ // Paper end -+ - @Override - public String toString() { - return "CraftMinecartFurnace"; diff --git a/patches/server/0858-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0858-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch new file mode 100644 index 0000000000..a9cdda7a75 --- /dev/null +++ b/patches/server/0858-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 19 Feb 2022 19:05:59 -0800 +Subject: [PATCH] Fix cancelling ProjectileHitEvent for piercing arrows + +Piercing arrows search for multiple entities inside a while +loop that is checking the projectile entity's removed state. +If the hit event is cancelled on the first entity, the event will +be called over and over again inside that while loop until the event +is not cancelled. The solution here, is to make use of an +already-existing field on AbstractArrow for tracking entities hit by +piercing arrows to avoid duplicate damage being applied. + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +index 8564ecd20578d907bcfa1b9c149da22e424e254a..bc01e5014e1355a225bdf5c47f2965290b45e2d2 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +@@ -300,6 +300,19 @@ public abstract class AbstractArrow extends Projectile { + } + } + ++ // Paper start ++ @Override ++ protected void preOnHit(HitResult hitResult) { ++ super.preOnHit(hitResult); ++ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) { ++ if (this.piercingIgnoreEntityIds == null) { ++ this.piercingIgnoreEntityIds = new IntOpenHashSet(5); ++ } ++ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId()); ++ } ++ } ++ // Paper end ++ + private boolean shouldFall() { + return this.inGround && this.level.noCollision((new AABB(this.position(), this.position())).inflate(0.06D)); + } diff --git a/patches/server/0859-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0859-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch deleted file mode 100644 index a9cdda7a75..0000000000 --- a/patches/server/0859-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 19 Feb 2022 19:05:59 -0800 -Subject: [PATCH] Fix cancelling ProjectileHitEvent for piercing arrows - -Piercing arrows search for multiple entities inside a while -loop that is checking the projectile entity's removed state. -If the hit event is cancelled on the first entity, the event will -be called over and over again inside that while loop until the event -is not cancelled. The solution here, is to make use of an -already-existing field on AbstractArrow for tracking entities hit by -piercing arrows to avoid duplicate damage being applied. - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 8564ecd20578d907bcfa1b9c149da22e424e254a..bc01e5014e1355a225bdf5c47f2965290b45e2d2 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -300,6 +300,19 @@ public abstract class AbstractArrow extends Projectile { - } - } - -+ // Paper start -+ @Override -+ protected void preOnHit(HitResult hitResult) { -+ super.preOnHit(hitResult); -+ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) { -+ if (this.piercingIgnoreEntityIds == null) { -+ this.piercingIgnoreEntityIds = new IntOpenHashSet(5); -+ } -+ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId()); -+ } -+ } -+ // Paper end -+ - private boolean shouldFall() { - return this.inGround && this.level.noCollision((new AABB(this.position(), this.position())).inflate(0.06D)); - } diff --git a/patches/server/0859-Fix-save-problems-on-shutdown.patch b/patches/server/0859-Fix-save-problems-on-shutdown.patch new file mode 100644 index 0000000000..b07c4721de --- /dev/null +++ b/patches/server/0859-Fix-save-problems-on-shutdown.patch @@ -0,0 +1,74 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 5 Mar 2022 17:12:52 -0800 +Subject: [PATCH] Fix save problems on shutdown + +- Save level.dat first, in case the shutdown is killed later +- Force run minecraftserver tasks and the chunk source tasks + while waiting for the chunk system to empty, as there's simply + too much trash that could prevent them from executing during + the chunk source tick (i.e "time left in tick" logic). +- Set forceTicks to true, so that player packets are always + processed so that the main process queue can be drained + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index df08b7afcf19ce694a87c25e8589c0c72521c5db..4d920031300a9801debc2eb39a4d3cb9d8fbb330 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -957,6 +957,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + return worldserver1.getChunkSource().chunkMap.hasWork(); + })) { +@@ -969,9 +976,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + return true; + }, false); ++ while (worldserver.getChunkSource().pollTask()); // Paper - drain tasks + } + +- this.waitUntilNextTick(); ++ this.forceTicks = true; // Paper ++ while (this.pollTask()); // Paper - drain tasks + } + + this.saveAllChunks(false, true, false); +@@ -1266,6 +1275,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sat, 5 Mar 2022 17:12:52 -0800 -Subject: [PATCH] Fix save problems on shutdown - -- Save level.dat first, in case the shutdown is killed later -- Force run minecraftserver tasks and the chunk source tasks - while waiting for the chunk system to empty, as there's simply - too much trash that could prevent them from executing during - the chunk source tick (i.e "time left in tick" logic). -- Set forceTicks to true, so that player packets are always - processed so that the main process queue can be drained - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index df08b7afcf19ce694a87c25e8589c0c72521c5db..4d920031300a9801debc2eb39a4d3cb9d8fbb330 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -957,6 +957,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - return worldserver1.getChunkSource().chunkMap.hasWork(); - })) { -@@ -969,9 +976,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - return true; - }, false); -+ while (worldserver.getChunkSource().pollTask()); // Paper - drain tasks - } - -- this.waitUntilNextTick(); -+ this.forceTicks = true; // Paper -+ while (this.pollTask()); // Paper - drain tasks - } - - this.saveAllChunks(false, true, false); -@@ -1266,6 +1275,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Tue, 22 Jun 2021 23:41:11 -0400 +Subject: [PATCH] More Projectile API + +Co-authored-by: Nassim Jahnke + +diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +index fee09e6ff72cf1da389d5811dd005642cd50a5b4..4f276b2a86735a2c664738450ae0fbdd82031d4e 100644 +--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java ++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java +@@ -98,6 +98,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + @Override + protected void onHit(HitResult hitResult) { + super.onHit(hitResult); ++ // Paper start - More projectile API ++ this.splash(hitResult); ++ } ++ public void splash(@org.jetbrains.annotations.Nullable HitResult hitResult) { ++ // Paper end - More projectile API + if (!this.level.isClientSide) { + ItemStack itemstack = this.getItem(); + Potion potionregistry = PotionUtils.getPotion(itemstack); +@@ -110,7 +115,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie + if (this.isLingering()) { + this.makeAreaOfEffectCloud(itemstack, potionregistry); + } else { +- this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null); ++ this.applySplash(list, hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null); // Paper - nullable hitResult + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +index c242f654c88ca1773429348939d3bb2ffae3768c..d1c7ab67cba881d96b7a5e9220130d86d0514304 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +@@ -16,24 +16,26 @@ import org.bukkit.inventory.meta.FireworkMeta; + public class CraftFirework extends CraftProjectile implements Firework { + + private final Random random = new Random(); +- private final CraftItemStack item; ++ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. + + public CraftFirework(CraftServer server, FireworkRocketEntity entity) { + super(server, entity); + +- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); +- +- if (item.isEmpty()) { +- item = new ItemStack(Items.FIREWORK_ROCKET); +- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); +- } +- +- this.item = CraftItemStack.asCraftMirror(item); +- +- // Ensure the item is a firework... +- if (this.item.getType() != Material.FIREWORK_ROCKET) { +- this.item.setType(Material.FIREWORK_ROCKET); +- } ++// Paper Start - Expose firework item directly ++// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); ++// ++// if (item.isEmpty()) { ++// item = new ItemStack(Items.FIREWORK_ROCKET); ++// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++// } ++// ++// this.item = CraftItemStack.asCraftMirror(item); ++// ++// // Ensure the item is a firework... ++// if (this.item.getType() != Material.FIREWORK_ROCKET) { ++// this.item.setType(Material.FIREWORK_ROCKET); ++// } ++ // Paper End - Expose firework item directly + } + + @Override +@@ -53,12 +55,12 @@ public class CraftFirework extends CraftProjectile implements Firework { + + @Override + public FireworkMeta getFireworkMeta() { +- return (FireworkMeta) this.item.getItemMeta(); ++ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), Material.FIREWORK_ROCKET); // Paper - Expose firework item directly + } + + @Override + public void setFireworkMeta(FireworkMeta meta) { +- this.item.setItemMeta(meta); ++ applyFireworkEffect(meta); // Paper - Expose firework item directly + + // Copied from EntityFireworks constructor, update firework lifetime/power + this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); +@@ -142,4 +144,46 @@ public class CraftFirework extends CraftProjectile implements Firework { + return getHandle().spawningEntity; + } + // Paper end ++ // Paper start - Expose firework item directly + manually setting flight ++ @Override ++ public org.bukkit.inventory.ItemStack getItem() { ++ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); ++ } ++ ++ @Override ++ public void setItem(org.bukkit.inventory.ItemStack itemStack) { ++ FireworkMeta meta = getFireworkMeta(); ++ ItemStack nmsItem = itemStack == null ? ItemStack.EMPTY : CraftItemStack.asNMSCopy(itemStack); ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); ++ ++ applyFireworkEffect(meta); ++ } ++ ++ @Override ++ public int getTicksFlown() { ++ return this.getHandle().life; ++ } ++ ++ @Override ++ public void setTicksFlown(int ticks) { ++ this.getHandle().life = ticks; ++ } ++ ++ @Override ++ public int getTicksToDetonate() { ++ return this.getHandle().lifetime; ++ } ++ ++ @Override ++ public void setTicksToDetonate(int ticks) { ++ this.getHandle().lifetime = ticks; ++ } ++ ++ void applyFireworkEffect(FireworkMeta meta) { ++ ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ ++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); ++ } ++ // Paper end - Expose firework item directly + manually setting flight + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +index 6bfa984781a483d048ef4318761203c701d8a632..5e0c2c5094e1578162d1a50d50701fbd25e6d961 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +@@ -119,4 +119,15 @@ public class CraftFishHook extends CraftProjectile implements FishHook { + public HookState getState() { + return HookState.values()[this.getHandle().currentState.ordinal()]; + } ++ // Paper start - More FishHook API ++ @Override ++ public int getWaitTime() { ++ return this.getHandle().timeUntilLured; ++ } ++ ++ @Override ++ public void setWaitTime(int ticks) { ++ this.getHandle().timeUntilLured = ticks; ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +index 0db8aa840ea026d48215ac5dc80ffde5f12725b1..397e0df15a0e64e5bc522f62f3b327a5039ec4c8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +@@ -39,11 +39,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw + Validate.notNull(item, "ItemStack cannot be null."); + + // The ItemStack must be a potion. +- Validate.isTrue(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack must be a lingering or splash potion. This item stack was " + item.getType() + "."); ++ //Validate.isTrue(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack must be a lingering or splash potion. This item stack was " + item.getType() + "."); // Paper - Projectile API ++ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API + + this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); ++ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API + } + ++ // Paper start - Projectile API ++ @Override ++ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { ++ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItemRaw(), Material.SPLASH_POTION); ++ } ++ ++ @Override ++ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { ++ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); ++ CraftItemStack.applyMetaToItem(item, meta); ++ this.getHandle().setItem(item); // Reset item ++ } ++ ++ @Override ++ public void splash() { ++ this.getHandle().splash(null); ++ } ++ // Paper end + @Override + public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { + return (net.minecraft.world.entity.projectile.ThrownPotion) entity; +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index 04aabec62f0c89e70681af3846d73659f4c81360..c7c5f18cde7a4ad4dd821e452de3068c2e2187d1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -274,12 +274,20 @@ public final class CraftItemStack extends ItemStack { + public ItemMeta getItemMeta() { + return CraftItemStack.getItemMeta(this.handle); + } ++ // Paper start ++ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta meta) { ++ ((org.bukkit.craftbukkit.inventory.CraftMetaItem) meta).applyToItem(itemStack.getOrCreateTag()); ++ } + + public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { ++ return getItemMeta(item, CraftItemStack.getType(item)); ++ } ++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material) { ++ // Paper end + if (!CraftItemStack.hasItemMeta(item)) { +- return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); ++ return CraftItemFactory.instance().getItemMeta(material); // Paper + } +- switch (CraftItemStack.getType(item)) { ++ switch (material) { // Paper + case WRITTEN_BOOK: + return new CraftMetaBookSigned(item.getTag()); + case WRITABLE_BOOK: diff --git a/patches/server/0861-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0861-Fix-swamp-hut-cat-generation-deadlock.patch new file mode 100644 index 0000000000..ae8f7dbdb2 --- /dev/null +++ b/patches/server/0861-Fix-swamp-hut-cat-generation-deadlock.patch @@ -0,0 +1,62 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 12 Mar 2022 06:31:13 -0800 +Subject: [PATCH] Fix swamp hut cat generation deadlock + +The worldgen thread will attempt to get structure references +via the world's getChunkAt method, which is fine if the gen is +not cancelled - but if the chunk was unloaded, the call will block +indefinitely. Instead of using the world state, we use the already +supplied ServerLevelAccessor which will always have the chunk available. + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index 5ad968a2f1add27da0d6a858e683d5d771128092..e44352857272a2a4027c67bd25a28a9498b7bb49 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -361,7 +361,7 @@ public class Cat extends TamableAnimal { + }); + ServerLevel worldserver = world.getLevel(); + +- if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) { ++ if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, world).isValid()) { // Paper - fix deadlock + this.setCatVariant(CatVariant.ALL_BLACK); + this.setPersistenceRequired(); + } +diff --git a/src/main/java/net/minecraft/world/level/StructureManager.java b/src/main/java/net/minecraft/world/level/StructureManager.java +index e7445c1ff0d1e3e989fcd8d3630dd5269a64598d..64ca546a905d69a795bbdafb4f9860b31cd863d1 100644 +--- a/src/main/java/net/minecraft/world/level/StructureManager.java ++++ b/src/main/java/net/minecraft/world/level/StructureManager.java +@@ -43,7 +43,12 @@ public class StructureManager { + } + + public List startsForStructure(ChunkPos pos, Predicate predicate) { +- Map map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper start ++ return this.startsForStructure(pos, predicate, null); ++ } ++ public List startsForStructure(ChunkPos pos, Predicate predicate, @Nullable ServerLevelAccessor levelAccessor) { ++ Map map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); ++ // Paper end + ImmutableList.Builder builder = ImmutableList.builder(); + + for(Map.Entry entry : map.entrySet()) { +@@ -107,13 +112,18 @@ public class StructureManager { + } + + public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey structureTag) { ++ // Paper start ++ return this.getStructureWithPieceAt(pos, structureTag, null); ++ } ++ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey structureTag, @Nullable ServerLevelAccessor levelAccessor) { ++ // Paper end + Registry registry = this.registryAccess().registryOrThrow(Registry.STRUCTURE_REGISTRY); + + for(StructureStart structureStart : this.startsForStructure(new ChunkPos(pos), (structure) -> { + return registry.getHolder(registry.getId(structure)).map((holder) -> { + return holder.is(structureTag); + }).orElse(false); +- })) { ++ }, levelAccessor)) { // Paper + if (this.structureHasPieceAt(pos, structureStart)) { + return structureStart; + } diff --git a/patches/server/0861-More-Projectile-API.patch b/patches/server/0861-More-Projectile-API.patch deleted file mode 100644 index f55b7fe3b1..0000000000 --- a/patches/server/0861-More-Projectile-API.patch +++ /dev/null @@ -1,223 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Tue, 22 Jun 2021 23:41:11 -0400 -Subject: [PATCH] More Projectile API - -Co-authored-by: Nassim Jahnke - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -index fee09e6ff72cf1da389d5811dd005642cd50a5b4..4f276b2a86735a2c664738450ae0fbdd82031d4e 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java -@@ -98,6 +98,11 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); -+ // Paper start - More projectile API -+ this.splash(hitResult); -+ } -+ public void splash(@org.jetbrains.annotations.Nullable HitResult hitResult) { -+ // Paper end - More projectile API - if (!this.level.isClientSide) { - ItemStack itemstack = this.getItem(); - Potion potionregistry = PotionUtils.getPotion(itemstack); -@@ -110,7 +115,7 @@ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplie - if (this.isLingering()) { - this.makeAreaOfEffectCloud(itemstack, potionregistry); - } else { -- this.applySplash(list, hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null); -+ this.applySplash(list, hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null); // Paper - nullable hitResult - } - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -index be86114eac3975b82ca74d4d6ed3f0402a642e8a..93fd9e87de3078f50431b5d80540d4335d7c79e5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java -@@ -14,24 +14,26 @@ import org.bukkit.inventory.meta.FireworkMeta; - public class CraftFirework extends CraftProjectile implements Firework { - - private final Random random = new Random(); -- private final CraftItemStack item; -+ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. - - public CraftFirework(CraftServer server, FireworkRocketEntity entity) { - super(server, entity); - -- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -- -- if (item.isEmpty()) { -- item = new ItemStack(Items.FIREWORK_ROCKET); -- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -- } -- -- this.item = CraftItemStack.asCraftMirror(item); -- -- // Ensure the item is a firework... -- if (this.item.getType() != Material.FIREWORK_ROCKET) { -- this.item.setType(Material.FIREWORK_ROCKET); -- } -+// Paper Start - Expose firework item directly -+// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM); -+// -+// if (item.isEmpty()) { -+// item = new ItemStack(Items.FIREWORK_ROCKET); -+// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+// } -+// -+// this.item = CraftItemStack.asCraftMirror(item); -+// -+// // Ensure the item is a firework... -+// if (this.item.getType() != Material.FIREWORK_ROCKET) { -+// this.item.setType(Material.FIREWORK_ROCKET); -+// } -+ // Paper End - Expose firework item directly - } - - @Override -@@ -51,12 +53,12 @@ public class CraftFirework extends CraftProjectile implements Firework { - - @Override - public FireworkMeta getFireworkMeta() { -- return (FireworkMeta) this.item.getItemMeta(); -+ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), Material.FIREWORK_ROCKET); // Paper - Expose firework item directly - } - - @Override - public void setFireworkMeta(FireworkMeta meta) { -- this.item.setItemMeta(meta); -+ applyFireworkEffect(meta); // Paper - Expose firework item directly - - // Copied from EntityFireworks constructor, update firework lifetime/power - this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7); -@@ -91,4 +93,46 @@ public class CraftFirework extends CraftProjectile implements Firework { - return boostedEntity != null ? (org.bukkit.entity.LivingEntity) boostedEntity.getBukkitEntity() : null; - } - // Paper end -+ // Paper start - Expose firework item directly + manually setting flight -+ @Override -+ public org.bukkit.inventory.ItemStack getItem() { -+ return CraftItemStack.asBukkitCopy(this.getHandle().getItem()); -+ } -+ -+ @Override -+ public void setItem(org.bukkit.inventory.ItemStack itemStack) { -+ FireworkMeta meta = getFireworkMeta(); -+ ItemStack nmsItem = itemStack == null ? ItemStack.EMPTY : CraftItemStack.asNMSCopy(itemStack); -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem); -+ -+ applyFireworkEffect(meta); -+ } -+ -+ @Override -+ public int getTicksFlown() { -+ return this.getHandle().life; -+ } -+ -+ @Override -+ public void setTicksFlown(int ticks) { -+ this.getHandle().life = ticks; -+ } -+ -+ @Override -+ public int getTicksToDetonate() { -+ return this.getHandle().lifetime; -+ } -+ -+ @Override -+ public void setTicksToDetonate(int ticks) { -+ this.getHandle().lifetime = ticks; -+ } -+ -+ void applyFireworkEffect(FireworkMeta meta) { -+ ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ -+ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item); -+ } -+ // Paper end - Expose firework item directly + manually setting flight - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -index 6bfa984781a483d048ef4318761203c701d8a632..5e0c2c5094e1578162d1a50d50701fbd25e6d961 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -@@ -119,4 +119,15 @@ public class CraftFishHook extends CraftProjectile implements FishHook { - public HookState getState() { - return HookState.values()[this.getHandle().currentState.ordinal()]; - } -+ // Paper start - More FishHook API -+ @Override -+ public int getWaitTime() { -+ return this.getHandle().timeUntilLured; -+ } -+ -+ @Override -+ public void setWaitTime(int ticks) { -+ this.getHandle().timeUntilLured = ticks; -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -index 0db8aa840ea026d48215ac5dc80ffde5f12725b1..397e0df15a0e64e5bc522f62f3b327a5039ec4c8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -@@ -39,11 +39,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw - Validate.notNull(item, "ItemStack cannot be null."); - - // The ItemStack must be a potion. -- Validate.isTrue(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack must be a lingering or splash potion. This item stack was " + item.getType() + "."); -+ //Validate.isTrue(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack must be a lingering or splash potion. This item stack was " + item.getType() + "."); // Paper - Projectile API -+ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API - - this.getHandle().setItem(CraftItemStack.asNMSCopy(item)); -+ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API - } - -+ // Paper start - Projectile API -+ @Override -+ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() { -+ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItemRaw(), Material.SPLASH_POTION); -+ } -+ -+ @Override -+ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) { -+ net.minecraft.world.item.ItemStack item = this.getHandle().getItem(); -+ CraftItemStack.applyMetaToItem(item, meta); -+ this.getHandle().setItem(item); // Reset item -+ } -+ -+ @Override -+ public void splash() { -+ this.getHandle().splash(null); -+ } -+ // Paper end - @Override - public net.minecraft.world.entity.projectile.ThrownPotion getHandle() { - return (net.minecraft.world.entity.projectile.ThrownPotion) entity; -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 04aabec62f0c89e70681af3846d73659f4c81360..c7c5f18cde7a4ad4dd821e452de3068c2e2187d1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -274,12 +274,20 @@ public final class CraftItemStack extends ItemStack { - public ItemMeta getItemMeta() { - return CraftItemStack.getItemMeta(this.handle); - } -+ // Paper start -+ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta meta) { -+ ((org.bukkit.craftbukkit.inventory.CraftMetaItem) meta).applyToItem(itemStack.getOrCreateTag()); -+ } - - public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) { -+ return getItemMeta(item, CraftItemStack.getType(item)); -+ } -+ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, Material material) { -+ // Paper end - if (!CraftItemStack.hasItemMeta(item)) { -- return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item)); -+ return CraftItemFactory.instance().getItemMeta(material); // Paper - } -- switch (CraftItemStack.getType(item)) { -+ switch (material) { // Paper - case WRITTEN_BOOK: - return new CraftMetaBookSigned(item.getTag()); - case WRITABLE_BOOK: diff --git a/patches/server/0862-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0862-Don-t-allow-vehicle-movement-from-players-while-tele.patch new file mode 100644 index 0000000000..77c8b0c110 --- /dev/null +++ b/patches/server/0862-Don-t-allow-vehicle-movement-from-players-while-tele.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 14 Mar 2022 12:35:37 -0700 +Subject: [PATCH] Don't allow vehicle movement from players while teleporting + +Bring the vehicle move packet behavior in line with the +regular player move packet. + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index b82d6ebb30c0fdc49f21d993c6a1affbecc27af7..cf3f9d225e4c4114fd14979ac5f7bf8ea45c3765 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -585,6 +585,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } else { + Entity entity = this.player.getRootVehicle(); + ++ // Paper start ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { ++ return; ++ } ++ // Paper end ++ + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { + ServerLevel worldserver = this.player.getLevel(); + double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER diff --git a/patches/server/0862-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0862-Fix-swamp-hut-cat-generation-deadlock.patch deleted file mode 100644 index ae8f7dbdb2..0000000000 --- a/patches/server/0862-Fix-swamp-hut-cat-generation-deadlock.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 12 Mar 2022 06:31:13 -0800 -Subject: [PATCH] Fix swamp hut cat generation deadlock - -The worldgen thread will attempt to get structure references -via the world's getChunkAt method, which is fine if the gen is -not cancelled - but if the chunk was unloaded, the call will block -indefinitely. Instead of using the world state, we use the already -supplied ServerLevelAccessor which will always have the chunk available. - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 5ad968a2f1add27da0d6a858e683d5d771128092..e44352857272a2a4027c67bd25a28a9498b7bb49 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -361,7 +361,7 @@ public class Cat extends TamableAnimal { - }); - ServerLevel worldserver = world.getLevel(); - -- if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) { -+ if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, world).isValid()) { // Paper - fix deadlock - this.setCatVariant(CatVariant.ALL_BLACK); - this.setPersistenceRequired(); - } -diff --git a/src/main/java/net/minecraft/world/level/StructureManager.java b/src/main/java/net/minecraft/world/level/StructureManager.java -index e7445c1ff0d1e3e989fcd8d3630dd5269a64598d..64ca546a905d69a795bbdafb4f9860b31cd863d1 100644 ---- a/src/main/java/net/minecraft/world/level/StructureManager.java -+++ b/src/main/java/net/minecraft/world/level/StructureManager.java -@@ -43,7 +43,12 @@ public class StructureManager { - } - - public List startsForStructure(ChunkPos pos, Predicate predicate) { -- Map map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); -+ // Paper start -+ return this.startsForStructure(pos, predicate, null); -+ } -+ public List startsForStructure(ChunkPos pos, Predicate predicate, @Nullable ServerLevelAccessor levelAccessor) { -+ Map map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); -+ // Paper end - ImmutableList.Builder builder = ImmutableList.builder(); - - for(Map.Entry entry : map.entrySet()) { -@@ -107,13 +112,18 @@ public class StructureManager { - } - - public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey structureTag) { -+ // Paper start -+ return this.getStructureWithPieceAt(pos, structureTag, null); -+ } -+ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey structureTag, @Nullable ServerLevelAccessor levelAccessor) { -+ // Paper end - Registry registry = this.registryAccess().registryOrThrow(Registry.STRUCTURE_REGISTRY); - - for(StructureStart structureStart : this.startsForStructure(new ChunkPos(pos), (structure) -> { - return registry.getHolder(registry.getId(structure)).map((holder) -> { - return holder.is(structureTag); - }).orElse(false); -- })) { -+ }, levelAccessor)) { // Paper - if (this.structureHasPieceAt(pos, structureStart)) { - return structureStart; - } diff --git a/patches/server/0863-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0863-Don-t-allow-vehicle-movement-from-players-while-tele.patch deleted file mode 100644 index c3a6450e6c..0000000000 --- a/patches/server/0863-Don-t-allow-vehicle-movement-from-players-while-tele.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 14 Mar 2022 12:35:37 -0700 -Subject: [PATCH] Don't allow vehicle movement from players while teleporting - -Bring the vehicle move packet behavior in line with the -regular player move packet. - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 26402aa8879e4e50c619c1e9d8e30ef49c3b8a34..faf63674f8c1ebc5f8dea1a937811f0d9e9d1d96 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -585,6 +585,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } else { - Entity entity = this.player.getRootVehicle(); - -+ // Paper start -+ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { -+ return; -+ } -+ // Paper end -+ - if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { - ServerLevel worldserver = this.player.getLevel(); - double d0 = entity.getX();final double fromX = d0; // Paper - OBFHELPER diff --git a/patches/server/0863-Implement-getComputedBiome-API.patch b/patches/server/0863-Implement-getComputedBiome-API.patch new file mode 100644 index 0000000000..b6a274bb77 --- /dev/null +++ b/patches/server/0863-Implement-getComputedBiome-API.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Mon, 14 Mar 2022 22:46:05 -0700 +Subject: [PATCH] Implement getComputedBiome API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 5628940cd3c3566c5db2beda506d4f20b6e3cbae..6445c2e4c97860e1c98f5263188d309cf55936f0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -211,6 +211,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + return CraftBlock.biomeBaseToBiome(this.getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2)); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome(int x, int y, int z) { ++ return CraftBlock.biomeBaseToBiome(this.getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), this.getHandle().getBiome(new BlockPos(x, y, z))); ++ } ++ // Paper end ++ + @Override + public void setBiome(Location location, Biome biome) { + this.setBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), biome); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 0030ac394d44acddcd2fc716277ae2b509a378af..22356c2380741dd811810420127e247e46b0b8e2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -341,6 +341,13 @@ public class CraftBlock implements Block { + return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome() { ++ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); ++ } ++ // Paper end ++ + @Override + public void setBiome(Biome bio) { + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); +diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +index 392701b0022e05d0fd03ee5836fd18f00502f028..b01904021bd4f485aaf03d1d7634b56f134d3099 100644 +--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java ++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java +@@ -166,6 +166,14 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe + return super.getBiome(x, y, z); + } + ++ // Paper start ++ @Override ++ public Biome getComputedBiome(int x, int y, int z) { ++ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); ++ return super.getComputedBiome(x, y, z); ++ } ++ // Paper end ++ + @Override + public void setBiome(int x, int y, int z, Holder biomeBase) { + Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); diff --git a/patches/server/0864-Implement-getComputedBiome-API.patch b/patches/server/0864-Implement-getComputedBiome-API.patch deleted file mode 100644 index b6a274bb77..0000000000 --- a/patches/server/0864-Implement-getComputedBiome-API.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Mon, 14 Mar 2022 22:46:05 -0700 -Subject: [PATCH] Implement getComputedBiome API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 5628940cd3c3566c5db2beda506d4f20b6e3cbae..6445c2e4c97860e1c98f5263188d309cf55936f0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -211,6 +211,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - return CraftBlock.biomeBaseToBiome(this.getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2)); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome(int x, int y, int z) { -+ return CraftBlock.biomeBaseToBiome(this.getHandle().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), this.getHandle().getBiome(new BlockPos(x, y, z))); -+ } -+ // Paper end -+ - @Override - public void setBiome(Location location, Biome biome) { - this.setBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), biome); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 0030ac394d44acddcd2fc716277ae2b509a378af..22356c2380741dd811810420127e247e46b0b8e2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -341,6 +341,13 @@ public class CraftBlock implements Block { - return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome() { -+ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); -+ } -+ // Paper end -+ - @Override - public void setBiome(Biome bio) { - this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); -diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -index 392701b0022e05d0fd03ee5836fd18f00502f028..b01904021bd4f485aaf03d1d7634b56f134d3099 100644 ---- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -+++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java -@@ -166,6 +166,14 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe - return super.getBiome(x, y, z); - } - -+ // Paper start -+ @Override -+ public Biome getComputedBiome(int x, int y, int z) { -+ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); -+ return super.getComputedBiome(x, y, z); -+ } -+ // Paper end -+ - @Override - public void setBiome(int x, int y, int z, Holder biomeBase) { - Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z); diff --git a/patches/server/0864-Make-some-itemstacks-nonnull.patch b/patches/server/0864-Make-some-itemstacks-nonnull.patch new file mode 100644 index 0000000000..e2cba95fe4 --- /dev/null +++ b/patches/server/0864-Make-some-itemstacks-nonnull.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 15 Mar 2022 01:38:15 -0700 +Subject: [PATCH] Make some itemstacks nonnull + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +index d6a228473ca6f425757683a4b17b035a53ab117f..8801d3f7ff6d2ff810f3e34a821dfb659c03f844 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +@@ -156,13 +156,13 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i + case OFF_HAND: + return this.getItemInOffHand(); + case FEET: +- return this.getBoots(); ++ return java.util.Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case LEGS: +- return this.getLeggings(); ++ return java.util.Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case CHEST: +- return this.getChestplate(); ++ return java.util.Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + case HEAD: +- return this.getHelmet(); ++ return java.util.Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull + default: + throw new IllegalArgumentException("Not implemented. This is a bug"); + } diff --git a/patches/server/0865-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch b/patches/server/0865-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch new file mode 100644 index 0000000000..fe6855e900 --- /dev/null +++ b/patches/server/0865-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sat, 26 Feb 2022 13:27:31 -0700 +Subject: [PATCH] Add debug for invalid GameProfiles on skull blocks/items + +Improves the error message for placed in world skull blocks by default, +also adds 'Paper.debugInvalidSkullProfiles' system property which can be +set to 'true' for extra debug info (trace of updateGameprofile caller). + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +index c5d5d90d10b30f30d1262367b3d75df43fbdb231..e017020b7630f54c506d93ab0504c6f3299fc80b 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java +@@ -114,13 +114,28 @@ public class SkullBlockEntity extends BlockEntity { + updateGameprofile(this.owner, (owner) -> { + this.owner = owner; + this.setChanged(); ++ // Paper start ++ }, () -> { ++ final @Nullable Level level = this.getLevel(); ++ return "SkullBlockEntity at " + this.getBlockPos() + (level == null ? "" : (" in level: " + level.dimension().location())); ++ // Paper end + }); + } + + public static void updateGameprofile(@Nullable GameProfile owner, Consumer callback) { ++ // Paper start ++ updateGameprofile(owner, callback, null); ++ } ++ ++ private static final boolean DEBUG_INVALID_SKULL_PROFILES = Boolean.getBoolean("Paper.debugInvalidSkullProfiles"); ++ ++ public static void updateGameprofile(@Nullable GameProfile owner, Consumer callback, final @Nullable java.util.function.Supplier debugInfo) { + if (owner != null && !StringUtil.isNullOrEmpty(owner.getName()) && (!owner.isComplete() || !owner.getProperties().containsKey("textures")) && profileCache != null && sessionService != null) { ++ final @Nullable Throwable trace = DEBUG_INVALID_SKULL_PROFILES ? new Throwable("updateGameprofile caller debug trace") : null; + profileCache.getAsync(owner.getName(), (profile) -> { + Util.PROFILE_EXECUTOR.execute(() -> { // Paper - not a good idea to use BLOCKING OPERATIONS on the worldgen executor ++ try { ++ // Paper end + Util.ifElse(profile, (profilex) -> { + Property property = Iterables.getFirst(profilex.getProperties().get("textures"), (Property)null); + if (property == null) { +@@ -137,6 +152,20 @@ public class SkullBlockEntity extends BlockEntity { + callback.accept(owner); + }); + }); ++ // Paper start ++ } catch (final Exception ex) { ++ if (trace != null) { ++ ex.addSuppressed(trace); ++ } ++ final String ownerMessage = "Original profile: '" + owner + "'"; ++ final String debugMessage = " Run with -DPaper.debugInvalidSkullProfiles=true for further debug information."; ++ final String message = ownerMessage + (trace == null ? debugMessage : ""); ++ if (debugInfo == null) { ++ throw new RuntimeException(message, ex); ++ } ++ throw new RuntimeException(debugInfo.get() + " " + message, ex); ++ } ++ // Paper end + }); + }); + } else { diff --git a/patches/server/0865-Make-some-itemstacks-nonnull.patch b/patches/server/0865-Make-some-itemstacks-nonnull.patch deleted file mode 100644 index e2cba95fe4..0000000000 --- a/patches/server/0865-Make-some-itemstacks-nonnull.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 15 Mar 2022 01:38:15 -0700 -Subject: [PATCH] Make some itemstacks nonnull - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -index d6a228473ca6f425757683a4b17b035a53ab117f..8801d3f7ff6d2ff810f3e34a821dfb659c03f844 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java -@@ -156,13 +156,13 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i - case OFF_HAND: - return this.getItemInOffHand(); - case FEET: -- return this.getBoots(); -+ return java.util.Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case LEGS: -- return this.getLeggings(); -+ return java.util.Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case CHEST: -- return this.getChestplate(); -+ return java.util.Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - case HEAD: -- return this.getHelmet(); -+ return java.util.Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull - default: - throw new IllegalArgumentException("Not implemented. This is a bug"); - } diff --git a/patches/server/0866-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch b/patches/server/0866-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch deleted file mode 100644 index fe6855e900..0000000000 --- a/patches/server/0866-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sat, 26 Feb 2022 13:27:31 -0700 -Subject: [PATCH] Add debug for invalid GameProfiles on skull blocks/items - -Improves the error message for placed in world skull blocks by default, -also adds 'Paper.debugInvalidSkullProfiles' system property which can be -set to 'true' for extra debug info (trace of updateGameprofile caller). - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java -index c5d5d90d10b30f30d1262367b3d75df43fbdb231..e017020b7630f54c506d93ab0504c6f3299fc80b 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java -@@ -114,13 +114,28 @@ public class SkullBlockEntity extends BlockEntity { - updateGameprofile(this.owner, (owner) -> { - this.owner = owner; - this.setChanged(); -+ // Paper start -+ }, () -> { -+ final @Nullable Level level = this.getLevel(); -+ return "SkullBlockEntity at " + this.getBlockPos() + (level == null ? "" : (" in level: " + level.dimension().location())); -+ // Paper end - }); - } - - public static void updateGameprofile(@Nullable GameProfile owner, Consumer callback) { -+ // Paper start -+ updateGameprofile(owner, callback, null); -+ } -+ -+ private static final boolean DEBUG_INVALID_SKULL_PROFILES = Boolean.getBoolean("Paper.debugInvalidSkullProfiles"); -+ -+ public static void updateGameprofile(@Nullable GameProfile owner, Consumer callback, final @Nullable java.util.function.Supplier debugInfo) { - if (owner != null && !StringUtil.isNullOrEmpty(owner.getName()) && (!owner.isComplete() || !owner.getProperties().containsKey("textures")) && profileCache != null && sessionService != null) { -+ final @Nullable Throwable trace = DEBUG_INVALID_SKULL_PROFILES ? new Throwable("updateGameprofile caller debug trace") : null; - profileCache.getAsync(owner.getName(), (profile) -> { - Util.PROFILE_EXECUTOR.execute(() -> { // Paper - not a good idea to use BLOCKING OPERATIONS on the worldgen executor -+ try { -+ // Paper end - Util.ifElse(profile, (profilex) -> { - Property property = Iterables.getFirst(profilex.getProperties().get("textures"), (Property)null); - if (property == null) { -@@ -137,6 +152,20 @@ public class SkullBlockEntity extends BlockEntity { - callback.accept(owner); - }); - }); -+ // Paper start -+ } catch (final Exception ex) { -+ if (trace != null) { -+ ex.addSuppressed(trace); -+ } -+ final String ownerMessage = "Original profile: '" + owner + "'"; -+ final String debugMessage = " Run with -DPaper.debugInvalidSkullProfiles=true for further debug information."; -+ final String message = ownerMessage + (trace == null ? debugMessage : ""); -+ if (debugInfo == null) { -+ throw new RuntimeException(message, ex); -+ } -+ throw new RuntimeException(debugInfo.get() + " " + message, ex); -+ } -+ // Paper end - }); - }); - } else { diff --git a/patches/server/0866-Implement-enchantWithLevels-API.patch b/patches/server/0866-Implement-enchantWithLevels-API.patch new file mode 100644 index 0000000000..204d4534fb --- /dev/null +++ b/patches/server/0866-Implement-enchantWithLevels-API.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Wed, 16 Mar 2022 20:35:21 -0700 +Subject: [PATCH] Implement enchantWithLevels API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index 4a8ac558d308c4e3bc63cdd8d7071a3f9ff3aa81..ce64286ac5b836283318ac1ac0bd4afb29db9bb7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -376,6 +376,21 @@ public final class CraftItemFactory implements ItemFactory { + } + + // Paper start ++ @Override ++ public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, java.util.Random random) { ++ Validate.notNull(itemStack, "Argument 'itemStack' must not be null"); ++ Validate.isTrue(itemStack.getType() != Material.AIR, "Argument 'itemStack' must not be of type AIR"); ++ Validate.isTrue(itemStack.getAmount() > 0, "Argument 'itemStack' amount must be greater than 0"); ++ Validate.isTrue(levels > 0 && levels <= 30, "Argument 'levels' must be in range [1, 30] (attempted " + levels + ")"); ++ Validate.notNull(random, "Argument 'random' must not be null"); ++ final net.minecraft.world.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack); ++ if (internalStack.tag != null) { ++ internalStack.tag.remove(net.minecraft.world.item.ItemStack.TAG_ENCH); ++ } ++ final net.minecraft.world.item.ItemStack enchanted = net.minecraft.world.item.enchantment.EnchantmentHelper.enchantItem(new org.bukkit.craftbukkit.util.RandomSourceWrapper(random), internalStack, levels, allowTreasure); ++ return CraftItemStack.asCraftMirror(enchanted); ++ } ++ + @Override + public net.kyori.adventure.text.event.HoverEvent asHoverEvent(final ItemStack item, final java.util.function.UnaryOperator op) { + final net.minecraft.nbt.CompoundTag tag = CraftItemStack.asNMSCopy(item).getTag(); diff --git a/patches/server/0867-Fix-saving-in-unloadWorld.patch b/patches/server/0867-Fix-saving-in-unloadWorld.patch new file mode 100644 index 0000000000..ae3e8954eb --- /dev/null +++ b/patches/server/0867-Fix-saving-in-unloadWorld.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Philip Kelley +Date: Wed, 16 Mar 2022 12:05:59 +0000 +Subject: [PATCH] Fix saving in unloadWorld + +Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 669fb0206a09377a8682325bc4bd744d285791f6..6eb74dcf4b1ed5c04f6890a07fbac7b9fff0d161 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1289,7 +1289,7 @@ public final class CraftServer implements Server { + + try { + if (save) { +- handle.save(null, true, true); ++ handle.save(null, true, false); // Paper - don't disable saving + } + + handle.getChunkSource().close(save); diff --git a/patches/server/0867-Implement-enchantWithLevels-API.patch b/patches/server/0867-Implement-enchantWithLevels-API.patch deleted file mode 100644 index 204d4534fb..0000000000 --- a/patches/server/0867-Implement-enchantWithLevels-API.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Wed, 16 Mar 2022 20:35:21 -0700 -Subject: [PATCH] Implement enchantWithLevels API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index 4a8ac558d308c4e3bc63cdd8d7071a3f9ff3aa81..ce64286ac5b836283318ac1ac0bd4afb29db9bb7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -376,6 +376,21 @@ public final class CraftItemFactory implements ItemFactory { - } - - // Paper start -+ @Override -+ public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, java.util.Random random) { -+ Validate.notNull(itemStack, "Argument 'itemStack' must not be null"); -+ Validate.isTrue(itemStack.getType() != Material.AIR, "Argument 'itemStack' must not be of type AIR"); -+ Validate.isTrue(itemStack.getAmount() > 0, "Argument 'itemStack' amount must be greater than 0"); -+ Validate.isTrue(levels > 0 && levels <= 30, "Argument 'levels' must be in range [1, 30] (attempted " + levels + ")"); -+ Validate.notNull(random, "Argument 'random' must not be null"); -+ final net.minecraft.world.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack); -+ if (internalStack.tag != null) { -+ internalStack.tag.remove(net.minecraft.world.item.ItemStack.TAG_ENCH); -+ } -+ final net.minecraft.world.item.ItemStack enchanted = net.minecraft.world.item.enchantment.EnchantmentHelper.enchantItem(new org.bukkit.craftbukkit.util.RandomSourceWrapper(random), internalStack, levels, allowTreasure); -+ return CraftItemStack.asCraftMirror(enchanted); -+ } -+ - @Override - public net.kyori.adventure.text.event.HoverEvent asHoverEvent(final ItemStack item, final java.util.function.UnaryOperator op) { - final net.minecraft.nbt.CompoundTag tag = CraftItemStack.asNMSCopy(item).getTag(); diff --git a/patches/server/0868-Buffer-OOB-setBlock-calls.patch b/patches/server/0868-Buffer-OOB-setBlock-calls.patch new file mode 100644 index 0000000000..62e5606e17 --- /dev/null +++ b/patches/server/0868-Buffer-OOB-setBlock-calls.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 19 Mar 2022 12:12:22 +0000 +Subject: [PATCH] Buffer OOB setBlock calls + +lets debug mode throw a trace in order to potentially see where +such calls are cascading from easier, but, generally, if you see one setBlock +call, you're gonna see more, and this just potentially causes a flood of logs +which can cause issues for slower terminals, etc. + +We can limit the flood by just allowing one for a single gen region, +we'll also only gen a trace for the first one, I see no real pressing need +to generate more, given that that would *massively* negate this patch otherwise + +diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +index 51d3150e732f95be13f5f54d994dab1fa89ed3f2..80c1e0e47818486a68e0114b063395290365346b 100644 +--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java ++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java +@@ -274,6 +274,7 @@ public class WorldGenRegion implements WorldGenLevel { + } + } + ++ private boolean hasSetFarWarned = false; // Paper + @Override + public boolean ensureCanWrite(BlockPos pos) { + int i = SectionPos.blockToSectionCoord(pos.getX()); +@@ -293,7 +294,15 @@ public class WorldGenRegion implements WorldGenLevel { + + return true; + } else { ++ // Paper start ++ if (!hasSetFarWarned) { + Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + pos + ", status: " + this.generatingStatus + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); ++ hasSetFarWarned = true; ++ if (this.getServer() != null && this.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); ++ } ++ } ++ // Paper end + return false; + } + } diff --git a/patches/server/0868-Fix-saving-in-unloadWorld.patch b/patches/server/0868-Fix-saving-in-unloadWorld.patch deleted file mode 100644 index 1e8c1b11d3..0000000000 --- a/patches/server/0868-Fix-saving-in-unloadWorld.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Philip Kelley -Date: Wed, 16 Mar 2022 12:05:59 +0000 -Subject: [PATCH] Fix saving in unloadWorld - -Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 9b24f38406a1017dca430bbaeed4bf4227677972..dc3a0acfd0b4e5210994f5beb59df4351c67af69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1289,7 +1289,7 @@ public final class CraftServer implements Server { - - try { - if (save) { -- handle.save(null, true, true); -+ handle.save(null, true, false); // Paper - don't disable saving - } - - handle.getChunkSource().close(save); diff --git a/patches/server/0869-Add-TameableDeathMessageEvent.patch b/patches/server/0869-Add-TameableDeathMessageEvent.patch new file mode 100644 index 0000000000..8bbbaffa2c --- /dev/null +++ b/patches/server/0869-Add-TameableDeathMessageEvent.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Mon, 21 Jun 2021 21:24:45 -0400 +Subject: [PATCH] Add TameableDeathMessageEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +index 59878c68d9e25a6f4ece26566d8e2b5108536ae8..acc25fb309568864dd7b53ad6a7a3ee6ff18e82a 100644 +--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java ++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java +@@ -208,7 +208,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { + @Override + public void die(DamageSource damageSource) { + if (!this.level.isClientSide && this.level.getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES) && this.getOwner() instanceof ServerPlayer) { +- this.getOwner().sendSystemMessage(this.getCombatTracker().getDeathMessage()); ++ // Paper start - TameableDeathMessageEvent ++ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage())); ++ if (event.callEvent()) { ++ this.getOwner().sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage())); ++ } ++ // Paper end - TameableDeathMessageEvent + } + + super.die(damageSource); diff --git a/patches/server/0869-Buffer-OOB-setBlock-calls.patch b/patches/server/0869-Buffer-OOB-setBlock-calls.patch deleted file mode 100644 index 62e5606e17..0000000000 --- a/patches/server/0869-Buffer-OOB-setBlock-calls.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 19 Mar 2022 12:12:22 +0000 -Subject: [PATCH] Buffer OOB setBlock calls - -lets debug mode throw a trace in order to potentially see where -such calls are cascading from easier, but, generally, if you see one setBlock -call, you're gonna see more, and this just potentially causes a flood of logs -which can cause issues for slower terminals, etc. - -We can limit the flood by just allowing one for a single gen region, -we'll also only gen a trace for the first one, I see no real pressing need -to generate more, given that that would *massively* negate this patch otherwise - -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 51d3150e732f95be13f5f54d994dab1fa89ed3f2..80c1e0e47818486a68e0114b063395290365346b 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -274,6 +274,7 @@ public class WorldGenRegion implements WorldGenLevel { - } - } - -+ private boolean hasSetFarWarned = false; // Paper - @Override - public boolean ensureCanWrite(BlockPos pos) { - int i = SectionPos.blockToSectionCoord(pos.getX()); -@@ -293,7 +294,15 @@ public class WorldGenRegion implements WorldGenLevel { - - return true; - } else { -+ // Paper start -+ if (!hasSetFarWarned) { - Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + pos + ", status: " + this.generatingStatus + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); -+ hasSetFarWarned = true; -+ if (this.getServer() != null && this.getServer().isDebugging()) { -+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); -+ } -+ } -+ // Paper end - return false; - } - } diff --git a/patches/server/0870-Add-TameableDeathMessageEvent.patch b/patches/server/0870-Add-TameableDeathMessageEvent.patch deleted file mode 100644 index 8bbbaffa2c..0000000000 --- a/patches/server/0870-Add-TameableDeathMessageEvent.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Mon, 21 Jun 2021 21:24:45 -0400 -Subject: [PATCH] Add TameableDeathMessageEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -index 59878c68d9e25a6f4ece26566d8e2b5108536ae8..acc25fb309568864dd7b53ad6a7a3ee6ff18e82a 100644 ---- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java -@@ -208,7 +208,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity { - @Override - public void die(DamageSource damageSource) { - if (!this.level.isClientSide && this.level.getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES) && this.getOwner() instanceof ServerPlayer) { -- this.getOwner().sendSystemMessage(this.getCombatTracker().getDeathMessage()); -+ // Paper start - TameableDeathMessageEvent -+ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage())); -+ if (event.callEvent()) { -+ this.getOwner().sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage())); -+ } -+ // Paper end - TameableDeathMessageEvent - } - - super.die(damageSource); diff --git a/patches/server/0870-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch b/patches/server/0870-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch new file mode 100644 index 0000000000..8ecf55d257 --- /dev/null +++ b/patches/server/0870-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SoSeDiK +Date: Mon, 21 Mar 2022 20:00:53 +0200 +Subject: [PATCH] Fix new block data for EntityChangeBlockEvent when sheep eats + grass block + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +index cc44e975aef8e9dabfbc740dd5a0db3a55c5831e..80aa539f7c6a6ee44338de084cdcdf5fb4ef996a 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +@@ -79,7 +79,7 @@ public class EatBlockGoal extends Goal { + + if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { + // CraftBukkit +- if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { ++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // Paper - Fix wrong block state + this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); + this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); + } diff --git a/patches/server/0871-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch b/patches/server/0871-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch deleted file mode 100644 index 8ecf55d257..0000000000 --- a/patches/server/0871-Fix-new-block-data-for-EntityChangeBlockEvent-when-s.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: SoSeDiK -Date: Mon, 21 Mar 2022 20:00:53 +0200 -Subject: [PATCH] Fix new block data for EntityChangeBlockEvent when sheep eats - grass block - - -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -index cc44e975aef8e9dabfbc740dd5a0db3a55c5831e..80aa539f7c6a6ee44338de084cdcdf5fb4ef996a 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java -@@ -79,7 +79,7 @@ public class EatBlockGoal extends Goal { - - if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { - // CraftBukkit -- if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { -+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // Paper - Fix wrong block state - this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); - this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); - } diff --git a/patches/server/0871-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0871-fix-player-loottables-running-when-mob-loot-gamerule.patch new file mode 100644 index 0000000000..5030bb2520 --- /dev/null +++ b/patches/server/0871-fix-player-loottables-running-when-mob-loot-gamerule.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 09:50:40 -0700 +Subject: [PATCH] fix player loottables running when mob loot gamerule is false + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 18c3d4aecf498f78040c27336d2ea56fd911d034..3f3ebe28c669419091fd20c18185c61712e7f1e8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -861,12 +861,14 @@ public class ServerPlayer extends Player { + } + } + } ++ if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - preserve this check from vanilla + // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) + this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); + for (org.bukkit.inventory.ItemStack item : this.drops) { + loot.add(item); + } + this.drops.clear(); // SPIGOT-5188: make sure to clear ++ } // Paper + + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + diff --git a/patches/server/0872-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0872-Ensure-entity-passenger-world-matches-ridden-entity.patch new file mode 100644 index 0000000000..16d39906af --- /dev/null +++ b/patches/server/0872-Ensure-entity-passenger-world-matches-ridden-entity.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:11:37 -0700 +Subject: [PATCH] Ensure entity passenger world matches ridden entity + +Bad plugins doing this would cause some obvious problems... + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index f1e471fee49d0213a97251be1b6793d5fb2165f2..a0c84b3fc264ef25ef1bcdeba482ac23461411f3 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2684,6 +2684,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } + + protected boolean addPassenger(Entity entity) { // CraftBukkit ++ // Paper start ++ if (entity.level != this.level) { ++ LOGGER.error("Entity passenger world must match, cannot add " + entity + " as passenger to " + this, new Throwable()); ++ return false; ++ } ++ // Paper end + 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)"); diff --git a/patches/server/0872-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0872-fix-player-loottables-running-when-mob-loot-gamerule.patch deleted file mode 100644 index 5030bb2520..0000000000 --- a/patches/server/0872-fix-player-loottables-running-when-mob-loot-gamerule.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 09:50:40 -0700 -Subject: [PATCH] fix player loottables running when mob loot gamerule is false - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 18c3d4aecf498f78040c27336d2ea56fd911d034..3f3ebe28c669419091fd20c18185c61712e7f1e8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -861,12 +861,14 @@ public class ServerPlayer extends Player { - } - } - } -+ if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - preserve this check from vanilla - // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) - this.dropFromLootTable(damageSource, this.lastHurtByPlayerTime > 0); - for (org.bukkit.inventory.ItemStack item : this.drops) { - loot.add(item); - } - this.drops.clear(); // SPIGOT-5188: make sure to clear -+ } // Paper - - Component defaultMessage = this.getCombatTracker().getDeathMessage(); - diff --git a/patches/server/0873-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0873-Ensure-entity-passenger-world-matches-ridden-entity.patch deleted file mode 100644 index c2917d9223..0000000000 --- a/patches/server/0873-Ensure-entity-passenger-world-matches-ridden-entity.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 31 Mar 2022 05:11:37 -0700 -Subject: [PATCH] Ensure entity passenger world matches ridden entity - -Bad plugins doing this would cause some obvious problems... - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 473b7c0bd9c49192041e3a6e4c8a9d760344a44a..82ccf8eacd5cac82ae2f44d38fc05f606ff50f8e 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2670,6 +2670,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } - - protected boolean addPassenger(Entity entity) { // CraftBukkit -+ // Paper start -+ if (entity.level != this.level) { -+ LOGGER.error("Entity passenger world must match, cannot add " + entity + " as passenger to " + this, new Throwable()); -+ return false; -+ } -+ // Paper end - 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)"); diff --git a/patches/server/0873-Guard-against-invalid-entity-positions.patch b/patches/server/0873-Guard-against-invalid-entity-positions.patch new file mode 100644 index 0000000000..4c59c50293 --- /dev/null +++ b/patches/server/0873-Guard-against-invalid-entity-positions.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 31 Mar 2022 05:18:28 -0700 +Subject: [PATCH] Guard against invalid entity positions + +Anything not finite should be blocked and logged + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index a0c84b3fc264ef25ef1bcdeba482ac23461411f3..19876a474fec6933322775073d4a161f604a102d 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -4187,11 +4187,33 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); + } + ++ // Paper start - block invalid positions ++ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { ++ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { ++ return true; ++ } ++ ++ String entityInfo = null; ++ try { ++ entityInfo = entity.toString(); ++ } catch (Exception ex) { ++ entityInfo = "[Entity info unavailable] "; ++ } ++ LOGGER.error("New entity position is invalid! Tried to set invalid position (" + newX + "," + newY + "," + newZ + ") for entity " + entity.getClass().getName() + " located at " + entity.position + ", entity info: " + entityInfo, new Throwable()); ++ return false; ++ } ++ // Paper end - block invalid positions ++ + public final void setPosRaw(double x, double y, double z) { + // Paper start + this.setPosRaw(x, y, z, false); + } + public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { ++ // Paper start - block invalid positions ++ if (!checkPosition(this, x, y, z)) { ++ return; ++ } ++ // Paper end - block invalid positions + // Paper end + // Paper start - fix MC-4 + if (this instanceof ItemEntity) { diff --git a/patches/server/0874-Guard-against-invalid-entity-positions.patch b/patches/server/0874-Guard-against-invalid-entity-positions.patch deleted file mode 100644 index 3e64edd9dc..0000000000 --- a/patches/server/0874-Guard-against-invalid-entity-positions.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 31 Mar 2022 05:18:28 -0700 -Subject: [PATCH] Guard against invalid entity positions - -Anything not finite should be blocked and logged - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 82ccf8eacd5cac82ae2f44d38fc05f606ff50f8e..6a1f11a2519cc320407696cc7165404a43d0b961 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4173,11 +4173,33 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); - } - -+ // Paper start - block invalid positions -+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) { -+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) { -+ return true; -+ } -+ -+ String entityInfo = null; -+ try { -+ entityInfo = entity.toString(); -+ } catch (Exception ex) { -+ entityInfo = "[Entity info unavailable] "; -+ } -+ LOGGER.error("New entity position is invalid! Tried to set invalid position (" + newX + "," + newY + "," + newZ + ") for entity " + entity.getClass().getName() + " located at " + entity.position + ", entity info: " + entityInfo, new Throwable()); -+ return false; -+ } -+ // Paper end - block invalid positions -+ - public final void setPosRaw(double x, double y, double z) { - // Paper start - this.setPosRaw(x, y, z, false); - } - public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { -+ // Paper start - block invalid positions -+ if (!checkPosition(this, x, y, z)) { -+ return; -+ } -+ // Paper end - block invalid positions - // Paper end - // Paper start - fix MC-4 - if (this instanceof ItemEntity) { diff --git a/patches/server/0874-cache-resource-keys.patch b/patches/server/0874-cache-resource-keys.patch new file mode 100644 index 0000000000..4234b8673f --- /dev/null +++ b/patches/server/0874-cache-resource-keys.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 20 Mar 2022 22:06:47 -0700 +Subject: [PATCH] cache resource keys + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index 22356c2380741dd811810420127e247e46b0b8e2..bfe9dc935c87e01fb435d8b46ce413b84ca74856 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -366,12 +366,13 @@ public class CraftBlock implements Block { + return (biome == null) ? Biome.CUSTOM : biome; + } + ++ private static final java.util.Map> BIOME_KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(Biome.class)); // Paper + public static Holder biomeToBiomeBase(net.minecraft.core.Registry registry, Biome bio) { + if (bio == null || bio == Biome.CUSTOM) { + return null; + } + +- return registry.getHolderOrThrow(ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, CraftNamespacedKey.toMinecraft(bio.getKey()))); ++ return registry.getHolderOrThrow(BIOME_KEY_CACHE.computeIfAbsent(bio, b -> ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, CraftNamespacedKey.toMinecraft(b.getKey())))); // Paper - cache key + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java +index 393bb27648501c6f25cf846e929f4c95b1a0b11f..979f82d589e378b9e52417d20b48d66f077f5dfd 100644 +--- a/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java +@@ -16,9 +16,10 @@ public class CraftEntityTag extends CraftTag>> KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper + @Override + public boolean isTagged(EntityType entity) { +- return registry.getHolderOrThrow(ResourceKey.create(net.minecraft.core.Registry.ENTITY_TYPE_REGISTRY, CraftNamespacedKey.toMinecraft(entity.getKey()))).is(tag); ++ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(entity, type -> ResourceKey.create(net.minecraft.core.Registry.ENTITY_TYPE_REGISTRY, CraftNamespacedKey.toMinecraft(type.getKey())))).is(tag); // Paper - cache key + } + + @Override +diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java +index cdd474e9b0363641839a66d3e61fec46c735879a..3611e3e9d33a0f1d82a78a27ea5c2c649225f564 100644 +--- a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java ++++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java +@@ -14,9 +14,10 @@ public class CraftFluidTag extends CraftTag> KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(Fluid.class)); // Paper + @Override + public boolean isTagged(Fluid fluid) { +- return registry.getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.FLUID_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(fluid.getKey()))).is(tag); // Paper ++ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(fluid, f -> net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.FLUID_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(f.getKey())))).is(tag); // Paper - cache key + } + + @Override diff --git a/patches/server/0875-Allow-to-change-the-podium-for-the-EnderDragon.patch b/patches/server/0875-Allow-to-change-the-podium-for-the-EnderDragon.patch new file mode 100644 index 0000000000..b53e022e0b --- /dev/null +++ b/patches/server/0875-Allow-to-change-the-podium-for-the-EnderDragon.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sun, 3 Apr 2022 11:31:42 -0400 +Subject: [PATCH] Allow to change the podium for the EnderDragon + + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +index bb51a85b33e1701c2e445305d68d3453772f73df..47d6236daca806878399890a8d08e55233f19fd9 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +@@ -99,6 +99,10 @@ public class EnderDragon extends Mob implements Enemy { + private final int[] nodeAdjacency = new int[24]; + private final BinaryHeap openSet = new BinaryHeap(); + private Explosion explosionSource = new Explosion(null, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit - reusable source for CraftTNTPrimed.getSource() ++ // Paper start - add var for save custom podium ++ @Nullable ++ private BlockPos podium; ++ // Paper end + + public EnderDragon(EntityType entitytypes, Level world) { + super(EntityType.ENDER_DRAGON, world); +@@ -119,6 +123,19 @@ public class EnderDragon extends Mob implements Enemy { + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); + } + ++ // Paper start ++ public BlockPos getPodium() { ++ if (this.podium == null) { ++ return EndPodiumFeature.END_PODIUM_LOCATION; ++ } ++ return this.podium; ++ } ++ ++ public void setPodium(@Nullable BlockPos blockPos) { ++ this.podium = blockPos; ++ } ++ // Paper end ++ + @Override + public boolean isFlapping() { + float f = Mth.cos(this.flapTime * 6.2831855F); +@@ -970,7 +987,7 @@ public class EnderDragon extends Mob implements Enemy { + d0 = segment2[1] - segment1[1]; + } + } else { +- BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); ++ BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium + double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D); + + d0 = (double) segmentOffset / d1; +@@ -997,7 +1014,7 @@ public class EnderDragon extends Mob implements Enemy { + vec3d = this.getViewVector(tickDelta); + } + } else { +- BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); ++ BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium + + f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); + float f3 = 6.0F / f1; +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +index a64ee433e34538ce2b52207b6183999ae611e5dd..0f78e1ab090bb1df7b863c90b3c7465a3ce28c8c 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java +@@ -32,7 +32,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { + public void doServerTick() { + ++this.time; + if (this.targetLocation == null) { +- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION); ++ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - use custom podium + this.targetLocation = Vec3.atBottomCenterOf(blockPos); + } + +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +index e58f608e424e384606289d9ff27bf8f9c63aeeb2..4c338d7f3d5274a36db768e4a1cdedca130127d4 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java +@@ -55,7 +55,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { + + private void findNewTarget() { + if (this.currentPath != null && this.currentPath.isDone()) { +- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(EndPodiumFeature.END_PODIUM_LOCATION)); ++ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium + int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); + if (this.dragon.getRandom().nextInt(i + 3) == 0) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +index fdfdd42a30d752b11d18f2cefe84c1e9ddec41a2..5fca7c4e1d1d9da6f29ad70f1b5703c7f092d851 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java +@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { + private void findNewTarget() { + if (this.currentPath == null || this.currentPath.isDone()) { + int i = this.dragon.findClosestNode(); +- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); ++ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium + Player player = this.dragon.level.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); + int j; + if (player != null) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +index ed29ba6c5c4f1380847564f07b5523cce77ab865..2948d58f9f90b353b86eb43f932ab0574b3415f7 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java +@@ -39,7 +39,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { + @Override + public void doServerTick() { + if (this.targetLocation == null) { +- this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION)); ++ this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium())); // Paper - use custom podium + } + + if (this.targetLocation.distanceToSqr(this.dragon.getX(), this.dragon.getY(), this.dragon.getZ()) < 1.0D) { +diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +index 0ae65d0fa03d12486f48b0274b6e2d4eea169caf..ffe89d8c1f22f672d145fedb3bb102589dc31656 100644 +--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java ++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java +@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { + @Override + public void doServerTick() { + if (!this.firstTick && this.currentPath != null) { +- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); ++ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium + if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0D)) { + this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +index cd487bd68f9a8177ae3e015b3a5d1bc469743f48..eeb6d48da156602c046db891cac0ccb4fa639473 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java +@@ -79,4 +79,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon { + public int getDeathAnimationTicks() { + return this.getHandle().dragonDeathTime; + } ++ ++ // Paper start ++ @Override ++ public org.bukkit.Location getPodium() { ++ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); ++ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); ++ } ++ ++ @Override ++ public void setPodium(org.bukkit.Location location) { ++ if (location == null) { ++ this.getHandle().setPodium(null); ++ } else { ++ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); ++ this.getHandle().setPodium(new net.minecraft.core.BlockPos(location.getX(), location.getY(), location.getZ())); ++ } ++ } ++ // Paper end + } diff --git a/patches/server/0875-cache-resource-keys.patch b/patches/server/0875-cache-resource-keys.patch deleted file mode 100644 index 4234b8673f..0000000000 --- a/patches/server/0875-cache-resource-keys.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 20 Mar 2022 22:06:47 -0700 -Subject: [PATCH] cache resource keys - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 22356c2380741dd811810420127e247e46b0b8e2..bfe9dc935c87e01fb435d8b46ce413b84ca74856 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -366,12 +366,13 @@ public class CraftBlock implements Block { - return (biome == null) ? Biome.CUSTOM : biome; - } - -+ private static final java.util.Map> BIOME_KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(Biome.class)); // Paper - public static Holder biomeToBiomeBase(net.minecraft.core.Registry registry, Biome bio) { - if (bio == null || bio == Biome.CUSTOM) { - return null; - } - -- return registry.getHolderOrThrow(ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, CraftNamespacedKey.toMinecraft(bio.getKey()))); -+ return registry.getHolderOrThrow(BIOME_KEY_CACHE.computeIfAbsent(bio, b -> ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, CraftNamespacedKey.toMinecraft(b.getKey())))); // Paper - cache key - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java -index 393bb27648501c6f25cf846e929f4c95b1a0b11f..979f82d589e378b9e52417d20b48d66f077f5dfd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java -+++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftEntityTag.java -@@ -16,9 +16,10 @@ public class CraftEntityTag extends CraftTag>> KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper - @Override - public boolean isTagged(EntityType entity) { -- return registry.getHolderOrThrow(ResourceKey.create(net.minecraft.core.Registry.ENTITY_TYPE_REGISTRY, CraftNamespacedKey.toMinecraft(entity.getKey()))).is(tag); -+ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(entity, type -> ResourceKey.create(net.minecraft.core.Registry.ENTITY_TYPE_REGISTRY, CraftNamespacedKey.toMinecraft(type.getKey())))).is(tag); // Paper - cache key - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -index cdd474e9b0363641839a66d3e61fec46c735879a..3611e3e9d33a0f1d82a78a27ea5c2c649225f564 100644 ---- a/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -+++ b/src/main/java/org/bukkit/craftbukkit/tag/CraftFluidTag.java -@@ -14,9 +14,10 @@ public class CraftFluidTag extends CraftTag> KEY_CACHE = Collections.synchronizedMap(new java.util.EnumMap<>(Fluid.class)); // Paper - @Override - public boolean isTagged(Fluid fluid) { -- return registry.getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.FLUID_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(fluid.getKey()))).is(tag); // Paper -+ return registry.getHolderOrThrow(KEY_CACHE.computeIfAbsent(fluid, f -> net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.FLUID_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(f.getKey())))).is(tag); // Paper - cache key - } - - @Override diff --git a/patches/server/0876-Allow-to-change-the-podium-for-the-EnderDragon.patch b/patches/server/0876-Allow-to-change-the-podium-for-the-EnderDragon.patch deleted file mode 100644 index b53e022e0b..0000000000 --- a/patches/server/0876-Allow-to-change-the-podium-for-the-EnderDragon.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 3 Apr 2022 11:31:42 -0400 -Subject: [PATCH] Allow to change the podium for the EnderDragon - - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index bb51a85b33e1701c2e445305d68d3453772f73df..47d6236daca806878399890a8d08e55233f19fd9 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -@@ -99,6 +99,10 @@ public class EnderDragon extends Mob implements Enemy { - private final int[] nodeAdjacency = new int[24]; - private final BinaryHeap openSet = new BinaryHeap(); - private Explosion explosionSource = new Explosion(null, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit - reusable source for CraftTNTPrimed.getSource() -+ // Paper start - add var for save custom podium -+ @Nullable -+ private BlockPos podium; -+ // Paper end - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -119,6 +123,19 @@ public class EnderDragon extends Mob implements Enemy { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); - } - -+ // Paper start -+ public BlockPos getPodium() { -+ if (this.podium == null) { -+ return EndPodiumFeature.END_PODIUM_LOCATION; -+ } -+ return this.podium; -+ } -+ -+ public void setPodium(@Nullable BlockPos blockPos) { -+ this.podium = blockPos; -+ } -+ // Paper end -+ - @Override - public boolean isFlapping() { - float f = Mth.cos(this.flapTime * 6.2831855F); -@@ -970,7 +987,7 @@ public class EnderDragon extends Mob implements Enemy { - d0 = segment2[1] - segment1[1]; - } - } else { -- BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); -+ BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium - double d1 = Math.max(Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0D, 1.0D); - - d0 = (double) segmentOffset / d1; -@@ -997,7 +1014,7 @@ public class EnderDragon extends Mob implements Enemy { - vec3d = this.getViewVector(tickDelta); - } - } else { -- BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); -+ BlockPos blockposition = this.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - use custom podium - - f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F); - float f3 = 6.0F / f1; -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -index a64ee433e34538ce2b52207b6183999ae611e5dd..0f78e1ab090bb1df7b863c90b3c7465a3ce28c8c 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java -@@ -32,7 +32,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance { - public void doServerTick() { - ++this.time; - if (this.targetLocation == null) { -- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.END_PODIUM_LOCATION); -+ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - use custom podium - this.targetLocation = Vec3.atBottomCenterOf(blockPos); - } - -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -index e58f608e424e384606289d9ff27bf8f9c63aeeb2..4c338d7f3d5274a36db768e4a1cdedca130127d4 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java -@@ -55,7 +55,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance { - - private void findNewTarget() { - if (this.currentPath != null && this.currentPath.isDone()) { -- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(EndPodiumFeature.END_PODIUM_LOCATION)); -+ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium - int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive(); - if (this.dragon.getRandom().nextInt(i + 3) == 0) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH); -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -index fdfdd42a30d752b11d18f2cefe84c1e9ddec41a2..5fca7c4e1d1d9da6f29ad70f1b5703c7f092d851 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java -@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance { - private void findNewTarget() { - if (this.currentPath == null || this.currentPath.isDone()) { - int i = this.dragon.findClosestNode(); -- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); -+ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium - Player player = this.dragon.level.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ()); - int j; - if (player != null) { -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -index ed29ba6c5c4f1380847564f07b5523cce77ab865..2948d58f9f90b353b86eb43f932ab0574b3415f7 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java -@@ -39,7 +39,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance { - @Override - public void doServerTick() { - if (this.targetLocation == null) { -- this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION)); -+ this.targetLocation = Vec3.atBottomCenterOf(this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium())); // Paper - use custom podium - } - - if (this.targetLocation.distanceToSqr(this.dragon.getX(), this.dragon.getY(), this.dragon.getZ()) < 1.0D) { -diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -index 0ae65d0fa03d12486f48b0274b6e2d4eea169caf..ffe89d8c1f22f672d145fedb3bb102589dc31656 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java -@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance { - @Override - public void doServerTick() { - if (!this.firstTick && this.currentPath != null) { -- BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.END_PODIUM_LOCATION); -+ BlockPos blockPos = this.dragon.level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - use custom podium - if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0D)) { - this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -index cd487bd68f9a8177ae3e015b3a5d1bc469743f48..eeb6d48da156602c046db891cac0ccb4fa639473 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java -@@ -79,4 +79,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon { - public int getDeathAnimationTicks() { - return this.getHandle().dragonDeathTime; - } -+ -+ // Paper start -+ @Override -+ public org.bukkit.Location getPodium() { -+ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium(); -+ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ()); -+ } -+ -+ @Override -+ public void setPodium(org.bukkit.Location location) { -+ if (location == null) { -+ this.getHandle().setPodium(null); -+ } else { -+ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is"); -+ this.getHandle().setPodium(new net.minecraft.core.BlockPos(location.getX(), location.getY(), location.getZ())); -+ } -+ } -+ // Paper end - } diff --git a/patches/server/0876-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0876-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch new file mode 100644 index 0000000000..9ef13e101f --- /dev/null +++ b/patches/server/0876-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: etil2jz +Date: Sat, 2 Apr 2022 23:29:24 +0200 +Subject: [PATCH] Fix NBT pieces overriding a block entity during worldgen + deadlock + +By checking if the world passed into StructureTemplate's placeInWorld +is not a WorldGenRegion, we can bypass the deadlock entirely. +See https://bugs.mojang.com/browse/MC-246262 + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +index 4aac9be67a073e60272a68b52c2cda026d4ee28f..30c44b39f9b7a434bb77d6307d2514f2ce9d2857 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java +@@ -266,7 +266,11 @@ public class StructureTemplate { + + if (definedstructure_blockinfo.nbt != null) { + tileentity = world.getBlockEntity(blockposition2); +- Clearable.tryClear(tileentity); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ Clearable.tryClear(tileentity); ++ } ++ // Paper end + world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20); + } + +@@ -381,7 +385,11 @@ public class StructureTemplate { + if (pair1.getSecond() != null) { + tileentity = world.getBlockEntity(blockposition6); + if (tileentity != null) { +- tileentity.setChanged(); ++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock ++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { ++ tileentity.setChanged(); ++ } ++ // Paper end + } + } + } diff --git a/patches/server/0877-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0877-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch deleted file mode 100644 index 9ef13e101f..0000000000 --- a/patches/server/0877-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: etil2jz -Date: Sat, 2 Apr 2022 23:29:24 +0200 -Subject: [PATCH] Fix NBT pieces overriding a block entity during worldgen - deadlock - -By checking if the world passed into StructureTemplate's placeInWorld -is not a WorldGenRegion, we can bypass the deadlock entirely. -See https://bugs.mojang.com/browse/MC-246262 - -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -index 4aac9be67a073e60272a68b52c2cda026d4ee28f..30c44b39f9b7a434bb77d6307d2514f2ce9d2857 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java -@@ -266,7 +266,11 @@ public class StructureTemplate { - - if (definedstructure_blockinfo.nbt != null) { - tileentity = world.getBlockEntity(blockposition2); -- Clearable.tryClear(tileentity); -+ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock -+ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { -+ Clearable.tryClear(tileentity); -+ } -+ // Paper end - world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20); - } - -@@ -381,7 +385,11 @@ public class StructureTemplate { - if (pair1.getSecond() != null) { - tileentity = world.getBlockEntity(blockposition6); - if (tileentity != null) { -- tileentity.setChanged(); -+ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock -+ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) { -+ tileentity.setChanged(); -+ } -+ // Paper end - } - } - } diff --git a/patches/server/0877-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch b/patches/server/0877-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch new file mode 100644 index 0000000000..4b39ce4dc3 --- /dev/null +++ b/patches/server/0877-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Tue, 12 Apr 2022 16:36:15 -0700 +Subject: [PATCH] Fix StructureGrowEvent species for RED_MUSHROOM + + +diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +index 5333188e45e47c12c8c56939b87603750a45af57..881d899ff551bc2c2c6fc7d58107a2aee7d86f2a 100644 +--- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +@@ -85,7 +85,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + + public boolean growMushroom(ServerLevel world, BlockPos pos, BlockState state, RandomSource random) { + world.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.BROWN_MUSHROOM; // CraftBukkit ++ SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit // Paper + if (((ConfiguredFeature) ((Holder) this.featureSupplier.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { diff --git a/patches/server/0878-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch b/patches/server/0878-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch deleted file mode 100644 index 4b39ce4dc3..0000000000 --- a/patches/server/0878-Fix-StructureGrowEvent-species-for-RED_MUSHROOM.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Tue, 12 Apr 2022 16:36:15 -0700 -Subject: [PATCH] Fix StructureGrowEvent species for RED_MUSHROOM - - -diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -index 5333188e45e47c12c8c56939b87603750a45af57..881d899ff551bc2c2c6fc7d58107a2aee7d86f2a 100644 ---- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java -@@ -85,7 +85,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { - - public boolean growMushroom(ServerLevel world, BlockPos pos, BlockState state, RandomSource random) { - world.removeBlock(pos, false); -- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.BROWN_MUSHROOM; // CraftBukkit -+ SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit // Paper - if (((ConfiguredFeature) ((Holder) this.featureSupplier.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) { - return true; - } else { diff --git a/patches/server/0878-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0878-Prevent-tile-entity-copies-loading-chunks.patch new file mode 100644 index 0000000000..3c5eabbf49 --- /dev/null +++ b/patches/server/0878-Prevent-tile-entity-copies-loading-chunks.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 13 Apr 2022 08:25:42 +0100 +Subject: [PATCH] Prevent tile entity copies loading chunks + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cf3f9d225e4c4114fd14979ac5f7bf8ea45c3765..cea81ea8afa1cedc2be997357db2a920a9e3499b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3411,7 +3411,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); + + if (this.player.level.isLoaded(blockposition)) { +- BlockEntity tileentity = this.player.level.getBlockEntity(blockposition); ++ // Paper start ++ BlockEntity tileentity = null; ++ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.getLevel().isLoadedAndInBounds(blockposition)) { ++ tileentity = this.player.level.getBlockEntity(blockposition); ++ } ++ // Paper end + + if (tileentity != null) { + tileentity.saveToItem(itemstack); diff --git a/patches/server/0879-Prevent-tile-entity-copies-loading-chunks.patch b/patches/server/0879-Prevent-tile-entity-copies-loading-chunks.patch deleted file mode 100644 index f1dbc73ef5..0000000000 --- a/patches/server/0879-Prevent-tile-entity-copies-loading-chunks.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 13 Apr 2022 08:25:42 +0100 -Subject: [PATCH] Prevent tile entity copies loading chunks - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 7cc9e225115f4c6ac6fc5f8bee46bf949a9245dd..143370d7395d1bed22420e515be288688b0faf63 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3407,7 +3407,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); - - if (this.player.level.isLoaded(blockposition)) { -- BlockEntity tileentity = this.player.level.getBlockEntity(blockposition); -+ // Paper start -+ BlockEntity tileentity = null; -+ if (this.player.distanceToSqr(blockposition.getX(), blockposition.getY(), blockposition.getZ()) < 32 * 32 && this.player.getLevel().isLoadedAndInBounds(blockposition)) { -+ tileentity = this.player.level.getBlockEntity(blockposition); -+ } -+ // Paper end - - if (tileentity != null) { - tileentity.saveToItem(itemstack); diff --git a/patches/server/0879-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0879-Use-username-instead-of-display-name-in-PlayerList-g.patch new file mode 100644 index 0000000000..2accf61444 --- /dev/null +++ b/patches/server/0879-Use-username-instead-of-display-name-in-PlayerList-g.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Fri, 15 Apr 2022 17:40:30 -0400 +Subject: [PATCH] Use username instead of display name in + PlayerList#getPlayerStats + + +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 5a5ea1f9d6e978916d32b170ecf7f848d2524303..5999d85e38951503fc83b40cfe39671921ae5088 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -1483,7 +1483,7 @@ public abstract class PlayerList { + // CraftBukkit start + public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { + ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); +- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; ++ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name + } + + public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0880-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch b/patches/server/0880-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch new file mode 100644 index 0000000000..951baf9062 --- /dev/null +++ b/patches/server/0880-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 15 Apr 2022 17:09:28 -0700 +Subject: [PATCH] Fix slime spawners not spawning outside slime chunks + +Fixes MC-50647 by just checking if the spawn type is a SPAWNER +and then bypassing the spawn check logic if on slimes if it is. + +diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java +index fa79316adb11ab39cf921475e12a50058fd82a87..7e85ad7ba31bbb32ea1e1dff5d1c83e7ce68b4b3 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java ++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java +@@ -320,6 +320,11 @@ public class Slime extends Mob implements Enemy { + + public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { + if (world.getDifficulty() != Difficulty.PEACEFUL) { ++ // Paper start - fix slime spawners; Fixes MC-50647 ++ if (spawnReason == MobSpawnType.SPAWNER) { ++ return random.nextInt(10) == 0; ++ } ++ // Paper end + // Paper start - Replace rules for Height in Swamp Biome + final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; + final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; diff --git a/patches/server/0880-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0880-Use-username-instead-of-display-name-in-PlayerList-g.patch deleted file mode 100644 index 2accf61444..0000000000 --- a/patches/server/0880-Use-username-instead-of-display-name-in-PlayerList-g.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Fri, 15 Apr 2022 17:40:30 -0400 -Subject: [PATCH] Use username instead of display name in - PlayerList#getPlayerStats - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 5a5ea1f9d6e978916d32b170ecf7f848d2524303..5999d85e38951503fc83b40cfe39671921ae5088 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1483,7 +1483,7 @@ public abstract class PlayerList { - // CraftBukkit start - public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) { - ServerStatsCounter serverstatisticmanager = entityhuman.getStats(); -- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager; -+ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name - } - - public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) { diff --git a/patches/server/0881-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch b/patches/server/0881-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch deleted file mode 100644 index 951baf9062..0000000000 --- a/patches/server/0881-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 15 Apr 2022 17:09:28 -0700 -Subject: [PATCH] Fix slime spawners not spawning outside slime chunks - -Fixes MC-50647 by just checking if the spawn type is a SPAWNER -and then bypassing the spawn check logic if on slimes if it is. - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index fa79316adb11ab39cf921475e12a50058fd82a87..7e85ad7ba31bbb32ea1e1dff5d1c83e7ce68b4b3 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -320,6 +320,11 @@ public class Slime extends Mob implements Enemy { - - public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - if (world.getDifficulty() != Difficulty.PEACEFUL) { -+ // Paper start - fix slime spawners; Fixes MC-50647 -+ if (spawnReason == MobSpawnType.SPAWNER) { -+ return random.nextInt(10) == 0; -+ } -+ // Paper end - // Paper start - Replace rules for Height in Swamp Biome - final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum; - final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum; diff --git a/patches/server/0881-Pass-ServerLevel-for-gamerule-callbacks.patch b/patches/server/0881-Pass-ServerLevel-for-gamerule-callbacks.patch new file mode 100644 index 0000000000..f29c5078f2 --- /dev/null +++ b/patches/server/0881-Pass-ServerLevel-for-gamerule-callbacks.patch @@ -0,0 +1,181 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 27 Mar 2022 13:51:09 -0400 +Subject: [PATCH] Pass ServerLevel for gamerule callbacks + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 20670bc075c387ee0422eb1014207e26105efccd..bdd6560fe85950b0a857a949cb38c044da44ca6b 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -309,7 +309,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + //DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); // Paper moved to after init + if (dedicatedserverproperties.announcePlayerAchievements != null) { +- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this); ++ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, null); // Paper + } + + if (dedicatedserverproperties.enableQuery) { +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index cea81ea8afa1cedc2be997357db2a920a9e3499b..15694736c7ee8836355409e0046e221ebdb7e524 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3007,7 +3007,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + this.player = this.server.getPlayerList().respawn(this.player, false); + if (this.server.isHardcore()) { + this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper +- ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); ++ ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.getLevel()); // Paper + } + } + break; +diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java +index 3c93bfeb94168f832904a8462ae23b06e81e080d..468c635d31cfa8051666bbefce8df4b448e9ed93 100644 +--- a/src/main/java/net/minecraft/world/level/GameRules.java ++++ b/src/main/java/net/minecraft/world/level/GameRules.java +@@ -51,7 +51,7 @@ public class GameRules { + public static final GameRules.Key RULE_SENDCOMMANDFEEDBACK = GameRules.register("sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_REDUCEDDEBUGINFO = GameRules.register("reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { + int i = gamerules_gameruleboolean.get() ? 22 : 23; +- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); ++ Iterator iterator = minecraftserver.players().iterator(); // Paper + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +@@ -71,7 +71,7 @@ public class GameRules { + public static final GameRules.Key RULE_DISABLE_RAIDS = GameRules.register("disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false)); + public static final GameRules.Key RULE_DOINSOMNIA = GameRules.register("doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)); + public static final GameRules.Key RULE_DO_IMMEDIATE_RESPAWN = GameRules.register("doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { +- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); ++ Iterator iterator = minecraftserver.players().iterator(); // Paper + + while (iterator.hasNext()) { + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); +@@ -157,13 +157,13 @@ public class GameRules { + ((GameRules.Type) type).callVisitor(consumer, (GameRules.Key) key); // CraftBukkit - decompile error + } + +- public void assignFrom(GameRules rules, @Nullable MinecraftServer server) { ++ public void assignFrom(GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + rules.rules.keySet().forEach((gamerules_gamerulekey) -> { + this.assignCap(gamerules_gamerulekey, rules, server); + }); + } + +- private > void assignCap(GameRules.Key key, GameRules rules, @Nullable MinecraftServer server) { ++ private > void assignCap(GameRules.Key key, GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + T t0 = rules.getRule(key); + + this.getRule(key).setFrom(t0, server); +@@ -231,10 +231,10 @@ public class GameRules { + + private final Supplier> argument; + private final Function, T> constructor; +- final BiConsumer callback; ++ final BiConsumer callback; // Paper + private final GameRules.VisitorCaller visitorCaller; + +- Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { ++ Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { // Paper + this.argument = argumentType; + this.constructor = ruleFactory; + this.callback = changeCallback; +@@ -266,10 +266,10 @@ public class GameRules { + + public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper + this.updateFromArgument(context, name, gameRuleKey); // Paper +- this.onChanged(((CommandSourceStack) context.getSource()).getServer()); ++ this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // Paper + } + +- public void onChanged(@Nullable MinecraftServer server) { ++ public void onChanged(@Nullable net.minecraft.server.level.ServerLevel server) { // Paper + if (server != null) { + this.type.callback.accept(server, this.getSelf()); + } +@@ -290,7 +290,7 @@ public class GameRules { + + protected abstract T copy(); + +- public abstract void setFrom(T rule, @Nullable MinecraftServer server); ++ public abstract void setFrom(T rule, @Nullable net.minecraft.server.level.ServerLevel level); // Paper + } + + public interface GameRuleTypeVisitor { +@@ -306,7 +306,7 @@ public class GameRules { + + private boolean value; + +- static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { ++ static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { // Paper + return new GameRules.Type<>(BoolArgumentType::bool, (gamerules_gameruledefinition) -> { + return new GameRules.BooleanValue(gamerules_gameruledefinition, initialValue); + }, changeCallback, GameRules.GameRuleTypeVisitor::visitBoolean); +@@ -334,7 +334,7 @@ public class GameRules { + return this.value; + } + +- public void set(boolean value, @Nullable MinecraftServer server) { ++ public void set(boolean value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + this.value = value; + this.onChanged(server); + } +@@ -364,7 +364,7 @@ public class GameRules { + return new GameRules.BooleanValue(this.type, this.value); + } + +- public void setFrom(GameRules.BooleanValue rule, @Nullable MinecraftServer server) { ++ public void setFrom(GameRules.BooleanValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + this.value = rule.value; + this.onChanged(server); + } +@@ -374,7 +374,7 @@ public class GameRules { + + private int value; + +- private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { ++ private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { // Paper + return new GameRules.Type<>(IntegerArgumentType::integer, (gamerules_gameruledefinition) -> { + return new GameRules.IntegerValue(gamerules_gameruledefinition, initialValue); + }, changeCallback, GameRules.GameRuleTypeVisitor::visitInteger); +@@ -402,7 +402,7 @@ public class GameRules { + return this.value; + } + +- public void set(int value, @Nullable MinecraftServer server) { ++ public void set(int value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + this.value = value; + this.onChanged(server); + } +@@ -453,7 +453,7 @@ public class GameRules { + return new GameRules.IntegerValue(this.type, this.value); + } + +- public void setFrom(GameRules.IntegerValue rule, @Nullable MinecraftServer server) { ++ public void setFrom(GameRules.IntegerValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper + this.value = rule.value; + this.onChanged(server); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 23c68b6cd26fbd05685ebcfbb5e81db4c8dedb29..9d27b093922f3dee9b459f8a9cdfa96f12f2e654 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -1912,7 +1912,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Paper end + GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); + handle.deserialize(event.getValue()); // Paper +- handle.onChanged(this.getHandle().getServer()); ++ handle.onChanged(this.getHandle()); // Paper + return true; + } + +@@ -1952,7 +1952,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Paper end + GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); + handle.deserialize(event.getValue()); // Paper +- handle.onChanged(this.getHandle().getServer()); ++ handle.onChanged(this.getHandle()); // Paper + return true; + } + diff --git a/patches/server/0882-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch b/patches/server/0882-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch new file mode 100644 index 0000000000..fff84c5a62 --- /dev/null +++ b/patches/server/0882-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: HexedHero <6012891+HexedHero@users.noreply.github.com> +Date: Sun, 10 Apr 2022 06:26:32 +0100 +Subject: [PATCH] Add pre-unbreaking amount to PlayerItemDamageEvent + + +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 67626e7faa4d0854d31b41c0a702edbeb6ce4270..c18a0bc94d0210396046f4475e49a739088593f3 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -563,10 +563,11 @@ public final class ItemStack { + } + } + ++ int originalDamage = amount; // Paper + amount -= k; + // CraftBukkit start + if (player instanceof ServerPlayer serverPlayer) { // Paper +- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper ++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper + event.getPlayer().getServer().getPluginManager().callEvent(event); + + if (amount != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0882-Pass-ServerLevel-for-gamerule-callbacks.patch b/patches/server/0882-Pass-ServerLevel-for-gamerule-callbacks.patch deleted file mode 100644 index afd5151a7c..0000000000 --- a/patches/server/0882-Pass-ServerLevel-for-gamerule-callbacks.patch +++ /dev/null @@ -1,181 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 27 Mar 2022 13:51:09 -0400 -Subject: [PATCH] Pass ServerLevel for gamerule callbacks - - -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 20670bc075c387ee0422eb1014207e26105efccd..bdd6560fe85950b0a857a949cb38c044da44ca6b 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -309,7 +309,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - - //DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); // Paper moved to after init - if (dedicatedserverproperties.announcePlayerAchievements != null) { -- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this); -+ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, null); // Paper - } - - if (dedicatedserverproperties.enableQuery) { -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 143370d7395d1bed22420e515be288688b0faf63..8e7d7d9f1f228bd0f71abfe46a3cef1f1a72a7ed 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3003,7 +3003,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - this.player = this.server.getPlayerList().respawn(this.player, false); - if (this.server.isHardcore()) { - this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper -- ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); -+ ((GameRules.BooleanValue) this.player.getLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.getLevel()); // Paper - } - } - break; -diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java -index 3c93bfeb94168f832904a8462ae23b06e81e080d..468c635d31cfa8051666bbefce8df4b448e9ed93 100644 ---- a/src/main/java/net/minecraft/world/level/GameRules.java -+++ b/src/main/java/net/minecraft/world/level/GameRules.java -@@ -51,7 +51,7 @@ public class GameRules { - public static final GameRules.Key RULE_SENDCOMMANDFEEDBACK = GameRules.register("sendCommandFeedback", GameRules.Category.CHAT, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_REDUCEDDEBUGINFO = GameRules.register("reducedDebugInfo", GameRules.Category.MISC, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { - int i = gamerules_gameruleboolean.get() ? 22 : 23; -- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = minecraftserver.players().iterator(); // Paper - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -71,7 +71,7 @@ public class GameRules { - public static final GameRules.Key RULE_DISABLE_RAIDS = GameRules.register("disableRaids", GameRules.Category.MOBS, GameRules.BooleanValue.create(false)); - public static final GameRules.Key RULE_DOINSOMNIA = GameRules.register("doInsomnia", GameRules.Category.SPAWNING, GameRules.BooleanValue.create(true)); - public static final GameRules.Key RULE_DO_IMMEDIATE_RESPAWN = GameRules.register("doImmediateRespawn", GameRules.Category.PLAYER, GameRules.BooleanValue.create(false, (minecraftserver, gamerules_gameruleboolean) -> { -- Iterator iterator = minecraftserver.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = minecraftserver.players().iterator(); // Paper - - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -157,13 +157,13 @@ public class GameRules { - ((GameRules.Type) type).callVisitor(consumer, (GameRules.Key) key); // CraftBukkit - decompile error - } - -- public void assignFrom(GameRules rules, @Nullable MinecraftServer server) { -+ public void assignFrom(GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - rules.rules.keySet().forEach((gamerules_gamerulekey) -> { - this.assignCap(gamerules_gamerulekey, rules, server); - }); - } - -- private > void assignCap(GameRules.Key key, GameRules rules, @Nullable MinecraftServer server) { -+ private > void assignCap(GameRules.Key key, GameRules rules, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - T t0 = rules.getRule(key); - - this.getRule(key).setFrom(t0, server); -@@ -231,10 +231,10 @@ public class GameRules { - - private final Supplier> argument; - private final Function, T> constructor; -- final BiConsumer callback; -+ final BiConsumer callback; // Paper - private final GameRules.VisitorCaller visitorCaller; - -- Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { -+ Type(Supplier> argumentType, Function, T> ruleFactory, BiConsumer changeCallback, GameRules.VisitorCaller ruleAcceptor) { // Paper - this.argument = argumentType; - this.constructor = ruleFactory; - this.callback = changeCallback; -@@ -266,10 +266,10 @@ public class GameRules { - - public void setFromArgument(CommandContext context, String name, GameRules.Key gameRuleKey) { // Paper - this.updateFromArgument(context, name, gameRuleKey); // Paper -- this.onChanged(((CommandSourceStack) context.getSource()).getServer()); -+ this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // Paper - } - -- public void onChanged(@Nullable MinecraftServer server) { -+ public void onChanged(@Nullable net.minecraft.server.level.ServerLevel server) { // Paper - if (server != null) { - this.type.callback.accept(server, this.getSelf()); - } -@@ -290,7 +290,7 @@ public class GameRules { - - protected abstract T copy(); - -- public abstract void setFrom(T rule, @Nullable MinecraftServer server); -+ public abstract void setFrom(T rule, @Nullable net.minecraft.server.level.ServerLevel level); // Paper - } - - public interface GameRuleTypeVisitor { -@@ -306,7 +306,7 @@ public class GameRules { - - private boolean value; - -- static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { -+ static GameRules.Type create(boolean initialValue, BiConsumer changeCallback) { // Paper - return new GameRules.Type<>(BoolArgumentType::bool, (gamerules_gameruledefinition) -> { - return new GameRules.BooleanValue(gamerules_gameruledefinition, initialValue); - }, changeCallback, GameRules.GameRuleTypeVisitor::visitBoolean); -@@ -334,7 +334,7 @@ public class GameRules { - return this.value; - } - -- public void set(boolean value, @Nullable MinecraftServer server) { -+ public void set(boolean value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - this.value = value; - this.onChanged(server); - } -@@ -364,7 +364,7 @@ public class GameRules { - return new GameRules.BooleanValue(this.type, this.value); - } - -- public void setFrom(GameRules.BooleanValue rule, @Nullable MinecraftServer server) { -+ public void setFrom(GameRules.BooleanValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - this.value = rule.value; - this.onChanged(server); - } -@@ -374,7 +374,7 @@ public class GameRules { - - private int value; - -- private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { -+ private static GameRules.Type create(int initialValue, BiConsumer changeCallback) { // Paper - return new GameRules.Type<>(IntegerArgumentType::integer, (gamerules_gameruledefinition) -> { - return new GameRules.IntegerValue(gamerules_gameruledefinition, initialValue); - }, changeCallback, GameRules.GameRuleTypeVisitor::visitInteger); -@@ -402,7 +402,7 @@ public class GameRules { - return this.value; - } - -- public void set(int value, @Nullable MinecraftServer server) { -+ public void set(int value, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - this.value = value; - this.onChanged(server); - } -@@ -453,7 +453,7 @@ public class GameRules { - return new GameRules.IntegerValue(this.type, this.value); - } - -- public void setFrom(GameRules.IntegerValue rule, @Nullable MinecraftServer server) { -+ public void setFrom(GameRules.IntegerValue rule, @Nullable net.minecraft.server.level.ServerLevel server) { // Paper - this.value = rule.value; - this.onChanged(server); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 23c68b6cd26fbd05685ebcfbb5e81db4c8dedb29..9d27b093922f3dee9b459f8a9cdfa96f12f2e654 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -1912,7 +1912,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Paper end - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule)); - handle.deserialize(event.getValue()); // Paper -- handle.onChanged(this.getHandle().getServer()); -+ handle.onChanged(this.getHandle()); // Paper - return true; - } - -@@ -1952,7 +1952,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Paper end - GameRules.Value handle = this.getHandle().getGameRules().getRule(CraftWorld.getGameRulesNMS().get(rule.getName())); - handle.deserialize(event.getValue()); // Paper -- handle.onChanged(this.getHandle().getServer()); -+ handle.onChanged(this.getHandle()); // Paper - return true; - } - diff --git a/patches/server/0883-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch b/patches/server/0883-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch deleted file mode 100644 index fff84c5a62..0000000000 --- a/patches/server/0883-Add-pre-unbreaking-amount-to-PlayerItemDamageEvent.patch +++ /dev/null @@ -1,23 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: HexedHero <6012891+HexedHero@users.noreply.github.com> -Date: Sun, 10 Apr 2022 06:26:32 +0100 -Subject: [PATCH] Add pre-unbreaking amount to PlayerItemDamageEvent - - -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 67626e7faa4d0854d31b41c0a702edbeb6ce4270..c18a0bc94d0210396046f4475e49a739088593f3 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -563,10 +563,11 @@ public final class ItemStack { - } - } - -+ int originalDamage = amount; // Paper - amount -= k; - // CraftBukkit start - if (player instanceof ServerPlayer serverPlayer) { // Paper -- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount); // Paper -+ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper - event.getPlayer().getServer().getPluginManager().callEvent(event); - - if (amount != event.getDamage() || event.isCancelled()) { diff --git a/patches/server/0883-WorldCreator-keepSpawnLoaded.patch b/patches/server/0883-WorldCreator-keepSpawnLoaded.patch new file mode 100644 index 0000000000..8d52feaaf9 --- /dev/null +++ b/patches/server/0883-WorldCreator-keepSpawnLoaded.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Sat, 3 Jul 2021 21:18:28 +0100 +Subject: [PATCH] WorldCreator#keepSpawnLoaded + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 6eb74dcf4b1ed5c04f6890a07fbac7b9fff0d161..c75e3cd197bee7caeef3a55b2746c15de3c0c3e4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1248,6 +1248,7 @@ public final class CraftServer implements Server { + internal.setSpawnSettings(true, true); + // Paper - move up + ++ internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper + this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); + internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API + diff --git a/patches/server/0884-Fix-NPE-for-BlockDataMeta-getBlockData.patch b/patches/server/0884-Fix-NPE-for-BlockDataMeta-getBlockData.patch new file mode 100644 index 0000000000..d33dabf6c9 --- /dev/null +++ b/patches/server/0884-Fix-NPE-for-BlockDataMeta-getBlockData.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 27 Mar 2022 16:00:28 -0700 +Subject: [PATCH] Fix NPE for BlockDataMeta#getBlockData + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +index a8294bf057e03c5d866f6da31e6cdfa9edd3f146..3c4dadd0012c11191c873fe25a7625193563915d 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +@@ -1093,7 +1093,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { + + @Override + public BlockData getBlockData(Material material) { +- return CraftBlockData.fromData(BlockItem.getBlockState(CraftMagicNumbers.getBlock(material).defaultBlockState(), blockData)); ++ // Paper start - fix NPE if this.blockData is null ++ final net.minecraft.world.level.block.state.BlockState defaultBlockState = CraftMagicNumbers.getBlock(material).defaultBlockState(); ++ return CraftBlockData.fromData(this.blockData == null ? defaultBlockState : BlockItem.getBlockState(defaultBlockState, blockData)); ++ // Paper end + } + + @Override diff --git a/patches/server/0884-WorldCreator-keepSpawnLoaded.patch b/patches/server/0884-WorldCreator-keepSpawnLoaded.patch deleted file mode 100644 index 39ab6f7b35..0000000000 --- a/patches/server/0884-WorldCreator-keepSpawnLoaded.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Sat, 3 Jul 2021 21:18:28 +0100 -Subject: [PATCH] WorldCreator#keepSpawnLoaded - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index dc3a0acfd0b4e5210994f5beb59df4351c67af69..eaf51d2b6ad149e19585e6cf600dddbf4bb3e68b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1248,6 +1248,7 @@ public final class CraftServer implements Server { - internal.setSpawnSettings(true, true); - // Paper - move up - -+ internal.keepSpawnInMemory = creator.keepSpawnLoaded().toBooleanOrElse(internal.getWorld().getKeepSpawnInMemory()); // Paper - this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal); - internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API - diff --git a/patches/server/0885-Fix-NPE-for-BlockDataMeta-getBlockData.patch b/patches/server/0885-Fix-NPE-for-BlockDataMeta-getBlockData.patch deleted file mode 100644 index d33dabf6c9..0000000000 --- a/patches/server/0885-Fix-NPE-for-BlockDataMeta-getBlockData.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 27 Mar 2022 16:00:28 -0700 -Subject: [PATCH] Fix NPE for BlockDataMeta#getBlockData - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -index a8294bf057e03c5d866f6da31e6cdfa9edd3f146..3c4dadd0012c11191c873fe25a7625193563915d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java -@@ -1093,7 +1093,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { - - @Override - public BlockData getBlockData(Material material) { -- return CraftBlockData.fromData(BlockItem.getBlockState(CraftMagicNumbers.getBlock(material).defaultBlockState(), blockData)); -+ // Paper start - fix NPE if this.blockData is null -+ final net.minecraft.world.level.block.state.BlockState defaultBlockState = CraftMagicNumbers.getBlock(material).defaultBlockState(); -+ return CraftBlockData.fromData(this.blockData == null ? defaultBlockState : BlockItem.getBlockState(defaultBlockState, blockData)); -+ // Paper end - } - - @Override diff --git a/patches/server/0885-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0885-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch new file mode 100644 index 0000000000..79c74b0a57 --- /dev/null +++ b/patches/server/0885-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 2 Feb 2022 13:50:06 -0800 +Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 1ad1f958a9b6e1bc21f1c505aa7ea54950de6cad..9378e83a67a70dbb1fb4f05b33f1e553d008e62b 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -419,12 +419,16 @@ public class ServerPlayerGameMode { + block.destroy(this.level, pos, iblockdata); + } + ++ ItemStack mainHandStack = null; // Paper ++ boolean isCorrectTool = false; // Paper + if (this.isCreative()) { + // return true; // CraftBukkit + } else { + ItemStack itemstack = this.player.getMainHandItem(); + ItemStack itemstack1 = itemstack.copy(); + boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata); ++ mainHandStack = itemstack1; // Paper ++ isCorrectTool = flag1; // Paper + + itemstack.mineBlock(this.level, iblockdata, pos, this.player); + if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items +@@ -445,6 +449,13 @@ public class ServerPlayerGameMode { + if (flag && event != null) { + iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } ++ // Paper start - trigger after items are dropped (check impls of block#playerDestroy) ++ if (mainHandStack != null) { ++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above ++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); ++ } ++ } ++ // Paper end + + return true; + // CraftBukkit end +diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +index 1aaab26c59bb9255955aff34ea1d057b88152768..0e61c47307b9e06eddc43a3aa5f8ae9da24acd08 100644 +--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java +@@ -88,7 +88,7 @@ public class BeehiveBlock extends BaseEntityBlock { + this.angerNearbyBees(world, pos); + } + +- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, stack, tileentitybeehive.getOccupantCount()); ++ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, stack, tileentitybeehive.getOccupantCount()); // Paper - moved until after items are dropped + } + + } diff --git a/patches/server/0886-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0886-Add-EntityDyeEvent-and-CollarColorable-interface.patch new file mode 100644 index 0000000000..8eacb2217e --- /dev/null +++ b/patches/server/0886-Add-EntityDyeEvent-and-CollarColorable-interface.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Fri, 18 Mar 2022 21:15:55 -0700 +Subject: [PATCH] Add EntityDyeEvent and CollarColorable interface + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java +index e44352857272a2a4027c67bd25a28a9498b7bb49..ab86bfdaebe9b8791f0cfa6e0c61f80c8f891a93 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java +@@ -399,6 +399,13 @@ public class Cat extends TamableAnimal { + DyeColor enumcolor = ((DyeItem) item).getDyeColor(); + + if (enumcolor != this.getCollarColor()) { ++ // Paper start ++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ enumcolor = DyeColor.byId(event.getColor().getWoolData()); ++ // Paper end + this.setCollarColor(enumcolor); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java +index 34dadacb8238896b799716875ea5d0e924c323e8..45c3cec839a7c23903dedf6e3e004305da2adceb 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java +@@ -392,6 +392,13 @@ public class Wolf extends TamableAnimal implements NeutralMob { + DyeColor enumcolor = ((DyeItem) item).getDyeColor(); + + if (enumcolor != this.getCollarColor()) { ++ // Paper start ++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); ++ if (!event.callEvent()) { ++ return InteractionResult.FAIL; ++ } ++ enumcolor = DyeColor.byId(event.getColor().getWoolData()); ++ // Paper end + this.setCollarColor(enumcolor); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); diff --git a/patches/server/0886-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0886-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch deleted file mode 100644 index 79c74b0a57..0000000000 --- a/patches/server/0886-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 2 Feb 2022 13:50:06 -0800 -Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 1ad1f958a9b6e1bc21f1c505aa7ea54950de6cad..9378e83a67a70dbb1fb4f05b33f1e553d008e62b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -419,12 +419,16 @@ public class ServerPlayerGameMode { - block.destroy(this.level, pos, iblockdata); - } - -+ ItemStack mainHandStack = null; // Paper -+ boolean isCorrectTool = false; // Paper - if (this.isCreative()) { - // return true; // CraftBukkit - } else { - ItemStack itemstack = this.player.getMainHandItem(); - ItemStack itemstack1 = itemstack.copy(); - boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata); -+ mainHandStack = itemstack1; // Paper -+ isCorrectTool = flag1; // Paper - - itemstack.mineBlock(this.level, iblockdata, pos, this.player); - if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items -@@ -445,6 +449,13 @@ public class ServerPlayerGameMode { - if (flag && event != null) { - iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper - } -+ // Paper start - trigger after items are dropped (check impls of block#playerDestroy) -+ if (mainHandStack != null) { -+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above -+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); -+ } -+ } -+ // Paper end - - return true; - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -index 1aaab26c59bb9255955aff34ea1d057b88152768..0e61c47307b9e06eddc43a3aa5f8ae9da24acd08 100644 ---- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java -@@ -88,7 +88,7 @@ public class BeehiveBlock extends BaseEntityBlock { - this.angerNearbyBees(world, pos); - } - -- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, stack, tileentitybeehive.getOccupantCount()); -+ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, stack, tileentitybeehive.getOccupantCount()); // Paper - moved until after items are dropped - } - - } diff --git a/patches/server/0887-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0887-Add-EntityDyeEvent-and-CollarColorable-interface.patch deleted file mode 100644 index 8eacb2217e..0000000000 --- a/patches/server/0887-Add-EntityDyeEvent-and-CollarColorable-interface.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Fri, 18 Mar 2022 21:15:55 -0700 -Subject: [PATCH] Add EntityDyeEvent and CollarColorable interface - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index e44352857272a2a4027c67bd25a28a9498b7bb49..ab86bfdaebe9b8791f0cfa6e0c61f80c8f891a93 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -399,6 +399,13 @@ public class Cat extends TamableAnimal { - DyeColor enumcolor = ((DyeItem) item).getDyeColor(); - - if (enumcolor != this.getCollarColor()) { -+ // Paper start -+ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ enumcolor = DyeColor.byId(event.getColor().getWoolData()); -+ // Paper end - this.setCollarColor(enumcolor); - if (!player.getAbilities().instabuild) { - itemstack.shrink(1); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -index 34dadacb8238896b799716875ea5d0e924c323e8..45c3cec839a7c23903dedf6e3e004305da2adceb 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -@@ -392,6 +392,13 @@ public class Wolf extends TamableAnimal implements NeutralMob { - DyeColor enumcolor = ((DyeItem) item).getDyeColor(); - - if (enumcolor != this.getCollarColor()) { -+ // Paper start -+ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity()); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ enumcolor = DyeColor.byId(event.getColor().getWoolData()); -+ // Paper end - this.setCollarColor(enumcolor); - if (!player.getAbilities().instabuild) { - itemstack.shrink(1); diff --git a/patches/server/0887-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0887-Fire-CauldronLevelChange-on-initial-fill.patch new file mode 100644 index 0000000000..c0fcd53f55 --- /dev/null +++ b/patches/server/0887-Fire-CauldronLevelChange-on-initial-fill.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 29 Mar 2022 13:46:23 -0700 +Subject: [PATCH] Fire CauldronLevelChange on initial fill + +Also don't fire level events or game events if stalactite +drip is cancelled + +diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +index 53089c3a36bf2c0ec1bc9b436884deff0c30f028..46846ac9981e447fc6886aecf82563378a4f5548 100644 +--- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +@@ -36,10 +36,18 @@ public class CauldronBlock extends AbstractCauldronBlock { + public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { + if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) { + if (precipitation == Biome.Precipitation.RAIN) { +- world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); ++ // Paper start - call event for initial fill ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { ++ return; ++ } ++ // Paper end + world.gameEvent((Entity) null, GameEvent.BLOCK_CHANGE, pos); + } else if (precipitation == Biome.Precipitation.SNOW) { +- world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()); ++ // Paper start - call event for initial fill ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { ++ return; ++ } ++ // Paper end + world.gameEvent((Entity) null, GameEvent.BLOCK_CHANGE, pos); + } + +@@ -57,11 +65,19 @@ public class CauldronBlock extends AbstractCauldronBlock { + + if (fluid == Fluids.WATER) { + iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState(); +- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit ++ // Paper start - don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end + world.levelEvent(1047, pos, 0); + } else if (fluid == Fluids.LAVA) { + iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState(); +- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit ++ // Paper start - don't send level event or game event if cancelled ++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit ++ return; ++ } ++ // Paper end + world.levelEvent(1046, pos, 0); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 24d2da792bc498adf4251555a538df4cafe2e827..1a7cb12fd3f183c00079d679452a01b8df8d2bbb 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -91,7 +91,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + } + + // CraftBukkit start +- public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { ++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable + CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition); + newState.setData(newBlock); + diff --git a/patches/server/0888-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0888-Fire-CauldronLevelChange-on-initial-fill.patch deleted file mode 100644 index c0fcd53f55..0000000000 --- a/patches/server/0888-Fire-CauldronLevelChange-on-initial-fill.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 29 Mar 2022 13:46:23 -0700 -Subject: [PATCH] Fire CauldronLevelChange on initial fill - -Also don't fire level events or game events if stalactite -drip is cancelled - -diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -index 53089c3a36bf2c0ec1bc9b436884deff0c30f028..46846ac9981e447fc6886aecf82563378a4f5548 100644 ---- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -@@ -36,10 +36,18 @@ public class CauldronBlock extends AbstractCauldronBlock { - public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) { - if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) { - if (precipitation == Biome.Precipitation.RAIN) { -- world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState()); -+ // Paper start - call event for initial fill -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { -+ return; -+ } -+ // Paper end - world.gameEvent((Entity) null, GameEvent.BLOCK_CHANGE, pos); - } else if (precipitation == Biome.Precipitation.SNOW) { -- world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState()); -+ // Paper start - call event for initial fill -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { -+ return; -+ } -+ // Paper end - world.gameEvent((Entity) null, GameEvent.BLOCK_CHANGE, pos); - } - -@@ -57,11 +65,19 @@ public class CauldronBlock extends AbstractCauldronBlock { - - if (fluid == Fluids.WATER) { - iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState(); -- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit -+ // Paper start - don't send level event or game event if cancelled -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit -+ return; -+ } -+ // Paper end - world.levelEvent(1047, pos, 0); - } else if (fluid == Fluids.LAVA) { - iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState(); -- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit -+ // Paper start - don't send level event or game event if cancelled -+ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit -+ return; -+ } -+ // Paper end - world.levelEvent(1046, pos, 0); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 24d2da792bc498adf4251555a538df4cafe2e827..1a7cb12fd3f183c00079d679452a01b8df8d2bbb 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -91,7 +91,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - } - - // CraftBukkit start -- public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { -+ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable - CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition); - newState.setData(newBlock); - diff --git a/patches/server/0888-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0888-fix-powder-snow-cauldrons-not-turning-to-water.patch new file mode 100644 index 0000000000..aef5bd0551 --- /dev/null +++ b/patches/server/0888-fix-powder-snow-cauldrons-not-turning-to-water.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 30 Dec 2021 14:02:13 -0800 +Subject: [PATCH] fix powder snow cauldrons not turning to water + +Powder snow cauldrons should turn to water when +extinguishing an entity + +diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +index 1a7cb12fd3f183c00079d679452a01b8df8d2bbb..a223959f766ac41aff7aeff80606f5e7c37ebf49 100644 +--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java +@@ -64,7 +64,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { + // CraftBukkit start + if (entity.mayInteract(world, pos)) { +- if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH)) { ++ if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities + return; + } + } +@@ -74,9 +74,15 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { + + } + ++ @Deprecated // Paper - use #handleEntityOnFireInsideWithEvent + protected void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { + LayeredCauldronBlock.lowerFillLevel(state, world, pos); + } ++ // Paper start ++ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) { ++ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); ++ } ++ // Paper end + + public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) { + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java +index 54c8f2ccadd685b43d7ee032a95bfcf193357ce9..7f6b240bbbb773ca49e0e6290169cc81f5529af5 100644 +--- a/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java +@@ -16,7 +16,14 @@ public class PowderSnowCauldronBlock extends LayeredCauldronBlock { + } + + @Override ++ @Deprecated // Paper - use #handleEntityOnFireInsideWithEvent + protected void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { + lowerFillLevel(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), world, pos); + } ++ // Paper - replace powdered snow with water (taken from #handleEntityOnFireInside) ++ @Override ++ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, net.minecraft.world.entity.Entity entity) { ++ return super.handleEntityOnFireInsideWithEvent(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), world, pos, entity); ++ } ++ // Paper end + } diff --git a/patches/server/0889-Add-PlayerStopUsingItemEvent.patch b/patches/server/0889-Add-PlayerStopUsingItemEvent.patch new file mode 100644 index 0000000000..90dcaae6a1 --- /dev/null +++ b/patches/server/0889-Add-PlayerStopUsingItemEvent.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Tue, 3 May 2022 20:41:37 -0400 +Subject: [PATCH] Add PlayerStopUsingItemEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 73d931489c76f2effe71362a46a69087a1a09463..33c22f908440664dc9d67f1678a3c4bd8b862457 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3926,6 +3926,7 @@ public abstract class LivingEntity extends Entity { + + public void releaseUsingItem() { + if (!this.useItem.isEmpty()) { ++ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper + this.useItem.releaseUsing(this.level, this, this.getUseItemRemainingTicks()); + if (this.useItem.useOnRelease()) { + this.updatingUsingItem(); diff --git a/patches/server/0889-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0889-fix-powder-snow-cauldrons-not-turning-to-water.patch deleted file mode 100644 index aef5bd0551..0000000000 --- a/patches/server/0889-fix-powder-snow-cauldrons-not-turning-to-water.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 30 Dec 2021 14:02:13 -0800 -Subject: [PATCH] fix powder snow cauldrons not turning to water - -Powder snow cauldrons should turn to water when -extinguishing an entity - -diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -index 1a7cb12fd3f183c00079d679452a01b8df8d2bbb..a223959f766ac41aff7aeff80606f5e7c37ebf49 100644 ---- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java -@@ -64,7 +64,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - if (!world.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) { - // CraftBukkit start - if (entity.mayInteract(world, pos)) { -- if (!LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH)) { -+ if (!this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities - return; - } - } -@@ -74,9 +74,15 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock { - - } - -+ @Deprecated // Paper - use #handleEntityOnFireInsideWithEvent - protected void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { - LayeredCauldronBlock.lowerFillLevel(state, world, pos); - } -+ // Paper start -+ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) { -+ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH); -+ } -+ // Paper end - - public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) { - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java -index 54c8f2ccadd685b43d7ee032a95bfcf193357ce9..7f6b240bbbb773ca49e0e6290169cc81f5529af5 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowCauldronBlock.java -@@ -16,7 +16,14 @@ public class PowderSnowCauldronBlock extends LayeredCauldronBlock { - } - - @Override -+ @Deprecated // Paper - use #handleEntityOnFireInsideWithEvent - protected void handleEntityOnFireInside(BlockState state, Level world, BlockPos pos) { - lowerFillLevel(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), world, pos); - } -+ // Paper - replace powdered snow with water (taken from #handleEntityOnFireInside) -+ @Override -+ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, net.minecraft.world.entity.Entity entity) { -+ return super.handleEntityOnFireInsideWithEvent(Blocks.WATER_CAULDRON.defaultBlockState().setValue(LEVEL, state.getValue(LEVEL)), world, pos, entity); -+ } -+ // Paper end - } diff --git a/patches/server/0890-Add-PlayerStopUsingItemEvent.patch b/patches/server/0890-Add-PlayerStopUsingItemEvent.patch deleted file mode 100644 index 3d11c6c6d8..0000000000 --- a/patches/server/0890-Add-PlayerStopUsingItemEvent.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Tue, 3 May 2022 20:41:37 -0400 -Subject: [PATCH] Add PlayerStopUsingItemEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 44c0f77bdeeb9061b1dfcd904ed2c63910e30e42..cff7993bdafd2f69e46c9985c7601a69ae47f452 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3904,6 +3904,7 @@ public abstract class LivingEntity extends Entity { - - public void releaseUsingItem() { - if (!this.useItem.isEmpty()) { -+ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - this.useItem.releaseUsing(this.level, this, this.getUseItemRemainingTicks()); - if (this.useItem.useOnRelease()) { - this.updatingUsingItem(); diff --git a/patches/server/0890-FallingBlock-auto-expire-setting.patch b/patches/server/0890-FallingBlock-auto-expire-setting.patch new file mode 100644 index 0000000000..a7b0c20dd6 --- /dev/null +++ b/patches/server/0890-FallingBlock-auto-expire-setting.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 5 Dec 2021 14:58:17 -0500 +Subject: [PATCH] FallingBlock auto expire setting + + +diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +index f6405e862b15b71dbb96215e604610fe5ff59bfc..ef07967b64180c54338b8fb2ba1780adec87f333 100644 +--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +@@ -61,6 +61,7 @@ public class FallingBlockEntity extends Entity { + @Nullable + public CompoundTag blockData; + protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); ++ public boolean autoExpire = true; // Paper - Auto expire setting + + public FallingBlockEntity(EntityType type, Level world) { + super(type, world); +@@ -175,7 +176,7 @@ public class FallingBlockEntity extends Entity { + } + + if (!this.onGround && !flag1) { +- if (!this.level.isClientSide && (this.time > 100 && (blockposition.getY() <= this.level.getMinBuildHeight() || blockposition.getY() > this.level.getMaxBuildHeight()) || this.time > 600)) { ++ if (!this.level.isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level.getMinBuildHeight() || blockposition.getY() > this.level.getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Auto expire setting + if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + this.spawnAtLocation((ItemLike) block); + } +@@ -321,6 +322,7 @@ public class FallingBlockEntity extends Entity { + if (this.blockData != null) { + nbt.put("TileEntityData", this.blockData); + } ++ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - AutoExpire setting + + } + +@@ -367,6 +369,10 @@ public class FallingBlockEntity extends Entity { + int srcZ = nbt.getInt("SourceLoc_z"); + this.setOrigin(new org.bukkit.Location(level.getWorld(), srcX, srcY, srcZ)); + } ++ ++ if (nbt.contains("Paper.AutoExpire")) { ++ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); ++ } + // Paper end + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +index 0de415236fe9997fc3ffedba20b8df68647cb822..87c413c2f3b59ae9ef36e5becc10b29a81348022 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +@@ -58,6 +58,17 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { + public void setHurtEntities(boolean hurtEntities) { + this.getHandle().hurtEntities = hurtEntities; + } ++ // Paper Start - Auto expire setting ++ @Override ++ public boolean doesAutoExpire() { ++ return this.getHandle().autoExpire; ++ } ++ ++ @Override ++ public void shouldAutoExpire(boolean autoExpires) { ++ this.getHandle().autoExpire = autoExpires; ++ } ++ // Paper End - Auto expire setting + + @Override + public void setTicksLived(int value) { diff --git a/patches/server/0891-Don-t-tick-markers.patch b/patches/server/0891-Don-t-tick-markers.patch new file mode 100644 index 0000000000..d21ff9fe5a --- /dev/null +++ b/patches/server/0891-Don-t-tick-markers.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Fri, 7 Jan 2022 11:58:26 +0100 +Subject: [PATCH] Don't tick markers + +Fixes https://github.com/PaperMC/Paper/issues/7276 by not adding markers to the entity +tick list at all and ignoring them in Spigot's activation range checks. The entity tick +list is only used in the tick and tickPassenger methods, so we can safely not add the +markers to it. + +diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +index 68f99e93ed3e843b4001a7a27620f88a48b85e67..0dc96c39151ec4dbeec3947cb17606f53a6392d4 100644 +--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java ++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +@@ -103,7 +103,7 @@ public final class EntityCommand implements PaperSubcommand { + ChunkPos chunk = e.chunkPosition(); + info.left++; + info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); +- if (!chunkProviderServer.isPositionTicking(e)) { ++ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. + nonEntityTicking.merge(key, 1, Integer::sum); + } + }); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index eceaa1f2ede1c068f9090d13bf9d3b3afaa08cc3..e5a64e70020487b15825a865623afa45b0ae59d4 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2495,6 +2495,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + public void onTickingStart(Entity entity) { ++ if (entity instanceof net.minecraft.world.entity.Marker) return; // Paper - Don't tick markers + ServerLevel.this.entityTickList.add(entity); + ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify + } +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index b1ed97618d08d7691d24f89e9e9b0ed0f2bddd09..40b382c2e0e33fe5c24a51b211cd2f9557a60c5e 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -212,7 +212,7 @@ public class ActivationRange + // Paper end + + // Paper start +- java.util.List entities = world.getEntities((Entity)null, maxBB, null); ++ java.util.List entities = world.getEntities((Entity)null, maxBB, (e) -> !(e instanceof net.minecraft.world.entity.Marker)); // Don't tick markers + for (int i = 0; i < entities.size(); i++) { + Entity entity = entities.get(i); + ActivationRange.activateEntity(entity); diff --git a/patches/server/0891-FallingBlock-auto-expire-setting.patch b/patches/server/0891-FallingBlock-auto-expire-setting.patch deleted file mode 100644 index a7b0c20dd6..0000000000 --- a/patches/server/0891-FallingBlock-auto-expire-setting.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Dec 2021 14:58:17 -0500 -Subject: [PATCH] FallingBlock auto expire setting - - -diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index f6405e862b15b71dbb96215e604610fe5ff59bfc..ef07967b64180c54338b8fb2ba1780adec87f333 100644 ---- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -@@ -61,6 +61,7 @@ public class FallingBlockEntity extends Entity { - @Nullable - public CompoundTag blockData; - protected static final EntityDataAccessor DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS); -+ public boolean autoExpire = true; // Paper - Auto expire setting - - public FallingBlockEntity(EntityType type, Level world) { - super(type, world); -@@ -175,7 +176,7 @@ public class FallingBlockEntity extends Entity { - } - - if (!this.onGround && !flag1) { -- if (!this.level.isClientSide && (this.time > 100 && (blockposition.getY() <= this.level.getMinBuildHeight() || blockposition.getY() > this.level.getMaxBuildHeight()) || this.time > 600)) { -+ if (!this.level.isClientSide && ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level.getMinBuildHeight() || blockposition.getY() > this.level.getMaxBuildHeight()) || (this.time > 600 && autoExpire))) { // Paper - Auto expire setting - if (this.dropItem && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { - this.spawnAtLocation((ItemLike) block); - } -@@ -321,6 +322,7 @@ public class FallingBlockEntity extends Entity { - if (this.blockData != null) { - nbt.put("TileEntityData", this.blockData); - } -+ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - AutoExpire setting - - } - -@@ -367,6 +369,10 @@ public class FallingBlockEntity extends Entity { - int srcZ = nbt.getInt("SourceLoc_z"); - this.setOrigin(new org.bukkit.Location(level.getWorld(), srcX, srcY, srcZ)); - } -+ -+ if (nbt.contains("Paper.AutoExpire")) { -+ this.autoExpire = nbt.getBoolean("Paper.AutoExpire"); -+ } - // Paper end - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -index 0de415236fe9997fc3ffedba20b8df68647cb822..87c413c2f3b59ae9ef36e5becc10b29a81348022 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -@@ -58,6 +58,17 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { - public void setHurtEntities(boolean hurtEntities) { - this.getHandle().hurtEntities = hurtEntities; - } -+ // Paper Start - Auto expire setting -+ @Override -+ public boolean doesAutoExpire() { -+ return this.getHandle().autoExpire; -+ } -+ -+ @Override -+ public void shouldAutoExpire(boolean autoExpires) { -+ this.getHandle().autoExpire = autoExpires; -+ } -+ // Paper End - Auto expire setting - - @Override - public void setTicksLived(int value) { diff --git a/patches/server/0892-Do-not-accept-invalid-client-settings.patch b/patches/server/0892-Do-not-accept-invalid-client-settings.patch new file mode 100644 index 0000000000..88c7e8c514 --- /dev/null +++ b/patches/server/0892-Do-not-accept-invalid-client-settings.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 7 May 2022 14:58:53 -0700 +Subject: [PATCH] Do not accept invalid client settings + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 15694736c7ee8836355409e0046e221ebdb7e524..a7fe72d745625951172a8fdf9a4689b5bf540445 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3590,6 +3590,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); ++ // Paper start - do not accept invalid information ++ if (packet.viewDistance() < 0) { ++ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.viewDistance()); ++ this.disconnect("Invalid client settings", PlayerKickEvent.Cause.ILLEGAL_ACTION); ++ return; ++ } ++ // Paper end - do not accept invalid information + this.player.updateOptions(packet); + } + diff --git a/patches/server/0892-Don-t-tick-markers.patch b/patches/server/0892-Don-t-tick-markers.patch deleted file mode 100644 index d21ff9fe5a..0000000000 --- a/patches/server/0892-Don-t-tick-markers.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Fri, 7 Jan 2022 11:58:26 +0100 -Subject: [PATCH] Don't tick markers - -Fixes https://github.com/PaperMC/Paper/issues/7276 by not adding markers to the entity -tick list at all and ignoring them in Spigot's activation range checks. The entity tick -list is only used in the tick and tickPassenger methods, so we can safely not add the -markers to it. - -diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -index 68f99e93ed3e843b4001a7a27620f88a48b85e67..0dc96c39151ec4dbeec3947cb17606f53a6392d4 100644 ---- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -@@ -103,7 +103,7 @@ public final class EntityCommand implements PaperSubcommand { - ChunkPos chunk = e.chunkPosition(); - info.left++; - info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1); -- if (!chunkProviderServer.isPositionTicking(e)) { -+ if (!chunkProviderServer.isPositionTicking(e) || e instanceof net.minecraft.world.entity.Marker) { // Markers aren't ticked. - nonEntityTicking.merge(key, 1, Integer::sum); - } - }); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index eceaa1f2ede1c068f9090d13bf9d3b3afaa08cc3..e5a64e70020487b15825a865623afa45b0ae59d4 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2495,6 +2495,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - - public void onTickingStart(Entity entity) { -+ if (entity instanceof net.minecraft.world.entity.Marker) return; // Paper - Don't tick markers - ServerLevel.this.entityTickList.add(entity); - ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify - } -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index b1ed97618d08d7691d24f89e9e9b0ed0f2bddd09..40b382c2e0e33fe5c24a51b211cd2f9557a60c5e 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -212,7 +212,7 @@ public class ActivationRange - // Paper end - - // Paper start -- java.util.List entities = world.getEntities((Entity)null, maxBB, null); -+ java.util.List entities = world.getEntities((Entity)null, maxBB, (e) -> !(e instanceof net.minecraft.world.entity.Marker)); // Don't tick markers - for (int i = 0; i < entities.size(); i++) { - Entity entity = entities.get(i); - ActivationRange.activateEntity(entity); diff --git a/patches/server/0893-Add-support-for-Proxy-Protocol.patch b/patches/server/0893-Add-support-for-Proxy-Protocol.patch new file mode 100644 index 0000000000..1cf43fbbe8 --- /dev/null +++ b/patches/server/0893-Add-support-for-Proxy-Protocol.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: PanSzelescik +Date: Thu, 7 Apr 2022 16:13:39 +0200 +Subject: [PATCH] Add support for Proxy Protocol + + +diff --git a/build.gradle.kts b/build.gradle.kts +index effc19371309a1af44e1b660b547b58530a8df3c..2374cc9bab5039d0a0dc11d4b2ec573ab75778a7 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -22,6 +22,7 @@ dependencies { + */ + implementation("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - implementation + annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - Needed to generate meta for our Log4j plugins ++ implementation("io.netty:netty-codec-haproxy:4.1.77.Final") + // Paper end + implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper + implementation("org.apache.logging.log4j:log4j-slf4j18-impl:2.17.1") // Paper +diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +index cfdbcd024de6ad0f9d4e83b2f912b36ef3299458..abcc3266d18f34d160eac87fdea153dce24c60b8 100644 +--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java ++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +@@ -111,6 +111,12 @@ public class ServerConnectionListener { + ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); + // Paper end + ++ // Paper start - indicate Proxy Protocol usage ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ ServerConnectionListener.LOGGER.info("Paper: Using Proxy Protocol"); ++ } ++ // Paper end ++ + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { + protected void initChannel(Channel channel) { + try { +@@ -124,6 +130,30 @@ public class ServerConnectionListener { + int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); + Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); + ++ // Paper start - Add support for Proxy Protocol ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); ++ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { ++ @Override ++ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ++ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { ++ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { ++ String realaddress = message.sourceAddress(); ++ int realport = message.sourcePort(); ++ ++ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); ++ ++ Connection connection = (Connection) channel.pipeline().get("packet_handler"); ++ connection.address = socketaddr; ++ } ++ } else { ++ super.channelRead(ctx, msg); ++ } ++ } ++ }); ++ } ++ // Paper end ++ + // ServerConnectionListener.this.connections.add((Connection) object); // CraftBukkit - decompile error + pending.add((Connection) object); // Paper + channel.pipeline().addLast("packet_handler", (ChannelHandler) object); diff --git a/patches/server/0893-Do-not-accept-invalid-client-settings.patch b/patches/server/0893-Do-not-accept-invalid-client-settings.patch deleted file mode 100644 index 55cc9a0e42..0000000000 --- a/patches/server/0893-Do-not-accept-invalid-client-settings.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sat, 7 May 2022 14:58:53 -0700 -Subject: [PATCH] Do not accept invalid client settings - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 8e7d7d9f1f228bd0f71abfe46a3cef1f1a72a7ed..6123e1151dad07fbfeb726d338a2268d9707c07d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3586,6 +3586,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - @Override - public void handleClientInformation(ServerboundClientInformationPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); -+ // Paper start - do not accept invalid information -+ if (packet.viewDistance() < 0) { -+ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.viewDistance()); -+ this.disconnect("Invalid client settings", PlayerKickEvent.Cause.ILLEGAL_ACTION); -+ return; -+ } -+ // Paper end - do not accept invalid information - this.player.updateOptions(packet); - } - diff --git a/patches/server/0894-Add-support-for-Proxy-Protocol.patch b/patches/server/0894-Add-support-for-Proxy-Protocol.patch deleted file mode 100644 index 1cf43fbbe8..0000000000 --- a/patches/server/0894-Add-support-for-Proxy-Protocol.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PanSzelescik -Date: Thu, 7 Apr 2022 16:13:39 +0200 -Subject: [PATCH] Add support for Proxy Protocol - - -diff --git a/build.gradle.kts b/build.gradle.kts -index effc19371309a1af44e1b660b547b58530a8df3c..2374cc9bab5039d0a0dc11d4b2ec573ab75778a7 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -22,6 +22,7 @@ dependencies { - */ - implementation("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - implementation - annotationProcessor("org.apache.logging.log4j:log4j-core:2.17.1") // Paper - Needed to generate meta for our Log4j plugins -+ implementation("io.netty:netty-codec-haproxy:4.1.77.Final") - // Paper end - implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper - implementation("org.apache.logging.log4j:log4j-slf4j18-impl:2.17.1") // Paper -diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -index cfdbcd024de6ad0f9d4e83b2f912b36ef3299458..abcc3266d18f34d160eac87fdea153dce24c60b8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java -@@ -111,6 +111,12 @@ public class ServerConnectionListener { - ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); - // Paper end - -+ // Paper start - indicate Proxy Protocol usage -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ ServerConnectionListener.LOGGER.info("Paper: Using Proxy Protocol"); -+ } -+ // Paper end -+ - this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) { - try { -@@ -124,6 +130,30 @@ public class ServerConnectionListener { - int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); - Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); - -+ // Paper start - Add support for Proxy Protocol -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); -+ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { -+ @Override -+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -+ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { -+ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { -+ String realaddress = message.sourceAddress(); -+ int realport = message.sourcePort(); -+ -+ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); -+ -+ Connection connection = (Connection) channel.pipeline().get("packet_handler"); -+ connection.address = socketaddr; -+ } -+ } else { -+ super.channelRead(ctx, msg); -+ } -+ } -+ }); -+ } -+ // Paper end -+ - // ServerConnectionListener.this.connections.add((Connection) object); // CraftBukkit - decompile error - pending.add((Connection) object); // Paper - channel.pipeline().addLast("packet_handler", (ChannelHandler) object); diff --git a/patches/server/0894-Fix-OfflinePlayer-getBedSpawnLocation.patch b/patches/server/0894-Fix-OfflinePlayer-getBedSpawnLocation.patch new file mode 100644 index 0000000000..b11ce011a5 --- /dev/null +++ b/patches/server/0894-Fix-OfflinePlayer-getBedSpawnLocation.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Mon, 30 May 2022 16:03:36 -0700 +Subject: [PATCH] Fix OfflinePlayer#getBedSpawnLocation + +When calling getBedSpawnLocation on an +instance of CraftOfflinePlayer the world was incorrect +due to the logic for reading the NBT not being up-to-date. + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +index d7823d7dc88cfba6f6ac9dae220e03dea4a0bcdd..6d2ba650f53de8a460857f1846401a20b50cc43c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -31,6 +31,7 @@ import org.bukkit.profile.PlayerProfile; + + @SerializableAs("Player") + public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable { ++ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper + private final GameProfile profile; + private final CraftServer server; + private final PlayerDataStorage storage; +@@ -319,11 +320,20 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + if (data == null) return null; + + if (data.contains("SpawnX") && data.contains("SpawnY") && data.contains("SpawnZ")) { +- String spawnWorld = data.getString("SpawnWorld"); +- if (spawnWorld.equals("")) { +- spawnWorld = this.server.getWorlds().get(0).getName(); ++ // Paper start - fix wrong world ++ final float respawnAngle = data.getFloat("SpawnAngle"); ++ org.bukkit.World spawnWorld = this.server.getWorld(data.getString("SpawnWorld")); // legacy ++ if (data.contains("SpawnDimension")) { ++ com.mojang.serialization.DataResult> result = net.minecraft.world.level.Level.RESOURCE_KEY_CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, data.get("SpawnDimension")); ++ net.minecraft.resources.ResourceKey levelKey = result.resultOrPartial(LOGGER::error).orElse(net.minecraft.world.level.Level.OVERWORLD); ++ net.minecraft.server.level.ServerLevel level = this.server.console.getLevel(levelKey); ++ spawnWorld = level != null ? level.getWorld() : spawnWorld; + } +- return new Location(this.server.getWorld(spawnWorld), data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ")); ++ if (spawnWorld == null) { ++ return null; ++ } ++ return new Location(spawnWorld, data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"), respawnAngle, 0); ++ // Paper end + } + return null; + } diff --git a/patches/server/0895-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch b/patches/server/0895-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch new file mode 100644 index 0000000000..59fabed7e4 --- /dev/null +++ b/patches/server/0895-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sat, 1 Jan 2022 23:11:26 -0800 +Subject: [PATCH] Fix FurnaceInventory for smokers and blast furnaces + + +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 54e61b9b058bee2167461aaaf828ed7a00949c29..53421f780ac8bc2a67f64671fcad632fcdb8bede 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java +@@ -65,7 +65,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return new CraftInventory(tileEntity); + } + +- public static class Furnace extends CraftTileInventoryConverter { ++ public static class Furnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical + + @Override + public Container getTileEntity() { +@@ -73,6 +73,11 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + return furnace; + } + ++ // Paper start - abstract furnace converter to apply to all 3 furnaces ++ } ++ ++ public static abstract class AbstractFurnaceInventoryConverter extends CraftTileInventoryConverter { ++ // Paper end + // Paper start + @Override + public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { +@@ -170,7 +175,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + } + } + +- public static class BlastFurnace extends CraftTileInventoryConverter { ++ public static class BlastFurnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical + + @Override + public Container getTileEntity() { +@@ -186,7 +191,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat + } + } + +- public static class Smoker extends CraftTileInventoryConverter { ++ public static class Smoker extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical + + @Override + public Container getTileEntity() { diff --git a/patches/server/0895-Fix-OfflinePlayer-getBedSpawnLocation.patch b/patches/server/0895-Fix-OfflinePlayer-getBedSpawnLocation.patch deleted file mode 100644 index b11ce011a5..0000000000 --- a/patches/server/0895-Fix-OfflinePlayer-getBedSpawnLocation.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Mon, 30 May 2022 16:03:36 -0700 -Subject: [PATCH] Fix OfflinePlayer#getBedSpawnLocation - -When calling getBedSpawnLocation on an -instance of CraftOfflinePlayer the world was incorrect -due to the logic for reading the NBT not being up-to-date. - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index d7823d7dc88cfba6f6ac9dae220e03dea4a0bcdd..6d2ba650f53de8a460857f1846401a20b50cc43c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -31,6 +31,7 @@ import org.bukkit.profile.PlayerProfile; - - @SerializableAs("Player") - public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable { -+ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - private final GameProfile profile; - private final CraftServer server; - private final PlayerDataStorage storage; -@@ -319,11 +320,20 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - if (data == null) return null; - - if (data.contains("SpawnX") && data.contains("SpawnY") && data.contains("SpawnZ")) { -- String spawnWorld = data.getString("SpawnWorld"); -- if (spawnWorld.equals("")) { -- spawnWorld = this.server.getWorlds().get(0).getName(); -+ // Paper start - fix wrong world -+ final float respawnAngle = data.getFloat("SpawnAngle"); -+ org.bukkit.World spawnWorld = this.server.getWorld(data.getString("SpawnWorld")); // legacy -+ if (data.contains("SpawnDimension")) { -+ com.mojang.serialization.DataResult> result = net.minecraft.world.level.Level.RESOURCE_KEY_CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, data.get("SpawnDimension")); -+ net.minecraft.resources.ResourceKey levelKey = result.resultOrPartial(LOGGER::error).orElse(net.minecraft.world.level.Level.OVERWORLD); -+ net.minecraft.server.level.ServerLevel level = this.server.console.getLevel(levelKey); -+ spawnWorld = level != null ? level.getWorld() : spawnWorld; - } -- return new Location(this.server.getWorld(spawnWorld), data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ")); -+ if (spawnWorld == null) { -+ return null; -+ } -+ return new Location(spawnWorld, data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"), respawnAngle, 0); -+ // Paper end - } - return null; - } diff --git a/patches/server/0896-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch b/patches/server/0896-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch deleted file mode 100644 index 59fabed7e4..0000000000 --- a/patches/server/0896-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sat, 1 Jan 2022 23:11:26 -0800 -Subject: [PATCH] Fix FurnaceInventory for smokers and blast furnaces - - -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 54e61b9b058bee2167461aaaf828ed7a00949c29..53421f780ac8bc2a67f64671fcad632fcdb8bede 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java -@@ -65,7 +65,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat - return new CraftInventory(tileEntity); - } - -- public static class Furnace extends CraftTileInventoryConverter { -+ public static class Furnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical - - @Override - public Container getTileEntity() { -@@ -73,6 +73,11 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat - return furnace; - } - -+ // Paper start - abstract furnace converter to apply to all 3 furnaces -+ } -+ -+ public static abstract class AbstractFurnaceInventoryConverter extends CraftTileInventoryConverter { -+ // Paper end - // Paper start - @Override - public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) { -@@ -170,7 +175,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat - } - } - -- public static class BlastFurnace extends CraftTileInventoryConverter { -+ public static class BlastFurnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical - - @Override - public Container getTileEntity() { -@@ -186,7 +191,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat - } - } - -- public static class Smoker extends CraftTileInventoryConverter { -+ public static class Smoker extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical - - @Override - public Container getTileEntity() { diff --git a/patches/server/0896-Sanitize-Sent-BlockEntity-NBT.patch b/patches/server/0896-Sanitize-Sent-BlockEntity-NBT.patch new file mode 100644 index 0000000000..fb3c8b1818 --- /dev/null +++ b/patches/server/0896-Sanitize-Sent-BlockEntity-NBT.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 3 Dec 2021 16:55:50 -0500 +Subject: [PATCH] Sanitize Sent BlockEntity NBT + + +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java +index 12d7cb0eb485987d245454fa2d9fef67ea7e9c76..b1e326cf4f7fe447f81b588dcb0eda9a435e59a8 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java +@@ -17,7 +17,7 @@ public class ClientboundBlockEntityDataPacket implements Packet nbtGetter) { +- return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity)); ++ return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity))); // Paper - Sanitize sent data + } + + public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) { +diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index 2b35059cfe7a27238e0a74df058733897a26ac1c..76b6437a1d807c7e1b673f8feeed1f171ee9a803 100644 +--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -183,6 +183,7 @@ public class ClientboundLevelChunkPacketData { + CompoundTag compoundTag = blockEntity.getUpdateTag(); + BlockPos blockPos = blockEntity.getBlockPos(); + int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ()); ++ blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data + return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag); + } + } +diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index d62181bd8bccfcfdd7da8f635bdf7ebc36294705..b96d57b0bcf21508f8e03e96b7553eb486fdf212 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -256,4 +256,12 @@ public abstract class BlockEntity { + return null; + } + // CraftBukkit end ++ // Paper start ++ public CompoundTag sanitizeSentNbt(CompoundTag tag) { ++ tag.remove("PublicBukkitValues"); ++ ++ return tag; ++ } ++ // Paper end ++ + } diff --git a/patches/server/0897-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0897-Prevent-entity-loading-causing-async-lookups.patch new file mode 100644 index 0000000000..4ac82d46a5 --- /dev/null +++ b/patches/server/0897-Prevent-entity-loading-causing-async-lookups.patch @@ -0,0 +1,69 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 6 Mar 2022 11:09:09 -0500 +Subject: [PATCH] Prevent entity loading causing async lookups + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 19876a474fec6933322775073d4a161f604a102d..45b740753ac8dd20deb8618d5ac85c48737d34f4 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -788,6 +788,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + + public void baseTick() { + this.level.getProfiler().push("entityBaseTick"); ++ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Update last hurt when ticking + this.feetBlockState = null; + if (this.isPassenger() && this.getVehicle().isRemoved()) { + this.stopRiding(); +diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java +index dedf76de5d6f46b9626ca4a98cfffe125b90dd0c..78632fd681049fbd49d0030c23ed204dbc515a44 100644 +--- a/src/main/java/net/minecraft/world/entity/NeutralMob.java ++++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java +@@ -42,18 +42,7 @@ public interface NeutralMob { + UUID uuid = nbt.getUUID("AngryAt"); + + this.setPersistentAngerTarget(uuid); +- Entity entity = ((ServerLevel) world).getEntity(uuid); +- +- if (entity != null) { +- if (entity instanceof Mob) { +- this.setLastHurtByMob((Mob) entity); +- } +- +- if (entity.getType() == EntityType.PLAYER) { +- this.setLastHurtByPlayer((Player) entity); +- } +- +- } ++ // Paper - Moved diff to separate method + } + } + } +@@ -127,4 +116,26 @@ public interface NeutralMob { + + @Nullable + LivingEntity getTarget(); ++ ++ // Paper start - Update last hurt when ticking ++ default void tickInitialPersistentAnger(Level level) { ++ UUID target = getPersistentAngerTarget(); ++ if (target == null) { ++ return; ++ } ++ ++ Entity entity = ((ServerLevel) level).getEntity(target); ++ ++ if (entity != null) { ++ if (entity instanceof Mob) { ++ this.setLastHurtByMob((Mob) entity); ++ } ++ ++ if (entity.getType() == EntityType.PLAYER) { ++ this.setLastHurtByPlayer((Player) entity); ++ } ++ ++ } ++ } ++ // Paper end + } diff --git a/patches/server/0897-Sanitize-Sent-BlockEntity-NBT.patch b/patches/server/0897-Sanitize-Sent-BlockEntity-NBT.patch deleted file mode 100644 index fb3c8b1818..0000000000 --- a/patches/server/0897-Sanitize-Sent-BlockEntity-NBT.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 3 Dec 2021 16:55:50 -0500 -Subject: [PATCH] Sanitize Sent BlockEntity NBT - - -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -index 12d7cb0eb485987d245454fa2d9fef67ea7e9c76..b1e326cf4f7fe447f81b588dcb0eda9a435e59a8 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket.java -@@ -17,7 +17,7 @@ public class ClientboundBlockEntityDataPacket implements Packet nbtGetter) { -- return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), nbtGetter.apply(blockEntity)); -+ return new ClientboundBlockEntityDataPacket(blockEntity.getBlockPos(), blockEntity.getType(), blockEntity.sanitizeSentNbt(nbtGetter.apply(blockEntity))); // Paper - Sanitize sent data - } - - public static ClientboundBlockEntityDataPacket create(BlockEntity blockEntity) { -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -index 2b35059cfe7a27238e0a74df058733897a26ac1c..76b6437a1d807c7e1b673f8feeed1f171ee9a803 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -@@ -183,6 +183,7 @@ public class ClientboundLevelChunkPacketData { - CompoundTag compoundTag = blockEntity.getUpdateTag(); - BlockPos blockPos = blockEntity.getBlockPos(); - int i = SectionPos.sectionRelative(blockPos.getX()) << 4 | SectionPos.sectionRelative(blockPos.getZ()); -+ blockEntity.sanitizeSentNbt(compoundTag); // Paper - Sanitize sent data - return new ClientboundLevelChunkPacketData.BlockEntityInfo(i, blockPos.getY(), blockEntity.getType(), compoundTag.isEmpty() ? null : compoundTag); - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index d62181bd8bccfcfdd7da8f635bdf7ebc36294705..b96d57b0bcf21508f8e03e96b7553eb486fdf212 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -256,4 +256,12 @@ public abstract class BlockEntity { - return null; - } - // CraftBukkit end -+ // Paper start -+ public CompoundTag sanitizeSentNbt(CompoundTag tag) { -+ tag.remove("PublicBukkitValues"); -+ -+ return tag; -+ } -+ // Paper end -+ - } diff --git a/patches/server/0898-Disable-component-selector-resolving-in-books-by-def.patch b/patches/server/0898-Disable-component-selector-resolving-in-books-by-def.patch new file mode 100644 index 0000000000..72862c20c5 --- /dev/null +++ b/patches/server/0898-Disable-component-selector-resolving-in-books-by-def.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 2 Jun 2022 20:35:58 +0200 +Subject: [PATCH] Disable component selector resolving in books by default + + +diff --git a/src/main/java/net/minecraft/world/item/WrittenBookItem.java b/src/main/java/net/minecraft/world/item/WrittenBookItem.java +index a324df312d9bb87d9e0962f8028d900933e70c07..31911c09fe15753ae32fa39417bdc9e9de552a88 100644 +--- a/src/main/java/net/minecraft/world/item/WrittenBookItem.java ++++ b/src/main/java/net/minecraft/world/item/WrittenBookItem.java +@@ -111,7 +111,7 @@ public class WrittenBookItem extends Item { + + public static boolean resolveBookComponents(ItemStack book, @Nullable CommandSourceStack commandSource, @Nullable Player player) { + CompoundTag compoundTag = book.getTag(); +- if (compoundTag != null && !compoundTag.getBoolean("resolved")) { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && compoundTag != null && !compoundTag.getBoolean("resolved")) { // Paper + compoundTag.putBoolean("resolved", true); + if (!makeSureTagIsValid(compoundTag)) { + return false; diff --git a/patches/server/0898-Prevent-entity-loading-causing-async-lookups.patch b/patches/server/0898-Prevent-entity-loading-causing-async-lookups.patch deleted file mode 100644 index 1b4a2b432d..0000000000 --- a/patches/server/0898-Prevent-entity-loading-causing-async-lookups.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 6 Mar 2022 11:09:09 -0500 -Subject: [PATCH] Prevent entity loading causing async lookups - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 6a1f11a2519cc320407696cc7165404a43d0b961..33ec6c1e942a7f852e4726683918ed06cde2e16a 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -788,6 +788,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - - public void baseTick() { - this.level.getProfiler().push("entityBaseTick"); -+ if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Update last hurt when ticking - this.feetBlockState = null; - if (this.isPassenger() && this.getVehicle().isRemoved()) { - this.stopRiding(); -diff --git a/src/main/java/net/minecraft/world/entity/NeutralMob.java b/src/main/java/net/minecraft/world/entity/NeutralMob.java -index dedf76de5d6f46b9626ca4a98cfffe125b90dd0c..78632fd681049fbd49d0030c23ed204dbc515a44 100644 ---- a/src/main/java/net/minecraft/world/entity/NeutralMob.java -+++ b/src/main/java/net/minecraft/world/entity/NeutralMob.java -@@ -42,18 +42,7 @@ public interface NeutralMob { - UUID uuid = nbt.getUUID("AngryAt"); - - this.setPersistentAngerTarget(uuid); -- Entity entity = ((ServerLevel) world).getEntity(uuid); -- -- if (entity != null) { -- if (entity instanceof Mob) { -- this.setLastHurtByMob((Mob) entity); -- } -- -- if (entity.getType() == EntityType.PLAYER) { -- this.setLastHurtByPlayer((Player) entity); -- } -- -- } -+ // Paper - Moved diff to separate method - } - } - } -@@ -127,4 +116,26 @@ public interface NeutralMob { - - @Nullable - LivingEntity getTarget(); -+ -+ // Paper start - Update last hurt when ticking -+ default void tickInitialPersistentAnger(Level level) { -+ UUID target = getPersistentAngerTarget(); -+ if (target == null) { -+ return; -+ } -+ -+ Entity entity = ((ServerLevel) level).getEntity(target); -+ -+ if (entity != null) { -+ if (entity instanceof Mob) { -+ this.setLastHurtByMob((Mob) entity); -+ } -+ -+ if (entity.getType() == EntityType.PLAYER) { -+ this.setLastHurtByPlayer((Player) entity); -+ } -+ -+ } -+ } -+ // Paper end - } diff --git a/patches/server/0899-Disable-component-selector-resolving-in-books-by-def.patch b/patches/server/0899-Disable-component-selector-resolving-in-books-by-def.patch deleted file mode 100644 index 72862c20c5..0000000000 --- a/patches/server/0899-Disable-component-selector-resolving-in-books-by-def.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Thu, 2 Jun 2022 20:35:58 +0200 -Subject: [PATCH] Disable component selector resolving in books by default - - -diff --git a/src/main/java/net/minecraft/world/item/WrittenBookItem.java b/src/main/java/net/minecraft/world/item/WrittenBookItem.java -index a324df312d9bb87d9e0962f8028d900933e70c07..31911c09fe15753ae32fa39417bdc9e9de552a88 100644 ---- a/src/main/java/net/minecraft/world/item/WrittenBookItem.java -+++ b/src/main/java/net/minecraft/world/item/WrittenBookItem.java -@@ -111,7 +111,7 @@ public class WrittenBookItem extends Item { - - public static boolean resolveBookComponents(ItemStack book, @Nullable CommandSourceStack commandSource, @Nullable Player player) { - CompoundTag compoundTag = book.getTag(); -- if (compoundTag != null && !compoundTag.getBoolean("resolved")) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks && compoundTag != null && !compoundTag.getBoolean("resolved")) { // Paper - compoundTag.putBoolean("resolved", true); - if (!makeSureTagIsValid(compoundTag)) { - return false; diff --git a/patches/server/0899-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0899-Throw-exception-on-world-create-while-being-ticked.patch new file mode 100644 index 0000000000..69beef7146 --- /dev/null +++ b/patches/server/0899-Throw-exception-on-world-create-while-being-ticked.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 22 Mar 2022 12:44:30 -0700 +Subject: [PATCH] Throw exception on world create while being ticked + +There are no plans to support creating worlds while worlds are +being ticked themselvess. + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 4d920031300a9801debc2eb39a4d3cb9d8fbb330..dd9ab51e904be2f2f2a2981d4f0f6638a6895e8d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -297,6 +297,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { + AtomicReference atomicreference = new AtomicReference(); +@@ -1527,6 +1528,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper +@@ -1574,6 +1576,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 8 Jun 2022 18:47:18 +0200 +Subject: [PATCH] Add Alternate Current redstone implementation + +Author: Space Walker + +Original license: MIT +Original project: https://github.com/SpaceWalkerRS/alternate-current + +This patch adds Alternate Current's redstone implementation as an alternative to vanilla and Eigencraft's. +Performance of (de)powering redstone dust is many times faster than vanilla, and even exceeds Eigencraft. +Similar to Eigencraft, Alternate Current heavily changes the update order of redstone dust. This means any contraption that +is location dependent in vanilla will either work everywhere or nowhere when using Alternate Current/Eigencraft. Beyond that +parity issues should be rare for both implementations, though Alternate Current has not been tested as thoroughly, so I +cannot comment on how the two compare in that aspect. + +Alternate Current needs the following modifications: +* Level/ServerLevel: Each level has its own 'wire handler' that handles redstone dust power changes. +* RedStoneWireBlock: Replace calls to vanilla's or Eigencraft's methods for handling power changes with calls to +Alternate Current's wire handler. + +diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f55c5c67b8461e9ef5614ea1a37f6e2866f39be3 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/LevelHelper.java +@@ -0,0 +1,65 @@ ++package alternate.current.wire; ++ ++import org.bukkit.event.block.BlockRedstoneEvent; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.chunk.ChunkAccess; ++import net.minecraft.world.level.chunk.ChunkStatus; ++import net.minecraft.world.level.chunk.LevelChunkSection; ++ ++public class LevelHelper { ++ ++ static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { ++ BlockRedstoneEvent event = new BlockRedstoneEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), prevPower, newPower); ++ level.getCraftServer().getPluginManager().callEvent(event); ++ ++ return event.getNewCurrent(); ++ } ++ ++ /** ++ * An optimized version of {@link net.minecraft.world.level.Level#setBlock ++ * Level.setBlock}. Since this method is only used to update redstone wire block ++ * states, lighting checks, height map updates, and block entity updates are ++ * omitted. ++ */ ++ static boolean setWireState(ServerLevel level, BlockPos pos, BlockState state, boolean updateNeighborShapes) { ++ int y = pos.getY(); ++ ++ if (y < level.getMinBuildHeight() || y >= level.getMaxBuildHeight()) { ++ return false; ++ } ++ ++ int x = pos.getX(); ++ int z = pos.getZ(); ++ int index = level.getSectionIndex(y); ++ ++ ChunkAccess chunk = level.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true); ++ LevelChunkSection section = chunk.getSections()[index]; ++ ++ if (section == null) { ++ return false; // we should never get here ++ } ++ ++ BlockState prevState = section.setBlockState(x & 15, y & 15, z & 15, state); ++ ++ if (state == prevState) { ++ return false; ++ } ++ ++ // notify clients of the BlockState change ++ level.getChunkSource().blockChanged(pos); ++ // mark the chunk for saving ++ chunk.setUnsaved(true); ++ ++ if (updateNeighborShapes) { ++ prevState.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ state.updateNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); ++ } ++ ++ return true; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/Node.java b/src/main/java/alternate/current/wire/Node.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8af6c69098e64945361d116b5fd6ac21e97fcd8d +--- /dev/null ++++ b/src/main/java/alternate/current/wire/Node.java +@@ -0,0 +1,113 @@ ++package alternate.current.wire; ++ ++import java.util.Arrays; ++ ++import alternate.current.wire.WireHandler.Directions; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++ ++/** ++ * A Node represents a block in the world. It also holds a few other pieces of ++ * information that speed up the calculations in the WireHandler class. ++ * ++ * @author Space Walker ++ */ ++public class Node { ++ ++ // flags that encode the Node type ++ private static final int CONDUCTOR = 0b01; ++ private static final int SOURCE = 0b10; ++ ++ final ServerLevel level; ++ final Node[] neighbors; ++ ++ BlockPos pos; ++ BlockState state; ++ boolean invalid; ++ ++ private int flags; ++ ++ /** The previous node in the priority queue. */ ++ Node prev_node; ++ /** The next node in the priority queue. */ ++ Node next_node; ++ /** The priority with which this node was queued. */ ++ int priority; ++ /** The wire that queued this node for an update. */ ++ WireNode neighborWire; ++ ++ Node(ServerLevel level) { ++ this.level = level; ++ this.neighbors = new Node[Directions.ALL.length]; ++ } ++ ++ @Override ++ public boolean equals(Object obj) { ++ if (this == obj) { ++ return true; ++ } ++ if (!(obj instanceof Node)) { ++ return false; ++ } ++ ++ Node node = (Node)obj; ++ ++ return level == node.level && pos.equals(node.pos); ++ } ++ ++ @Override ++ public int hashCode() { ++ return pos.hashCode(); ++ } ++ ++ Node set(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ if (state.is(Blocks.REDSTONE_WIRE)) { ++ throw new IllegalStateException("Cannot update a regular Node to a WireNode!"); ++ } ++ ++ if (clearNeighbors) { ++ Arrays.fill(neighbors, null); ++ } ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ this.invalid = false; ++ ++ this.flags = 0; ++ ++ if (this.state.isRedstoneConductor(this.level, this.pos)) { ++ this.flags |= CONDUCTOR; ++ } ++ if (this.state.isSignalSource()) { ++ this.flags |= SOURCE; ++ } ++ ++ return this; ++ } ++ ++ /** ++ * Determine the priority with which this node should be queued. ++ */ ++ int priority() { ++ return neighborWire.priority; ++ } ++ ++ public boolean isWire() { ++ return false; ++ } ++ ++ public boolean isConductor() { ++ return (flags & CONDUCTOR) != 0; ++ } ++ ++ public boolean isSignalSource() { ++ return (flags & SOURCE) != 0; ++ } ++ ++ public WireNode asWire() { ++ throw new UnsupportedOperationException("Not a WireNode!"); ++ } ++} +diff --git a/src/main/java/alternate/current/wire/PriorityQueue.java b/src/main/java/alternate/current/wire/PriorityQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d71b4d0e4c44a2620b41b89475412db53bea20ed +--- /dev/null ++++ b/src/main/java/alternate/current/wire/PriorityQueue.java +@@ -0,0 +1,211 @@ ++package alternate.current.wire; ++ ++import java.util.AbstractQueue; ++import java.util.Arrays; ++import java.util.Iterator; ++ ++import net.minecraft.world.level.redstone.Redstone; ++ ++public class PriorityQueue extends AbstractQueue { ++ ++ private static final int OFFSET = -Redstone.SIGNAL_MIN; ++ ++ /** The last node for each priority value. */ ++ private final Node[] tails; ++ ++ private Node head; ++ private Node tail; ++ ++ private int size; ++ ++ PriorityQueue() { ++ this.tails = new Node[(Redstone.SIGNAL_MAX + OFFSET) + 1]; ++ } ++ ++ @Override ++ public boolean offer(Node node) { ++ if (node == null) { ++ throw new NullPointerException(); ++ } ++ ++ int priority = node.priority(); ++ ++ if (contains(node)) { ++ if (node.priority == priority) { ++ // already queued with this priority; exit ++ return false; ++ } else { ++ // already queued with different priority; move it ++ move(node, priority); ++ } ++ } else { ++ insert(node, priority); ++ } ++ ++ return true; ++ } ++ ++ @Override ++ public Node poll() { ++ if (head == null) { ++ return null; ++ } ++ ++ Node node = head; ++ Node next = node.next_node; ++ ++ if (next == null) { ++ clear(); // reset the tails array ++ } else { ++ if (node.priority != next.priority) { ++ // If the head is also a tail, its entry in the array ++ // can be cleared; there is no previous node with the ++ // same priority to take its place. ++ tails[node.priority + OFFSET] = null; ++ } ++ ++ node.next_node = null; ++ next.prev_node = null; ++ head = next; ++ ++ size--; ++ } ++ ++ return node; ++ } ++ ++ @Override ++ public Node peek() { ++ return head; ++ } ++ ++ @Override ++ public void clear() { ++ for (Node node = head; node != null; ) { ++ Node n = node; ++ node = node.next_node; ++ ++ n.prev_node = null; ++ n.next_node = null; ++ } ++ ++ Arrays.fill(tails, null); ++ ++ head = null; ++ tail = null; ++ ++ size = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ public boolean contains(Node node) { ++ return node == head || node.prev_node != null; ++ } ++ ++ private void move(Node node, int priority) { ++ remove(node); ++ insert(node, priority); ++ } ++ ++ private void remove(Node node) { ++ Node prev = node.prev_node; ++ Node next = node.next_node; ++ ++ if (node == tail || node.priority != next.priority) { ++ // assign a new tail for this node's priority ++ if (node == head || node.priority != prev.priority) { ++ // there is no other node with the same priority; clear ++ tails[node.priority + OFFSET] = null; ++ } else { ++ // the previous node in the queue becomes the tail ++ tails[node.priority + OFFSET] = prev; ++ } ++ } ++ ++ if (node == head) { ++ head = next; ++ } else { ++ prev.next_node = next; ++ } ++ if (node == tail) { ++ tail = prev; ++ } else { ++ next.prev_node = prev; ++ } ++ ++ node.prev_node = null; ++ node.next_node = null; ++ ++ size--; ++ } ++ ++ private void insert(Node node, int priority) { ++ node.priority = priority; ++ ++ // nodes are sorted by priority (highest to lowest) ++ // nodes with the same priority are ordered FIFO ++ if (head == null) { ++ // first element in this queue \o/ ++ head = tail = node; ++ } else if (priority > head.priority) { ++ linkHead(node); ++ } else if (priority <= tail.priority) { ++ linkTail(node); ++ } else { ++ // since the node is neither the head nor the tail ++ // findPrev is guaranteed to find a non-null element ++ linkAfter(findPrev(node), node); ++ } ++ ++ tails[priority + OFFSET] = node; ++ ++ size++; ++ } ++ ++ private void linkHead(Node node) { ++ node.next_node = head; ++ head.prev_node = node; ++ head = node; ++ } ++ ++ private void linkTail(Node node) { ++ tail.next_node = node; ++ node.prev_node = tail; ++ tail = node; ++ } ++ ++ private void linkAfter(Node prev, Node node) { ++ linkBetween(prev, node, prev.next_node); ++ } ++ ++ private void linkBetween(Node prev, Node node, Node next) { ++ prev.next_node = node; ++ node.prev_node = prev; ++ ++ node.next_node = next; ++ next.prev_node = node; ++ } ++ ++ private Node findPrev(Node node) { ++ Node prev = null; ++ ++ for (int i = node.priority + OFFSET; i < tails.length; i++) { ++ prev = tails[i]; ++ ++ if (prev != null) { ++ break; ++ } ++ } ++ ++ return prev; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/SimpleQueue.java b/src/main/java/alternate/current/wire/SimpleQueue.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2b30074252551e1dc55d5be17d26fb4a2d8eb2e4 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/SimpleQueue.java +@@ -0,0 +1,112 @@ ++package alternate.current.wire; ++ ++import java.util.AbstractQueue; ++import java.util.Iterator; ++ ++public class SimpleQueue extends AbstractQueue { ++ ++ private WireNode head; ++ private WireNode tail; ++ ++ private int size; ++ ++ SimpleQueue() { ++ ++ } ++ ++ @Override ++ public boolean offer(WireNode node) { ++ if (node == null) { ++ throw new NullPointerException(); ++ } ++ ++ if (tail == null) { ++ head = tail = node; ++ } else { ++ tail.next_wire = node; ++ tail = node; ++ } ++ ++ size++; ++ ++ return true; ++ } ++ ++ @Override ++ public WireNode poll() { ++ if (head == null) { ++ return null; ++ } ++ ++ WireNode node = head; ++ WireNode next = node.next_wire; ++ ++ if (next == null) { ++ head = tail = null; ++ } else { ++ node.next_wire = null; ++ head = next; ++ } ++ ++ size--; ++ ++ return node; ++ } ++ ++ @Override ++ public WireNode peek() { ++ return head; ++ } ++ ++ @Override ++ public void clear() { ++ for (WireNode node = head; node != null; ) { ++ WireNode n = node; ++ node = node.next_wire; ++ ++ n.next_wire = null; ++ } ++ ++ head = null; ++ tail = null; ++ ++ size = 0; ++ } ++ ++ @Override ++ public Iterator iterator() { ++ return new SimpleIterator(); ++ } ++ ++ @Override ++ public int size() { ++ return size; ++ } ++ ++ private class SimpleIterator implements Iterator { ++ ++ private WireNode curr; ++ private WireNode next; ++ ++ private SimpleIterator() { ++ next = head; ++ } ++ ++ @Override ++ public boolean hasNext() { ++ if (next == null && curr != null) { ++ next = curr.next_wire; ++ } ++ ++ return next != null; ++ } ++ ++ @Override ++ public WireNode next() { ++ curr = next; ++ next = curr.next_wire; ++ ++ return curr; ++ } ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireConnection.java b/src/main/java/alternate/current/wire/WireConnection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4fd8cb29024330397cfe4cbc1f237d285bfb7b3e +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnection.java +@@ -0,0 +1,30 @@ ++package alternate.current.wire; ++ ++/** ++ * This class represents a connection between some WireNode (the 'owner') and a ++ * neighboring WireNode. Two wires are considered to be connected if power can ++ * flow from one wire to the other (and/or vice versa). ++ * ++ * @author Space Walker ++ */ ++public class WireConnection { ++ ++ /** The connected wire. */ ++ final WireNode wire; ++ /** Cardinal direction to the connected wire. */ ++ final int iDir; ++ /** True if the owner of the connection can provide power to the connected wire. */ ++ final boolean offer; ++ /** True if the connected wire can provide power to the owner of the connection. */ ++ final boolean accept; ++ ++ /** The next connection in the sequence. */ ++ WireConnection next; ++ ++ WireConnection(WireNode wire, int iDir, boolean offer, boolean accept) { ++ this.wire = wire; ++ this.iDir = iDir; ++ this.offer = offer; ++ this.accept = accept; ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireConnectionManager.java b/src/main/java/alternate/current/wire/WireConnectionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5a7209f05b549c222f6c9bc2af2a35790964947e +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireConnectionManager.java +@@ -0,0 +1,136 @@ ++package alternate.current.wire; ++ ++import java.util.Arrays; ++import java.util.function.Consumer; ++ ++import alternate.current.wire.WireHandler.Directions; ++import alternate.current.wire.WireHandler.NodeProvider; ++ ++public class WireConnectionManager { ++ ++ /** The owner of these connections. */ ++ final WireNode owner; ++ ++ /** The first connection for each cardinal direction. */ ++ private final WireConnection[] heads; ++ ++ private WireConnection head; ++ private WireConnection tail; ++ ++ /** The total number of connections. */ ++ int total; ++ ++ /** ++ * A 4 bit number that encodes in which direction(s) the owner has connections ++ * to other wires. ++ */ ++ private int flowTotal; ++ /** The direction of flow based connections to other wires. */ ++ int iFlowDir; ++ ++ WireConnectionManager(WireNode owner) { ++ this.owner = owner; ++ ++ this.heads = new WireConnection[Directions.HORIZONTAL.length]; ++ ++ this.total = 0; ++ ++ this.flowTotal = 0; ++ this.iFlowDir = -1; ++ } ++ ++ void set(NodeProvider nodes) { ++ if (total > 0) { ++ clear(); ++ } ++ ++ boolean belowIsConductor = nodes.getNeighbor(owner, Directions.DOWN).isConductor(); ++ boolean aboveIsConductor = nodes.getNeighbor(owner, Directions.UP).isConductor(); ++ ++ for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) { ++ Node neighbor = nodes.getNeighbor(owner, iDir); ++ ++ if (neighbor.isWire()) { ++ add(neighbor.asWire(), iDir, true, true); ++ ++ continue; ++ } ++ ++ boolean sideIsConductor = neighbor.isConductor(); ++ ++ if (!sideIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); ++ ++ if (node.isWire()) { ++ add(node.asWire(), iDir, belowIsConductor, true); ++ } ++ } ++ if (!aboveIsConductor) { ++ Node node = nodes.getNeighbor(neighbor, Directions.UP); ++ ++ if (node.isWire()) { ++ add(node.asWire(), iDir, true, sideIsConductor); ++ } ++ } ++ } ++ ++ if (total > 0) { ++ iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal]; ++ } ++ } ++ ++ private void clear() { ++ Arrays.fill(heads, null); ++ ++ head = null; ++ tail = null; ++ ++ total = 0; ++ ++ flowTotal = 0; ++ iFlowDir = -1; ++ } ++ ++ private void add(WireNode wire, int iDir, boolean offer, boolean accept) { ++ add(new WireConnection(wire, iDir, offer, accept)); ++ } ++ ++ private void add(WireConnection connection) { ++ if (head == null) { ++ head = connection; ++ tail = connection; ++ } else { ++ tail.next = connection; ++ tail = connection; ++ } ++ ++ total++; ++ ++ if (heads[connection.iDir] == null) { ++ heads[connection.iDir] = connection; ++ flowTotal |= (1 << connection.iDir); ++ } ++ } ++ ++ /** ++ * Iterate over all connections. Use this method if the iteration order is not ++ * important. ++ */ ++ void forEach(Consumer consumer) { ++ for (WireConnection c = head; c != null; c = c.next) { ++ consumer.accept(c); ++ } ++ } ++ ++ /** ++ * Iterate over all connections. Use this method if the iteration order is ++ * important. ++ */ ++ void forEach(Consumer consumer, int iFlowDir) { ++ for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) { ++ for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) { ++ consumer.accept(c); ++ } ++ } ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..35d9017c21ce77290d8e86cceb0676666e6e0eff +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireHandler.java +@@ -0,0 +1,1150 @@ ++package alternate.current.wire; ++ ++import java.util.Iterator; ++import java.util.Queue; ++import java.util.function.Consumer; ++ ++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 net.minecraft.core.BlockPos; ++import net.minecraft.core.Direction; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.redstone.Redstone; ++ ++/** ++ * This class handles power changes for redstone wire. The algorithm was ++ * designed with the following goals in mind: ++ *
++ * 1. Minimize the number of times a wire checks its surroundings to determine ++ * its power level. ++ *
++ * 2. Minimize the number of block and shape updates emitted. ++ *
++ * 3. Emit block and shape updates in a deterministic, non-locational order, ++ * fixing bug MC-11193. ++ * ++ *

++ * In Vanilla redstone wire is laggy because it fails on points 1 and 2. ++ * ++ *

++ * Redstone wire updates recursively and each wire calculates its power level in ++ * isolation rather than in the context of the network it is a part of. This ++ * means a wire in a grid can change its power level over half a dozen times ++ * before settling on its final value. This problem used to be worse in 1.13 and ++ * below, where a wire would only decrease its power level by 1 at a time. ++ * ++ *

++ * In addition to this, a wire emits 42 block updates and up to 22 shape updates ++ * each time it changes its power level. ++ * ++ *

++ * Of those 42 block updates, 6 are to itself, which are thus not only ++ * redundant, but a big source of lag, since those cause the wire to ++ * unnecessarily re-calculate its power level. A block only has 24 neighbors ++ * within a Manhattan distance of 2, meaning 12 of the remaining 36 block ++ * updates are duplicates and thus also redundant. ++ * ++ *

++ * Of the 22 shape updates, only 6 are strictly necessary. The other 16 are sent ++ * to blocks diagonally above and below. These are necessary if a wire changes ++ * its connections, but not when it changes its power level. ++ * ++ *

++ * Redstone wire in Vanilla also fails on point 3, though this is more of a ++ * quality-of-life issue than a lag issue. The recursive nature in which it ++ * updates, combined with the location-dependent order in which each wire ++ * updates its neighbors, makes the order in which neighbors of a wire network ++ * are updated incredibly inconsistent and seemingly random. ++ * ++ *

++ * Alternate Current fixes each of these problems as follows. ++ * ++ *

++ * 1. To make sure a wire calculates its power level as little as possible, we ++ * remove the recursive nature in which redstone wire updates in Vanilla. ++ * Instead, we build a network of connected wires, find those wires that receive ++ * redstone power from "outside" the network, and spread the power from there. ++ * This has a few advantages: ++ *
++ * - Each wire checks for power from non-wire components at most once, and from ++ * nearby wires just twice. ++ *
++ * - Each wire only sets its power level in the world once. This is important, ++ * because calls to Level.setBlock are even more expensive than calls to ++ * Level.getBlockState. ++ * ++ *

++ * 2. There are 2 obvious ways in which we can reduce the number of block and ++ * shape updates. ++ *
++ * - Get rid of the 18 redundant block updates and 16 redundant shape updates, ++ * so each wire only emits 24 block updates and 6 shape updates whenever it ++ * changes its power level. ++ *
++ * - Only emit block updates and shape updates once a wire reaches its final ++ * power level, rather than at each intermediary stage. ++ *
++ * For an individual wire, these two optimizations are the best you can do, but ++ * for an entire grid, you can do better! ++ * ++ *

++ * Since we calculate the power of the entire network, sending block and shape ++ * updates to the wires in it is redundant. Removing those updates can reduce ++ * the number of block and shape updates by up to 20%. ++ * ++ *

++ * 3. To make the order of block updates to neighbors of a network ++ * deterministic, the first thing we must do is to replace the location- ++ * dependent order in which a wire updates its neighbors. Instead, we base it on ++ * the direction of power flow. This part of the algorithm was heavily inspired ++ * by theosib's 'RedstoneWireTurbo', which you can read more about in theosib's ++ * comment on Mojira here ++ * or by checking out its implementation in carpet mod here. ++ * ++ *

++ * The idea is to determine the direction of power flow through a wire based on ++ * the power it receives from neighboring wires. For example, if the only power ++ * a wire receives is from a neighboring wire to its west, it can be said that ++ * the direction of power flow through the wire is east. ++ * ++ *

++ * We make the order of block updates to neighbors of a wire depend on what is ++ * determined to be the direction of power flow. This not only removes ++ * locationality entirely, it even removes directionality in a large number of ++ * cases. Unlike in 'RedstoneWireTurbo', however, I have decided to keep a ++ * directional element in ambiguous cases, rather than to introduce randomness, ++ * though this is trivial to change. ++ * ++ *

++ * While this change fixes the block update order of individual wires, we must ++ * still address the overall block update order of a network. This turns out to ++ * be a simple fix, because of a change we made earlier: we search through the ++ * network for wires that receive power from outside it, and spread the power ++ * from there. If we make each wire transmit its power to neighboring wires in ++ * an order dependent on the direction of power flow, we end up with a ++ * non-locational and largely non-directional wire update order. ++ * ++ * @author Space Walker ++ */ ++public class WireHandler { ++ ++ public static class Directions { ++ ++ public static final Direction[] ALL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.DOWN, Direction.UP }; ++ public static final Direction[] HORIZONTAL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH }; ++ ++ // Indices for the arrays above. ++ // The cardinal directions are ordered clockwise. This allows ++ // for conversion between relative and absolute directions ++ // ('left' 'right' vs 'east' 'west') with simple arithmetic: ++ // If some Direction index 'iDir' is considered 'forward', then ++ // '(iDir + 1) & 0b11' is 'right', '(iDir + 2) & 0b11' is 'backward', etc. ++ public static final int WEST = 0b000; // 0 ++ public static final int NORTH = 0b001; // 1 ++ public static final int EAST = 0b010; // 2 ++ public static final int SOUTH = 0b011; // 3 ++ public static final int DOWN = 0b100; // 4 ++ public static final int UP = 0b101; // 5 ++ ++ public static int iOpposite(int iDir) { ++ return iDir ^ (0b10 >>> (iDir >>> 2)); ++ } ++ ++ // Each array is placed at the index that encodes the direction that is missing ++ // from the array. ++ private static final int[][] I_EXCEPT = { ++ { NORTH, EAST, SOUTH, DOWN, UP }, ++ { WEST, EAST, SOUTH, DOWN, UP }, ++ { WEST, NORTH, SOUTH, DOWN, UP }, ++ { WEST, NORTH, EAST, DOWN, UP }, ++ { WEST, NORTH, EAST, SOUTH, UP }, ++ { WEST, NORTH, EAST, SOUTH, DOWN } ++ }; ++ private static final int[][] I_EXCEPT_CARDINAL = { ++ { NORTH, EAST, SOUTH }, ++ { WEST, EAST, SOUTH }, ++ { WEST, NORTH, SOUTH }, ++ { WEST, NORTH, EAST, }, ++ { WEST, NORTH, EAST, SOUTH }, ++ { WEST, NORTH, EAST, SOUTH } ++ }; ++ } ++ ++ /** ++ * This conversion table takes in information about incoming flow, and outputs ++ * the determined outgoing flow. ++ * ++ *

++ * The input is a 4 bit number that encodes the incoming flow. Each bit ++ * represents a cardinal direction, and when it is 'on', there is flow in that ++ * direction. ++ * ++ *

++ * The output is a single Direction index, or -1 for ambiguous cases. ++ * ++ *

++ * The outgoing flow is determined as follows: ++ * ++ *

++ * If there is just 1 direction of incoming flow, that direction will be the ++ * direction of outgoing flow. ++ * ++ *

++ * If there are 2 directions of incoming flow, and these directions are not each ++ * other's opposites, the direction that is 'more clockwise' will be the ++ * direction of outgoing flow. More precisely, the direction that is 1 clockwise ++ * turn from the other is picked. ++ * ++ *

++ * If there are 3 directions of incoming flow, the two opposing directions ++ * cancel each other out, and the remaining direction will be the direction of ++ * outgoing flow. ++ * ++ *

++ * In all other cases, the flow is completely ambiguous. ++ */ ++ static final int[] FLOW_IN_TO_FLOW_OUT = { ++ -1, // 0b0000: - -> x ++ Directions.WEST, // 0b0001: west -> west ++ Directions.NORTH, // 0b0010: north -> north ++ Directions.NORTH, // 0b0011: west/north -> north ++ Directions.EAST, // 0b0100: east -> east ++ -1, // 0b0101: west/east -> x ++ Directions.EAST, // 0b0110: north/east -> east ++ Directions.NORTH, // 0b0111: west/north/east -> north ++ Directions.SOUTH, // 0b1000: south -> south ++ Directions.WEST, // 0b1001: west/south -> west ++ -1, // 0b1010: north/south -> x ++ Directions.WEST, // 0b1011: west/north/south -> west ++ Directions.SOUTH, // 0b1100: east/south -> south ++ Directions.SOUTH, // 0b1101: west/east/south -> south ++ Directions.EAST, // 0b1110: north/east/south -> east ++ -1, // 0b1111: west/north/east/south -> x ++ }; ++ /** ++ * Update orders of all directions. Given that the index encodes the direction ++ * that is to be considered 'forward', the resulting update order is ++ * { front, back, right, left, down, up }. ++ */ ++ static final int[][] FULL_UPDATE_ORDERS = { ++ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, ++ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, ++ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, ++ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } ++ }; ++ /** ++ * The default update order of all directions. It is equivalent to the order of ++ * shape updates in vanilla Minecraft. ++ */ ++ static final int[] DEFAULT_FULL_UPDATE_ORDER = FULL_UPDATE_ORDERS[0]; ++ /** ++ * Update orders of cardinal directions. Given that the index encodes the ++ * direction that is to be considered 'forward', the resulting update order is ++ * { front, back, right, left }. ++ */ ++ static final int[][] CARDINAL_UPDATE_ORDERS = { ++ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, ++ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, ++ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, ++ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } ++ }; ++ /** ++ * The default update order of all cardinal directions. ++ */ ++ static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0]; ++ ++ private static final int POWER_MIN = Redstone.SIGNAL_MIN; ++ private static final int POWER_MAX = Redstone.SIGNAL_MAX; ++ private static final int POWER_STEP = 1; ++ ++ // If Vanilla will ever multi-thread the ticking of levels, there should ++ // be only one WireHandler per level, in case redstone updates in multiple ++ // levels at the same time. There are already mods that add multi-threading ++ // as well. ++ private final ServerLevel level; ++ ++ /** Map of wires and neighboring blocks. */ ++ private final Long2ObjectMap nodes; ++ /** Queue for the breadth-first search through the network. */ ++ private final Queue search; ++ /** Queue of updates to wires and neighboring blocks. */ ++ private final Queue updates; ++ ++ // Rather than creating new nodes every time a network is updated we keep ++ // a cache of nodes that can be re-used. ++ private Node[] nodeCache; ++ private int nodeCount; ++ ++ /** Is this WireHandler currently working through the update queue? */ ++ private boolean updating; ++ ++ public WireHandler(ServerLevel level) { ++ this.level = level; ++ ++ this.nodes = new Long2ObjectOpenHashMap<>(); ++ this.search = new SimpleQueue(); ++ this.updates = new PriorityQueue(); ++ ++ this.nodeCache = new Node[16]; ++ this.fillNodeCache(0, 16); ++ } ++ ++ /** ++ * Retrieve the {@link alternate.current.wire.Node Node} that represents the ++ * block at the given position in the level. ++ */ ++ private Node getOrAddNode(BlockPos pos) { ++ return nodes.compute(pos.asLong(), (key, node) -> { ++ if (node == null) { ++ // If there is not yet a node at this position, retrieve and ++ // update one from the cache. ++ return getNextNode(pos); ++ } ++ if (node.invalid) { ++ return revalidateNode(node); ++ } ++ ++ return node; ++ }); ++ } ++ ++ /** ++ * Remove and return the {@link alternate.current.wire.Node Node} at the given ++ * position. ++ */ ++ private Node removeNode(BlockPos pos) { ++ return nodes.remove(pos.asLong()); ++ } ++ ++ /** ++ * Return a {@link alternate.current.wire.Node Node} that represents the block ++ * at the given position. ++ */ ++ private Node getNextNode(BlockPos pos) { ++ return getNextNode(pos, level.getBlockState(pos)); ++ } ++ ++ /** ++ * Return a node that represents the given position and block state. If it is a ++ * wire, then create a new {@link alternate.current.wire.WireNode WireNode}. ++ * Otherwise, grab the next {@link alternate.current.wire.Node Node} from the ++ * cache and update it. ++ */ ++ private Node getNextNode(BlockPos pos, BlockState state) { ++ return state.is(Blocks.REDSTONE_WIRE) ? new WireNode(level, pos, state) : getNextNode().set(pos, state, true); ++ } ++ ++ /** ++ * Grab the first unused node from the cache. If all of the cache is already in ++ * use, increase it in size first. ++ */ ++ private Node getNextNode() { ++ if (nodeCount == nodeCache.length) { ++ increaseNodeCache(); ++ } ++ ++ return nodeCache[nodeCount++]; ++ } ++ ++ private void increaseNodeCache() { ++ Node[] oldCache = nodeCache; ++ nodeCache = new Node[oldCache.length << 1]; ++ ++ for (int index = 0; index < oldCache.length; index++) { ++ nodeCache[index] = oldCache[index]; ++ } ++ ++ fillNodeCache(oldCache.length, nodeCache.length); ++ } ++ ++ private void fillNodeCache(int start, int end) { ++ for (int index = start; index < end; index++) { ++ nodeCache[index] = new Node(level); ++ } ++ } ++ ++ /** ++ * Try to revalidate the given node by looking at the block state that is ++ * occupying its position. If the given node is a wire but the block state is ++ * not, or vice versa, a new node must be created/grabbed from the cache. ++ * Otherwise, the node can be quickly revalidated with the new block state. ++ */ ++ private Node revalidateNode(Node node) { ++ BlockPos pos = node.pos; ++ BlockState state = level.getBlockState(pos); ++ ++ boolean wasWire = node.isWire(); ++ boolean isWire = state.is(Blocks.REDSTONE_WIRE); ++ ++ if (wasWire != isWire) { ++ return getNextNode(pos, state); ++ } ++ ++ node.invalid = false; ++ ++ if (isWire) { ++ // No need to update the block state of this wire - it will grab ++ // the current block state just before setting power anyway. ++ WireNode wire = node.asWire(); ++ ++ wire.root = false; ++ wire.discovered = false; ++ wire.searched = false; ++ } else { ++ node.set(pos, state, false); ++ } ++ ++ return node; ++ } ++ ++ /** ++ * Retrieve the neighbor of a node in the given direction and create a link ++ * between the two nodes if they are not yet linked. This link makes accessing ++ * neighbors of a node signficantly faster. ++ */ ++ private Node getNeighbor(Node node, int iDir) { ++ Node neighbor = node.neighbors[iDir]; ++ ++ if (neighbor == null || neighbor.invalid) { ++ Direction dir = Directions.ALL[iDir]; ++ BlockPos pos = node.pos.relative(dir); ++ ++ Node oldNeighbor = neighbor; ++ neighbor = getOrAddNode(pos); ++ ++ if (neighbor != oldNeighbor) { ++ int iOpp = Directions.iOpposite(iDir); ++ ++ node.neighbors[iDir] = neighbor; ++ neighbor.neighbors[iOpp] = node; ++ } ++ } ++ ++ return neighbor; ++ } ++ ++ /** ++ * Iterate over all neighboring nodes of the given wire. The iteration order is ++ * designed to be an extension of the default block update order, and is ++ * determined as follows: ++ *
++ * 1. The direction of power flow through the wire is to be considered ++ * 'forward'. The iteration order depends on the neighbors' relative positions ++ * to the wire. ++ *
++ * 2. Each neighbor is identified by the step(s) you must take, starting at the ++ * wire, to reach it. Each step is 1 block, thus the position of a neighbor is ++ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), ++ * etc. ++ *
++ * 3. Neighbors are iterated over in pairs that lie on opposite sides of the ++ * wire. ++ *
++ * 4. Neighbors are iterated over in order of their distance from the wire. This ++ * means they are iterated over in 3 groups: direct neighbors first, then ++ * diagonal neighbors, and last are the far neighbors that are 2 blocks directly ++ * out. ++ *
++ * 5. The order within each group is determined using the following basic order: ++ * { front, back, right, left, down, up }. This order was chosen because it ++ * converts to the following order of absolute directions when west is said to ++ * be 'forward': { west, east, north, south, down, up } - this is the order of ++ * shape updates. ++ */ ++ private void forEachNeighbor(WireNode wire, Consumer consumer) { ++ int forward = wire.iFlowDir; ++ int rightward = (forward + 1) & 0b11; ++ int backward = (forward + 2) & 0b11; ++ int leftward = (forward + 3) & 0b11; ++ int downward = Directions.DOWN; ++ int upward = Directions.UP; ++ ++ Node front = getNeighbor(wire, forward); ++ Node right = getNeighbor(wire, rightward); ++ Node back = getNeighbor(wire, backward); ++ Node left = getNeighbor(wire, leftward); ++ Node below = getNeighbor(wire, downward); ++ Node above = getNeighbor(wire, upward); ++ ++ // direct neighbors (6) ++ consumer.accept(front); ++ consumer.accept(back); ++ consumer.accept(right); ++ consumer.accept(left); ++ consumer.accept(below); ++ consumer.accept(above); ++ ++ // diagonal neighbors (12) ++ consumer.accept(getNeighbor(front, rightward)); ++ consumer.accept(getNeighbor(back, leftward)); ++ consumer.accept(getNeighbor(front, leftward)); ++ consumer.accept(getNeighbor(back, rightward)); ++ consumer.accept(getNeighbor(front, downward)); ++ consumer.accept(getNeighbor(back, upward)); ++ consumer.accept(getNeighbor(front, upward)); ++ consumer.accept(getNeighbor(back, downward)); ++ consumer.accept(getNeighbor(right, downward)); ++ consumer.accept(getNeighbor(left, upward)); ++ consumer.accept(getNeighbor(right, upward)); ++ consumer.accept(getNeighbor(left, downward)); ++ ++ // far neighbors (6) ++ consumer.accept(getNeighbor(front, forward)); ++ consumer.accept(getNeighbor(back, backward)); ++ consumer.accept(getNeighbor(right, rightward)); ++ consumer.accept(getNeighbor(left, leftward)); ++ consumer.accept(getNeighbor(below, downward)); ++ consumer.accept(getNeighbor(above, upward)); ++ } ++ ++ /** ++ * This method should be called whenever a wire receives a block update. ++ */ ++ public void onWireUpdated(BlockPos pos) { ++ invalidate(); ++ findRoots(pos); ++ tryUpdate(); ++ } ++ ++ /** ++ * This method should be called whenever a wire is placed. ++ */ ++ public void onWireAdded(BlockPos pos) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ wire.added = true; ++ ++ invalidate(); ++ revalidateNode(wire); ++ findRoot(wire); ++ tryUpdate(); ++ } ++ ++ /** ++ * This method should be called whenever a wire is removed. ++ */ ++ public void onWireRemoved(BlockPos pos, BlockState state) { ++ Node node = removeNode(pos); ++ WireNode wire; ++ ++ if (node == null || !node.isWire()) { ++ wire = new WireNode(level, pos, state); ++ } else { ++ wire = node.asWire(); ++ } ++ ++ wire.invalid = true; ++ wire.removed = true; ++ ++ // If these fields are set to 'true', the removal of this wire was part of ++ // already ongoing power changes, so we can exit early here. ++ if (updating && wire.shouldBreak) { ++ return; ++ } ++ ++ invalidate(); ++ revalidateNode(wire); ++ findRoot(wire); ++ tryUpdate(); ++ } ++ ++ /** ++ * The nodes map is a snapshot of the state of the world. It becomes invalid ++ * when power changes are carried out, since the block and shape updates can ++ * lead to block changes. If these block changes cause the network to be updated ++ * again every node must be invalidated, and revalidated before it is used ++ * again. This ensures the power calculations of the network are accurate. ++ */ ++ private void invalidate() { ++ if (updating && !nodes.isEmpty()) { ++ Iterator> it = Long2ObjectMaps.fastIterator(nodes); ++ ++ while (it.hasNext()) { ++ Entry entry = it.next(); ++ Node node = entry.getValue(); ++ ++ node.invalid = true; ++ } ++ } ++ } ++ ++ /** ++ * Look for wires at and around the given position that are in an invalid state ++ * and require power changes. These wires are called 'roots' because it is only ++ * when these wires change power level that neighboring wires must adjust as ++ * well. ++ * ++ *

++ * While it is strictly only necessary to check the wire at the given position, ++ * if that wire is part of a network, it is beneficial to check its surroundings ++ * for other wires that require power changes. This is because a network can ++ * receive power at multiple points. Consider the following setup: ++ * ++ *

++ * (top-down view, W = wire, L = lever, _ = air/other) ++ *
{@code _ _ W _ _ } ++ *
{@code _ W W W _ } ++ *
{@code W W L W W } ++ *
{@code _ W W W _ } ++ *
{@code _ _ W _ _ } ++ * ++ *

++ * The lever powers four wires in the network at once. If this is identified ++ * correctly, the entire network can (un)power at once. While it is not ++ * practical to cover every possible situation where a network is (un)powered ++ * from multiple points at once, checking for common cases like the one ++ * described above is relatively straight-forward. ++ */ ++ private void findRoots(BlockPos pos) { ++ Node node = getOrAddNode(pos); ++ ++ if (!node.isWire()) { ++ return; // we should never get here ++ } ++ ++ WireNode wire = node.asWire(); ++ findRoot(wire); ++ ++ // If the wire at the given position is not in an invalid state or is not ++ // part of a larger network, we can exit early. ++ if (!wire.searched || wire.connections.total == 0) { ++ return; ++ } ++ ++ for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (neighbor.isConductor() || neighbor.isSignalSource()) { ++ findRootsAround(neighbor, Directions.iOpposite(iDir)); ++ } ++ } ++ } ++ ++ /** ++ * Look for wires around the given node that require power changes. ++ */ ++ private void findRootsAround(Node node, int except) { ++ for (int iDir : Directions.I_EXCEPT_CARDINAL[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isWire()) { ++ findRoot(neighbor.asWire()); ++ } ++ } ++ } ++ ++ /** ++ * Check if the given wire requires power changes. If it does, queue it for the ++ * breadth-first search as a root. ++ */ ++ private void findRoot(WireNode wire) { ++ // Each wire only needs to be checked once. ++ if (wire.discovered) { ++ return; ++ } ++ ++ discover(wire); ++ findExternalPower(wire); ++ findPower(wire, false); ++ ++ if (needsUpdate(wire)) { ++ searchRoot(wire); ++ } ++ } ++ ++ /** ++ * Prepare the given wire for the breadth-first search. This means: ++ *
++ * - Check if the wire should break. Rather than breaking the wire right away, ++ * its effects are integrated into the power calculations. ++ *
++ * - Reset the virtual and external power. ++ *
++ * - Find connections to neighboring wires. ++ */ ++ private void discover(WireNode wire) { ++ if (wire.discovered) { ++ return; ++ } ++ ++ wire.discovered = true; ++ wire.searched = false; ++ ++ if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(level, wire.pos)) { ++ wire.shouldBreak = true; ++ } ++ ++ wire.virtualPower = wire.currentPower; ++ wire.externalPower = POWER_MIN - 1; ++ ++ wire.connections.set(this::getNeighbor); ++ } ++ ++ /** ++ * Determine the power level the given wire receives from the blocks around it. ++ * Power from non-wire components only needs to be computed if power from ++ * neighboring wires has decreased, so as to determine how low the power of the ++ * wire can fall. ++ */ ++ private void findPower(WireNode wire, boolean ignoreSearched) { ++ // As wire power is (re-)computed, flow information must be reset. ++ wire.virtualPower = wire.externalPower; ++ wire.flowIn = 0; ++ ++ // If the wire is removed or going to break, its power level should always be ++ // the minimum value. This is because it (effectively) no longer exists, so ++ // cannot provide any power to neighboring wires. ++ if (wire.removed || wire.shouldBreak) { ++ return; ++ } ++ ++ // Power received from neighboring wires will never exceed POWER_MAX - ++ // POWER_STEP, so if the external power is already larger than or equal to ++ // that, there is no need to check for power from neighboring wires. ++ if (wire.externalPower < (POWER_MAX - POWER_STEP)) { ++ findWirePower(wire, ignoreSearched); ++ } ++ } ++ ++ /** ++ * Determine the power the given wire receives from connected neighboring wires ++ * and update the virtual power accordingly. ++ */ ++ private void findWirePower(WireNode wire, boolean ignoreSearched) { ++ wire.connections.forEach(connection -> { ++ if (!connection.accept) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (!ignoreSearched || !neighbor.searched) { ++ int power = Math.max(POWER_MIN, neighbor.virtualPower - POWER_STEP); ++ int iOpp = Directions.iOpposite(connection.iDir); ++ ++ wire.offerPower(power, iOpp); ++ } ++ }); ++ } ++ ++ /** ++ * Determine the redstone signal the given wire receives from non-wire ++ * components and update the virtual power accordingly. ++ */ ++ private void findExternalPower(WireNode wire) { ++ // If the wire is removed or going to break, its power level should always be ++ // the minimum value. Thus external power need not be computed. ++ // In other cases external power need only be computed once. ++ if (wire.removed || wire.shouldBreak || wire.externalPower >= POWER_MIN) { ++ return; ++ } ++ ++ wire.externalPower = getExternalPower(wire); ++ ++ if (wire.externalPower > wire.virtualPower) { ++ wire.virtualPower = wire.externalPower; ++ } ++ } ++ ++ /** ++ * Determine the redstone signal the given wire receives from non-wire ++ * components. ++ */ ++ private int getExternalPower(WireNode wire) { ++ int power = POWER_MIN; ++ ++ for (int iDir = 0; iDir < Directions.ALL.length; iDir++) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ // Power from wires is handled separately. ++ if (neighbor.isWire()) { ++ continue; ++ } ++ ++ // Since 1.16 there is a block that is both a conductor and a signal ++ // source: the target block! ++ if (neighbor.isConductor()) { ++ power = Math.max(power, getDirectSignalTo(wire, neighbor, Directions.iOpposite(iDir))); ++ } ++ if (neighbor.isSignalSource()) { ++ power = Math.max(power, neighbor.state.getSignal(level, neighbor.pos, Directions.ALL[iDir])); ++ } ++ ++ if (power >= POWER_MAX) { ++ return POWER_MAX; ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Determine the direct signal the given wire receives from neighboring blocks ++ * through the given conductor node. ++ */ ++ private int getDirectSignalTo(WireNode wire, Node node, int except) { ++ int power = POWER_MIN; ++ ++ for (int iDir : Directions.I_EXCEPT[except]) { ++ Node neighbor = getNeighbor(node, iDir); ++ ++ if (neighbor.isSignalSource()) { ++ power = Math.max(power, neighbor.state.getDirectSignal(level, neighbor.pos, Directions.ALL[iDir])); ++ ++ if (power >= POWER_MAX) { ++ return POWER_MAX; ++ } ++ } ++ } ++ ++ return power; ++ } ++ ++ /** ++ * Check if the given wire needs to update its state in the world. ++ */ ++ private boolean needsUpdate(WireNode wire) { ++ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower; ++ } ++ ++ /** ++ * Queue the given wire for the breadth-first search as a root. ++ */ ++ private void searchRoot(WireNode wire) { ++ int iBackupFlowDir; ++ ++ if (wire.connections.iFlowDir < 0) { ++ iBackupFlowDir = 0; ++ } else { ++ iBackupFlowDir = wire.connections.iFlowDir; ++ } ++ ++ search(wire, true, iBackupFlowDir); ++ } ++ ++ /** ++ * Queue the given wire for the breadth-first search and set a backup flow ++ * direction. ++ */ ++ private void search(WireNode wire, boolean root, int iBackupFlowDir) { ++ search.offer(wire); ++ ++ wire.root = root; ++ wire.searched = true; ++ // Normally the flow is not set until the power level is updated. However, ++ // in networks with multiple power sources the update order between them ++ // depends on which was discovered first. To make this less prone to ++ // directionality, each wire node is given a 'backup' flow. For roots, this ++ // is the determined flow of their connections. For non-roots this is the ++ // direction from which they were discovered. ++ wire.iFlowDir = iBackupFlowDir; ++ } ++ ++ private void tryUpdate() { ++ if (!search.isEmpty()) { ++ update(); ++ } ++ if (!updating) { ++ nodes.clear(); ++ nodeCount = 0; ++ } ++ } ++ ++ /** ++ * Update the network and neighboring blocks. This is done in 3 steps. ++ * ++ *

++ * 1. Search through the network ++ *
++ * Conduct a breadth-first search around the roots to find wires that are in an ++ * invalid state and need power changes. ++ * ++ *

++ * 2. Depower the network ++ *
++ * Depower all wires in the network. This allows power to be spread most ++ * efficiently. ++ * ++ *

++ * 3. Power the network ++ *
++ * Work through the update queue, setting the new power level of each wire and ++ * updating neighboring blocks. After a wire has updated its power level, it ++ * will emit shape updates and queue updates for neighboring wires and blocks. ++ */ ++ private void update() { ++ // Search through the network for wires that need power changes. This includes ++ // the roots as well as any wires that will be affected by power changes to ++ // those roots. ++ searchNetwork(); ++ ++ // Depower all the wires in the network. ++ depowerNetwork(); ++ ++ // Bring each wire up to its new power level and update neighboring blocks. ++ try { ++ powerNetwork(); ++ } catch (Throwable t) { ++ // If anything goes wrong while carrying out power changes, this field must ++ // be reset to 'false', or the wire handler will be locked out of carrying ++ // out power changes until the world is reloaded. ++ updating = false; ++ ++ throw t; ++ } ++ } ++ ++ /** ++ * Search through the network for wires that are in an invalid state and need ++ * power changes. These wires are added to the end of the queue, so that their ++ * neighbors can be searched next. ++ */ ++ private void searchNetwork() { ++ for (WireNode wire : search) { ++ // The order in which wires are searched will influence the order in ++ // which they update their power levels. ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ if (neighbor.searched) { ++ return; ++ } ++ ++ discover(neighbor); ++ findPower(neighbor, false); ++ ++ // If power from neighboring wires has decreased, check for power ++ // from non-wire components so as to determine how low power can ++ // fall. ++ if (neighbor.virtualPower < neighbor.currentPower) { ++ findExternalPower(neighbor); ++ } ++ ++ if (needsUpdate(neighbor)) { ++ search(neighbor, false, connection.iDir); ++ } ++ }, wire.iFlowDir); ++ } ++ } ++ ++ /** ++ * Depower all wires in the network so that power can be spread from the power ++ * sources. ++ */ ++ private void depowerNetwork() { ++ while (!search.isEmpty()) { ++ WireNode wire = search.poll(); ++ findPower(wire, true); ++ ++ if (wire.root || wire.removed || wire.shouldBreak || wire.virtualPower > POWER_MIN) { ++ queueWire(wire); ++ } else { ++ // Wires that do not receive any power do not queue power changes ++ // until they are offered power from a neighboring wire. To ensure ++ // that they accept any power from neighboring wires and thus queue ++ // their power changes, their virtual power is set to below the ++ // minimum. ++ wire.virtualPower--; ++ } ++ } ++ } ++ ++ /** ++ * Work through the update queue, setting the new power level of each wire, then ++ * queueing updates to connected wires and neighboring blocks. ++ */ ++ private void powerNetwork() { ++ // If an instantaneous update chain causes updates to another network ++ // (or the same network in another place), new power changes will be ++ // integrated into the already ongoing power queue, so we can exit early ++ // here. ++ if (updating) { ++ return; ++ } ++ ++ updating = true; ++ ++ while (!updates.isEmpty()) { ++ Node node = updates.poll(); ++ ++ if (node.isWire()) { ++ WireNode wire = node.asWire(); ++ ++ if (!needsUpdate(wire)) { ++ continue; ++ } ++ ++ findPowerFlow(wire); ++ transmitPower(wire); ++ ++ if (wire.setPower()) { ++ queueNeighbors(wire); ++ ++ // If the wire was newly placed or removed, shape updates have ++ // already been emitted. However, unlike before 1.19, neighbor ++ // updates are now queued, so to preserve behavior parity with ++ // previous versions, we emit extra shape updates here to ++ // notify neighboring observers. ++ updateNeighborShapes(wire); ++ } ++ } else { ++ WireNode neighborWire = node.neighborWire; ++ ++ if (neighborWire != null) { ++ BlockPos neighborPos = neighborWire.pos; ++ Block neighborBlock = neighborWire.state.getBlock(); ++ ++ updateBlock(node, neighborPos, neighborBlock); ++ } ++ } ++ } ++ ++ updating = false; ++ } ++ ++ /** ++ * Use the information of incoming power flow to determine the direction of ++ * power flow through this wire. If that flow is ambiguous, try to use a flow ++ * direction based on connections to neighboring wires. If that is also ++ * ambiguous, use the backup value that was set when the wire was first added to ++ * the network. ++ */ ++ private void findPowerFlow(WireNode wire) { ++ int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn]; ++ ++ if (flow >= 0) { ++ wire.iFlowDir = flow; ++ } else if (wire.connections.iFlowDir >= 0) { ++ wire.iFlowDir = wire.connections.iFlowDir; ++ } ++ } ++ ++ /** ++ * Transmit power from the given wire to neighboring wires and queue updates to ++ * those wires. ++ */ ++ private void transmitPower(WireNode wire) { ++ wire.connections.forEach(connection -> { ++ if (!connection.offer) { ++ return; ++ } ++ ++ WireNode neighbor = connection.wire; ++ ++ int power = Math.max(POWER_MIN, wire.virtualPower - POWER_STEP); ++ int iDir = connection.iDir; ++ ++ if (neighbor.offerPower(power, iDir)) { ++ queueWire(neighbor); ++ } ++ }, wire.iFlowDir); ++ } ++ ++ /** ++ * Emit shape updates around the given wire. ++ */ ++ private void updateNeighborShapes(WireNode wire) { ++ BlockPos wirePos = wire.pos; ++ BlockState wireState = wire.state; ++ ++ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { ++ Node neighbor = getNeighbor(wire, iDir); ++ ++ if (!neighbor.isWire()) { ++ int iOpp = Directions.iOpposite(iDir); ++ Direction opp = Directions.ALL[iOpp]; ++ ++ updateShape(neighbor, opp, wirePos, wireState); ++ } ++ } ++ } ++ ++ private void updateShape(Node node, Direction dir, BlockPos neighborPos, BlockState neighborState) { ++ BlockPos pos = node.pos; ++ BlockState state = level.getBlockState(pos); ++ ++ // Shape updates to redstone wire are very expensive, and should never happen ++ // as a result of power changes anyway. ++ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { ++ BlockState newState = state.updateShape(dir, neighborState, level, pos, neighborPos); ++ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); ++ } ++ } ++ ++ /** ++ * Queue block updates to nodes around the given wire. ++ */ ++ private void queueNeighbors(WireNode wire) { ++ forEachNeighbor(wire, neighbor -> { ++ queueNeighbor(neighbor, wire); ++ }); ++ } ++ ++ /** ++ * Queue the given node for an update from the given neighboring wire. ++ */ ++ private void queueNeighbor(Node node, WireNode neighborWire) { ++ // Updates to wires are queued when power is transmitted. ++ if (!node.isWire()) { ++ node.neighborWire = neighborWire; ++ updates.offer(node); ++ } ++ } ++ ++ /** ++ * Queue the given wire for a power change. If the wire does not need a power ++ * change (perhaps because its power has already changed), transmit power to ++ * neighboring wires. ++ */ ++ private void queueWire(WireNode wire) { ++ if (needsUpdate(wire)) { ++ updates.offer(wire); ++ } else { ++ findPowerFlow(wire); ++ transmitPower(wire); ++ } ++ } ++ ++ /** ++ * Emit a block update to the given node. ++ */ ++ private void updateBlock(Node node, BlockPos neighborPos, Block neighborBlock) { ++ BlockPos pos = node.pos; ++ BlockState state = level.getBlockState(pos); ++ ++ // While this check makes sure wires in the network are not given block ++ // updates, it also prevents block updates to wires in neighboring networks. ++ // While this should not make a difference in theory, in practice, it is ++ // possible to force a network into an invalid state without updating it, even ++ // if it is relatively obscure. ++ // While I was willing to make this compromise in return for some significant ++ // performance gains in certain setups, if you are not, you can add all the ++ // positions of the network to a set and filter out block updates to wires in ++ // the network that way. ++ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { ++ state.neighborChanged(level, pos, neighborBlock, neighborPos, false); ++ } ++ } ++ ++ @FunctionalInterface ++ public static interface NodeProvider { ++ ++ public Node getNeighbor(Node node, int iDir); ++ ++ } ++} +diff --git a/src/main/java/alternate/current/wire/WireNode.java b/src/main/java/alternate/current/wire/WireNode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33cd90c30c22200a4e1ae64f40a0bf7864546b33 +--- /dev/null ++++ b/src/main/java/alternate/current/wire/WireNode.java +@@ -0,0 +1,122 @@ ++package alternate.current.wire; ++ ++import net.minecraft.core.BlockPos; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.util.Mth; ++import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.Blocks; ++import net.minecraft.world.level.block.RedStoneWireBlock; ++import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.redstone.Redstone; ++ ++/** ++ * A WireNode is a Node that represents a wire in the world. It stores all the ++ * information about the wire that the WireHandler needs to calculate power ++ * changes. ++ * ++ * @author Space Walker ++ */ ++public class WireNode extends Node { ++ ++ final WireConnectionManager connections; ++ ++ /** The power level this wire currently holds in the world. */ ++ int currentPower; ++ /** ++ * While calculating power changes for a network, this field is used to keep ++ * track of the power level this wire should have. ++ */ ++ int virtualPower; ++ /** The power level received from non-wire components. */ ++ int externalPower; ++ /** ++ * A 4-bit number that keeps track of the power flow of the wires that give this ++ * wire its power level. ++ */ ++ int flowIn; ++ /** The direction of power flow, based on the incoming flow. */ ++ int iFlowDir; ++ boolean added; ++ boolean removed; ++ boolean shouldBreak; ++ boolean root; ++ boolean discovered; ++ boolean searched; ++ ++ /** The next wire in the simple queue. */ ++ WireNode next_wire; ++ ++ WireNode(ServerLevel level, BlockPos pos, BlockState state) { ++ super(level); ++ ++ this.pos = pos.immutable(); ++ this.state = state; ++ ++ this.connections = new WireConnectionManager(this); ++ ++ this.virtualPower = this.currentPower = this.state.getValue(RedStoneWireBlock.POWER); ++ this.priority = priority(); ++ } ++ ++ @Override ++ Node set(BlockPos pos, BlockState state, boolean clearNeighbors) { ++ throw new UnsupportedOperationException("Cannot update a WireNode!"); ++ } ++ ++ @Override ++ int priority() { ++ return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX); ++ } ++ ++ @Override ++ public boolean isWire() { ++ return true; ++ } ++ ++ @Override ++ public WireNode asWire() { ++ return this; ++ } ++ ++ boolean offerPower(int power, int iDir) { ++ if (removed || shouldBreak) { ++ return false; ++ } ++ if (power == virtualPower) { ++ flowIn |= (1 << iDir); ++ return false; ++ } ++ if (power > virtualPower) { ++ virtualPower = power; ++ flowIn = (1 << iDir); ++ ++ return true; ++ } ++ ++ return false; ++ } ++ ++ boolean setPower() { ++ if (removed) { ++ return true; ++ } ++ ++ state = level.getBlockState(pos); ++ ++ if (!state.is(Blocks.REDSTONE_WIRE)) { ++ return false; // we should never get here ++ } ++ ++ if (shouldBreak) { ++ Block.dropResources(state, level, pos); ++ level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); ++ ++ return true; ++ } ++ ++ currentPower = LevelHelper.doRedstoneEvent(level, pos, currentPower, Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX)); ++ state = state.setValue(RedStoneWireBlock.POWER, currentPower); ++ ++ return LevelHelper.setWireState(level, pos, state, added); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index e5a64e70020487b15825a865623afa45b0ae59d4..684063b8433f78ef0adf0de1057ea24720b32181 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -219,6 +219,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + public final UUID uuid; + public boolean hasPhysicsEvent = true; // Paper + public boolean hasEntityMoveEvent = false; // Paper ++ private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public static Throwable getAddToWorldStackTrace(Entity entity) { + return new Throwable(entity + " Added to world at " + new java.util.Date()); + } +@@ -2484,6 +2485,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + return this.entityManager.canPositionTick(pos.toLong()); // Paper + } + ++ // Paper start - optimize redstone (Alternate Current) ++ @Override ++ public alternate.current.wire.WireHandler getWireHandler() { ++ return wireHandler; ++ } ++ // Paper end ++ + private final class EntityCallbacks implements LevelCallback { + + EntityCallbacks() {} +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 30140ae5a74a511c9031b8e772e724b25e56de3d..46c45f249e71ca94044f1260a23cd7331098fb2c 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -1453,4 +1453,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + return ret; + } + // Paper end ++ ++ // Paper start - optimize redstone (Alternate Current) ++ public alternate.current.wire.WireHandler getWireHandler() { ++ // This method is overridden in ServerLevel. ++ // Since Paper is a server platform there is no risk ++ // of this implementation being called. It is here ++ // only so this method can be called without casting ++ // an instance of Level to ServerLevel. ++ return null; ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +index 3cba4921daad4b346a3f964f0fa48e1bb4d634a3..2bc21e3373f6fc6fbbaa7202ba82e7da86045b6a 100644 +--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -253,7 +253,7 @@ public class RedStoneWireBlock extends Block { + return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); + } + +- // Paper start - Optimize redstone ++ // Paper start - Optimize redstone (Eigencraft) + // The bulk of the new functionality is found in RedstoneWireTurbo.java + com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); + +@@ -455,7 +455,13 @@ public class RedStoneWireBlock extends Block { + @Override + public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { + if (!oldState.is(state.getBlock()) && !world.isClientSide) { +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ // Paper start - optimize redstone - replace call to updatePowerStrength ++ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireAdded(pos); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end + Iterator iterator = Direction.Plane.VERTICAL.iterator(); + + while (iterator.hasNext()) { +@@ -482,7 +488,13 @@ public class RedStoneWireBlock extends Block { + world.updateNeighborsAt(pos.relative(enumdirection), this); + } + +- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone ++ // Paper start - optimize redstone - replace call to updatePowerStrength ++ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireRemoved(pos, state); // Alternate Current ++ } else { ++ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft ++ } ++ // Paper end + this.updateNeighborsOfNeighboringWires(world, pos); + } + } +@@ -516,8 +528,14 @@ public class RedStoneWireBlock extends Block { + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + if (!world.isClientSide) { ++ // Paper start - optimize redstone (Alternate Current) ++ // Alternate Current handles breaking of redstone wires in the WireHandler. ++ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { ++ world.getWireHandler().onWireUpdated(pos); ++ } else ++ // Paper end + if (state.canSurvive(world, pos)) { +- this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone ++ this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone (Eigencraft) + } else { + dropResources(state, world, pos); + world.removeBlock(pos, false); diff --git a/patches/server/0900-Throw-exception-on-world-create-while-being-ticked.patch b/patches/server/0900-Throw-exception-on-world-create-while-being-ticked.patch deleted file mode 100644 index 978f8d8459..0000000000 --- a/patches/server/0900-Throw-exception-on-world-create-while-being-ticked.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 22 Mar 2022 12:44:30 -0700 -Subject: [PATCH] Throw exception on world create while being ticked - -There are no plans to support creating worlds while worlds are -being ticked themselvess. - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 4d920031300a9801debc2eb39a4d3cb9d8fbb330..dd9ab51e904be2f2f2a2981d4f0f6638a6895e8d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -297,6 +297,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { - AtomicReference atomicreference = new AtomicReference(); -@@ -1527,6 +1528,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper -@@ -1574,6 +1576,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Wed, 8 Jun 2022 18:47:18 +0200 -Subject: [PATCH] Add Alternate Current redstone implementation - -Author: Space Walker - -Original license: MIT -Original project: https://github.com/SpaceWalkerRS/alternate-current - -This patch adds Alternate Current's redstone implementation as an alternative to vanilla and Eigencraft's. -Performance of (de)powering redstone dust is many times faster than vanilla, and even exceeds Eigencraft. -Similar to Eigencraft, Alternate Current heavily changes the update order of redstone dust. This means any contraption that -is location dependent in vanilla will either work everywhere or nowhere when using Alternate Current/Eigencraft. Beyond that -parity issues should be rare for both implementations, though Alternate Current has not been tested as thoroughly, so I -cannot comment on how the two compare in that aspect. - -Alternate Current needs the following modifications: -* Level/ServerLevel: Each level has its own 'wire handler' that handles redstone dust power changes. -* RedStoneWireBlock: Replace calls to vanilla's or Eigencraft's methods for handling power changes with calls to -Alternate Current's wire handler. - -diff --git a/src/main/java/alternate/current/wire/LevelHelper.java b/src/main/java/alternate/current/wire/LevelHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f55c5c67b8461e9ef5614ea1a37f6e2866f39be3 ---- /dev/null -+++ b/src/main/java/alternate/current/wire/LevelHelper.java -@@ -0,0 +1,65 @@ -+package alternate.current.wire; -+ -+import org.bukkit.event.block.BlockRedstoneEvent; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+ -+public class LevelHelper { -+ -+ static int doRedstoneEvent(ServerLevel level, BlockPos pos, int prevPower, int newPower) { -+ BlockRedstoneEvent event = new BlockRedstoneEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), prevPower, newPower); -+ level.getCraftServer().getPluginManager().callEvent(event); -+ -+ return event.getNewCurrent(); -+ } -+ -+ /** -+ * An optimized version of {@link net.minecraft.world.level.Level#setBlock -+ * Level.setBlock}. Since this method is only used to update redstone wire block -+ * states, lighting checks, height map updates, and block entity updates are -+ * omitted. -+ */ -+ static boolean setWireState(ServerLevel level, BlockPos pos, BlockState state, boolean updateNeighborShapes) { -+ int y = pos.getY(); -+ -+ if (y < level.getMinBuildHeight() || y >= level.getMaxBuildHeight()) { -+ return false; -+ } -+ -+ int x = pos.getX(); -+ int z = pos.getZ(); -+ int index = level.getSectionIndex(y); -+ -+ ChunkAccess chunk = level.getChunk(x >> 4, z >> 4, ChunkStatus.FULL, true); -+ LevelChunkSection section = chunk.getSections()[index]; -+ -+ if (section == null) { -+ return false; // we should never get here -+ } -+ -+ BlockState prevState = section.setBlockState(x & 15, y & 15, z & 15, state); -+ -+ if (state == prevState) { -+ return false; -+ } -+ -+ // notify clients of the BlockState change -+ level.getChunkSource().blockChanged(pos); -+ // mark the chunk for saving -+ chunk.setUnsaved(true); -+ -+ if (updateNeighborShapes) { -+ prevState.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); -+ state.updateNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); -+ state.updateIndirectNeighbourShapes(level, pos, Block.UPDATE_CLIENTS); -+ } -+ -+ return true; -+ } -+} -diff --git a/src/main/java/alternate/current/wire/Node.java b/src/main/java/alternate/current/wire/Node.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8af6c69098e64945361d116b5fd6ac21e97fcd8d ---- /dev/null -+++ b/src/main/java/alternate/current/wire/Node.java -@@ -0,0 +1,113 @@ -+package alternate.current.wire; -+ -+import java.util.Arrays; -+ -+import alternate.current.wire.WireHandler.Directions; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+ -+/** -+ * A Node represents a block in the world. It also holds a few other pieces of -+ * information that speed up the calculations in the WireHandler class. -+ * -+ * @author Space Walker -+ */ -+public class Node { -+ -+ // flags that encode the Node type -+ private static final int CONDUCTOR = 0b01; -+ private static final int SOURCE = 0b10; -+ -+ final ServerLevel level; -+ final Node[] neighbors; -+ -+ BlockPos pos; -+ BlockState state; -+ boolean invalid; -+ -+ private int flags; -+ -+ /** The previous node in the priority queue. */ -+ Node prev_node; -+ /** The next node in the priority queue. */ -+ Node next_node; -+ /** The priority with which this node was queued. */ -+ int priority; -+ /** The wire that queued this node for an update. */ -+ WireNode neighborWire; -+ -+ Node(ServerLevel level) { -+ this.level = level; -+ this.neighbors = new Node[Directions.ALL.length]; -+ } -+ -+ @Override -+ public boolean equals(Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ if (!(obj instanceof Node)) { -+ return false; -+ } -+ -+ Node node = (Node)obj; -+ -+ return level == node.level && pos.equals(node.pos); -+ } -+ -+ @Override -+ public int hashCode() { -+ return pos.hashCode(); -+ } -+ -+ Node set(BlockPos pos, BlockState state, boolean clearNeighbors) { -+ if (state.is(Blocks.REDSTONE_WIRE)) { -+ throw new IllegalStateException("Cannot update a regular Node to a WireNode!"); -+ } -+ -+ if (clearNeighbors) { -+ Arrays.fill(neighbors, null); -+ } -+ -+ this.pos = pos.immutable(); -+ this.state = state; -+ this.invalid = false; -+ -+ this.flags = 0; -+ -+ if (this.state.isRedstoneConductor(this.level, this.pos)) { -+ this.flags |= CONDUCTOR; -+ } -+ if (this.state.isSignalSource()) { -+ this.flags |= SOURCE; -+ } -+ -+ return this; -+ } -+ -+ /** -+ * Determine the priority with which this node should be queued. -+ */ -+ int priority() { -+ return neighborWire.priority; -+ } -+ -+ public boolean isWire() { -+ return false; -+ } -+ -+ public boolean isConductor() { -+ return (flags & CONDUCTOR) != 0; -+ } -+ -+ public boolean isSignalSource() { -+ return (flags & SOURCE) != 0; -+ } -+ -+ public WireNode asWire() { -+ throw new UnsupportedOperationException("Not a WireNode!"); -+ } -+} -diff --git a/src/main/java/alternate/current/wire/PriorityQueue.java b/src/main/java/alternate/current/wire/PriorityQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d71b4d0e4c44a2620b41b89475412db53bea20ed ---- /dev/null -+++ b/src/main/java/alternate/current/wire/PriorityQueue.java -@@ -0,0 +1,211 @@ -+package alternate.current.wire; -+ -+import java.util.AbstractQueue; -+import java.util.Arrays; -+import java.util.Iterator; -+ -+import net.minecraft.world.level.redstone.Redstone; -+ -+public class PriorityQueue extends AbstractQueue { -+ -+ private static final int OFFSET = -Redstone.SIGNAL_MIN; -+ -+ /** The last node for each priority value. */ -+ private final Node[] tails; -+ -+ private Node head; -+ private Node tail; -+ -+ private int size; -+ -+ PriorityQueue() { -+ this.tails = new Node[(Redstone.SIGNAL_MAX + OFFSET) + 1]; -+ } -+ -+ @Override -+ public boolean offer(Node node) { -+ if (node == null) { -+ throw new NullPointerException(); -+ } -+ -+ int priority = node.priority(); -+ -+ if (contains(node)) { -+ if (node.priority == priority) { -+ // already queued with this priority; exit -+ return false; -+ } else { -+ // already queued with different priority; move it -+ move(node, priority); -+ } -+ } else { -+ insert(node, priority); -+ } -+ -+ return true; -+ } -+ -+ @Override -+ public Node poll() { -+ if (head == null) { -+ return null; -+ } -+ -+ Node node = head; -+ Node next = node.next_node; -+ -+ if (next == null) { -+ clear(); // reset the tails array -+ } else { -+ if (node.priority != next.priority) { -+ // If the head is also a tail, its entry in the array -+ // can be cleared; there is no previous node with the -+ // same priority to take its place. -+ tails[node.priority + OFFSET] = null; -+ } -+ -+ node.next_node = null; -+ next.prev_node = null; -+ head = next; -+ -+ size--; -+ } -+ -+ return node; -+ } -+ -+ @Override -+ public Node peek() { -+ return head; -+ } -+ -+ @Override -+ public void clear() { -+ for (Node node = head; node != null; ) { -+ Node n = node; -+ node = node.next_node; -+ -+ n.prev_node = null; -+ n.next_node = null; -+ } -+ -+ Arrays.fill(tails, null); -+ -+ head = null; -+ tail = null; -+ -+ size = 0; -+ } -+ -+ @Override -+ public Iterator iterator() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int size() { -+ return size; -+ } -+ -+ public boolean contains(Node node) { -+ return node == head || node.prev_node != null; -+ } -+ -+ private void move(Node node, int priority) { -+ remove(node); -+ insert(node, priority); -+ } -+ -+ private void remove(Node node) { -+ Node prev = node.prev_node; -+ Node next = node.next_node; -+ -+ if (node == tail || node.priority != next.priority) { -+ // assign a new tail for this node's priority -+ if (node == head || node.priority != prev.priority) { -+ // there is no other node with the same priority; clear -+ tails[node.priority + OFFSET] = null; -+ } else { -+ // the previous node in the queue becomes the tail -+ tails[node.priority + OFFSET] = prev; -+ } -+ } -+ -+ if (node == head) { -+ head = next; -+ } else { -+ prev.next_node = next; -+ } -+ if (node == tail) { -+ tail = prev; -+ } else { -+ next.prev_node = prev; -+ } -+ -+ node.prev_node = null; -+ node.next_node = null; -+ -+ size--; -+ } -+ -+ private void insert(Node node, int priority) { -+ node.priority = priority; -+ -+ // nodes are sorted by priority (highest to lowest) -+ // nodes with the same priority are ordered FIFO -+ if (head == null) { -+ // first element in this queue \o/ -+ head = tail = node; -+ } else if (priority > head.priority) { -+ linkHead(node); -+ } else if (priority <= tail.priority) { -+ linkTail(node); -+ } else { -+ // since the node is neither the head nor the tail -+ // findPrev is guaranteed to find a non-null element -+ linkAfter(findPrev(node), node); -+ } -+ -+ tails[priority + OFFSET] = node; -+ -+ size++; -+ } -+ -+ private void linkHead(Node node) { -+ node.next_node = head; -+ head.prev_node = node; -+ head = node; -+ } -+ -+ private void linkTail(Node node) { -+ tail.next_node = node; -+ node.prev_node = tail; -+ tail = node; -+ } -+ -+ private void linkAfter(Node prev, Node node) { -+ linkBetween(prev, node, prev.next_node); -+ } -+ -+ private void linkBetween(Node prev, Node node, Node next) { -+ prev.next_node = node; -+ node.prev_node = prev; -+ -+ node.next_node = next; -+ next.prev_node = node; -+ } -+ -+ private Node findPrev(Node node) { -+ Node prev = null; -+ -+ for (int i = node.priority + OFFSET; i < tails.length; i++) { -+ prev = tails[i]; -+ -+ if (prev != null) { -+ break; -+ } -+ } -+ -+ return prev; -+ } -+} -diff --git a/src/main/java/alternate/current/wire/SimpleQueue.java b/src/main/java/alternate/current/wire/SimpleQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2b30074252551e1dc55d5be17d26fb4a2d8eb2e4 ---- /dev/null -+++ b/src/main/java/alternate/current/wire/SimpleQueue.java -@@ -0,0 +1,112 @@ -+package alternate.current.wire; -+ -+import java.util.AbstractQueue; -+import java.util.Iterator; -+ -+public class SimpleQueue extends AbstractQueue { -+ -+ private WireNode head; -+ private WireNode tail; -+ -+ private int size; -+ -+ SimpleQueue() { -+ -+ } -+ -+ @Override -+ public boolean offer(WireNode node) { -+ if (node == null) { -+ throw new NullPointerException(); -+ } -+ -+ if (tail == null) { -+ head = tail = node; -+ } else { -+ tail.next_wire = node; -+ tail = node; -+ } -+ -+ size++; -+ -+ return true; -+ } -+ -+ @Override -+ public WireNode poll() { -+ if (head == null) { -+ return null; -+ } -+ -+ WireNode node = head; -+ WireNode next = node.next_wire; -+ -+ if (next == null) { -+ head = tail = null; -+ } else { -+ node.next_wire = null; -+ head = next; -+ } -+ -+ size--; -+ -+ return node; -+ } -+ -+ @Override -+ public WireNode peek() { -+ return head; -+ } -+ -+ @Override -+ public void clear() { -+ for (WireNode node = head; node != null; ) { -+ WireNode n = node; -+ node = node.next_wire; -+ -+ n.next_wire = null; -+ } -+ -+ head = null; -+ tail = null; -+ -+ size = 0; -+ } -+ -+ @Override -+ public Iterator iterator() { -+ return new SimpleIterator(); -+ } -+ -+ @Override -+ public int size() { -+ return size; -+ } -+ -+ private class SimpleIterator implements Iterator { -+ -+ private WireNode curr; -+ private WireNode next; -+ -+ private SimpleIterator() { -+ next = head; -+ } -+ -+ @Override -+ public boolean hasNext() { -+ if (next == null && curr != null) { -+ next = curr.next_wire; -+ } -+ -+ return next != null; -+ } -+ -+ @Override -+ public WireNode next() { -+ curr = next; -+ next = curr.next_wire; -+ -+ return curr; -+ } -+ } -+} -diff --git a/src/main/java/alternate/current/wire/WireConnection.java b/src/main/java/alternate/current/wire/WireConnection.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4fd8cb29024330397cfe4cbc1f237d285bfb7b3e ---- /dev/null -+++ b/src/main/java/alternate/current/wire/WireConnection.java -@@ -0,0 +1,30 @@ -+package alternate.current.wire; -+ -+/** -+ * This class represents a connection between some WireNode (the 'owner') and a -+ * neighboring WireNode. Two wires are considered to be connected if power can -+ * flow from one wire to the other (and/or vice versa). -+ * -+ * @author Space Walker -+ */ -+public class WireConnection { -+ -+ /** The connected wire. */ -+ final WireNode wire; -+ /** Cardinal direction to the connected wire. */ -+ final int iDir; -+ /** True if the owner of the connection can provide power to the connected wire. */ -+ final boolean offer; -+ /** True if the connected wire can provide power to the owner of the connection. */ -+ final boolean accept; -+ -+ /** The next connection in the sequence. */ -+ WireConnection next; -+ -+ WireConnection(WireNode wire, int iDir, boolean offer, boolean accept) { -+ this.wire = wire; -+ this.iDir = iDir; -+ this.offer = offer; -+ this.accept = accept; -+ } -+} -diff --git a/src/main/java/alternate/current/wire/WireConnectionManager.java b/src/main/java/alternate/current/wire/WireConnectionManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5a7209f05b549c222f6c9bc2af2a35790964947e ---- /dev/null -+++ b/src/main/java/alternate/current/wire/WireConnectionManager.java -@@ -0,0 +1,136 @@ -+package alternate.current.wire; -+ -+import java.util.Arrays; -+import java.util.function.Consumer; -+ -+import alternate.current.wire.WireHandler.Directions; -+import alternate.current.wire.WireHandler.NodeProvider; -+ -+public class WireConnectionManager { -+ -+ /** The owner of these connections. */ -+ final WireNode owner; -+ -+ /** The first connection for each cardinal direction. */ -+ private final WireConnection[] heads; -+ -+ private WireConnection head; -+ private WireConnection tail; -+ -+ /** The total number of connections. */ -+ int total; -+ -+ /** -+ * A 4 bit number that encodes in which direction(s) the owner has connections -+ * to other wires. -+ */ -+ private int flowTotal; -+ /** The direction of flow based connections to other wires. */ -+ int iFlowDir; -+ -+ WireConnectionManager(WireNode owner) { -+ this.owner = owner; -+ -+ this.heads = new WireConnection[Directions.HORIZONTAL.length]; -+ -+ this.total = 0; -+ -+ this.flowTotal = 0; -+ this.iFlowDir = -1; -+ } -+ -+ void set(NodeProvider nodes) { -+ if (total > 0) { -+ clear(); -+ } -+ -+ boolean belowIsConductor = nodes.getNeighbor(owner, Directions.DOWN).isConductor(); -+ boolean aboveIsConductor = nodes.getNeighbor(owner, Directions.UP).isConductor(); -+ -+ for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) { -+ Node neighbor = nodes.getNeighbor(owner, iDir); -+ -+ if (neighbor.isWire()) { -+ add(neighbor.asWire(), iDir, true, true); -+ -+ continue; -+ } -+ -+ boolean sideIsConductor = neighbor.isConductor(); -+ -+ if (!sideIsConductor) { -+ Node node = nodes.getNeighbor(neighbor, Directions.DOWN); -+ -+ if (node.isWire()) { -+ add(node.asWire(), iDir, belowIsConductor, true); -+ } -+ } -+ if (!aboveIsConductor) { -+ Node node = nodes.getNeighbor(neighbor, Directions.UP); -+ -+ if (node.isWire()) { -+ add(node.asWire(), iDir, true, sideIsConductor); -+ } -+ } -+ } -+ -+ if (total > 0) { -+ iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal]; -+ } -+ } -+ -+ private void clear() { -+ Arrays.fill(heads, null); -+ -+ head = null; -+ tail = null; -+ -+ total = 0; -+ -+ flowTotal = 0; -+ iFlowDir = -1; -+ } -+ -+ private void add(WireNode wire, int iDir, boolean offer, boolean accept) { -+ add(new WireConnection(wire, iDir, offer, accept)); -+ } -+ -+ private void add(WireConnection connection) { -+ if (head == null) { -+ head = connection; -+ tail = connection; -+ } else { -+ tail.next = connection; -+ tail = connection; -+ } -+ -+ total++; -+ -+ if (heads[connection.iDir] == null) { -+ heads[connection.iDir] = connection; -+ flowTotal |= (1 << connection.iDir); -+ } -+ } -+ -+ /** -+ * Iterate over all connections. Use this method if the iteration order is not -+ * important. -+ */ -+ void forEach(Consumer consumer) { -+ for (WireConnection c = head; c != null; c = c.next) { -+ consumer.accept(c); -+ } -+ } -+ -+ /** -+ * Iterate over all connections. Use this method if the iteration order is -+ * important. -+ */ -+ void forEach(Consumer consumer, int iFlowDir) { -+ for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) { -+ for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) { -+ consumer.accept(c); -+ } -+ } -+ } -+} -diff --git a/src/main/java/alternate/current/wire/WireHandler.java b/src/main/java/alternate/current/wire/WireHandler.java -new file mode 100644 -index 0000000000000000000000000000000000000000..35d9017c21ce77290d8e86cceb0676666e6e0eff ---- /dev/null -+++ b/src/main/java/alternate/current/wire/WireHandler.java -@@ -0,0 +1,1150 @@ -+package alternate.current.wire; -+ -+import java.util.Iterator; -+import java.util.Queue; -+import java.util.function.Consumer; -+ -+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 net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.redstone.Redstone; -+ -+/** -+ * This class handles power changes for redstone wire. The algorithm was -+ * designed with the following goals in mind: -+ *
-+ * 1. Minimize the number of times a wire checks its surroundings to determine -+ * its power level. -+ *
-+ * 2. Minimize the number of block and shape updates emitted. -+ *
-+ * 3. Emit block and shape updates in a deterministic, non-locational order, -+ * fixing bug MC-11193. -+ * -+ *

-+ * In Vanilla redstone wire is laggy because it fails on points 1 and 2. -+ * -+ *

-+ * Redstone wire updates recursively and each wire calculates its power level in -+ * isolation rather than in the context of the network it is a part of. This -+ * means a wire in a grid can change its power level over half a dozen times -+ * before settling on its final value. This problem used to be worse in 1.13 and -+ * below, where a wire would only decrease its power level by 1 at a time. -+ * -+ *

-+ * In addition to this, a wire emits 42 block updates and up to 22 shape updates -+ * each time it changes its power level. -+ * -+ *

-+ * Of those 42 block updates, 6 are to itself, which are thus not only -+ * redundant, but a big source of lag, since those cause the wire to -+ * unnecessarily re-calculate its power level. A block only has 24 neighbors -+ * within a Manhattan distance of 2, meaning 12 of the remaining 36 block -+ * updates are duplicates and thus also redundant. -+ * -+ *

-+ * Of the 22 shape updates, only 6 are strictly necessary. The other 16 are sent -+ * to blocks diagonally above and below. These are necessary if a wire changes -+ * its connections, but not when it changes its power level. -+ * -+ *

-+ * Redstone wire in Vanilla also fails on point 3, though this is more of a -+ * quality-of-life issue than a lag issue. The recursive nature in which it -+ * updates, combined with the location-dependent order in which each wire -+ * updates its neighbors, makes the order in which neighbors of a wire network -+ * are updated incredibly inconsistent and seemingly random. -+ * -+ *

-+ * Alternate Current fixes each of these problems as follows. -+ * -+ *

-+ * 1. To make sure a wire calculates its power level as little as possible, we -+ * remove the recursive nature in which redstone wire updates in Vanilla. -+ * Instead, we build a network of connected wires, find those wires that receive -+ * redstone power from "outside" the network, and spread the power from there. -+ * This has a few advantages: -+ *
-+ * - Each wire checks for power from non-wire components at most once, and from -+ * nearby wires just twice. -+ *
-+ * - Each wire only sets its power level in the world once. This is important, -+ * because calls to Level.setBlock are even more expensive than calls to -+ * Level.getBlockState. -+ * -+ *

-+ * 2. There are 2 obvious ways in which we can reduce the number of block and -+ * shape updates. -+ *
-+ * - Get rid of the 18 redundant block updates and 16 redundant shape updates, -+ * so each wire only emits 24 block updates and 6 shape updates whenever it -+ * changes its power level. -+ *
-+ * - Only emit block updates and shape updates once a wire reaches its final -+ * power level, rather than at each intermediary stage. -+ *
-+ * For an individual wire, these two optimizations are the best you can do, but -+ * for an entire grid, you can do better! -+ * -+ *

-+ * Since we calculate the power of the entire network, sending block and shape -+ * updates to the wires in it is redundant. Removing those updates can reduce -+ * the number of block and shape updates by up to 20%. -+ * -+ *

-+ * 3. To make the order of block updates to neighbors of a network -+ * deterministic, the first thing we must do is to replace the location- -+ * dependent order in which a wire updates its neighbors. Instead, we base it on -+ * the direction of power flow. This part of the algorithm was heavily inspired -+ * by theosib's 'RedstoneWireTurbo', which you can read more about in theosib's -+ * comment on Mojira here -+ * or by checking out its implementation in carpet mod here. -+ * -+ *

-+ * The idea is to determine the direction of power flow through a wire based on -+ * the power it receives from neighboring wires. For example, if the only power -+ * a wire receives is from a neighboring wire to its west, it can be said that -+ * the direction of power flow through the wire is east. -+ * -+ *

-+ * We make the order of block updates to neighbors of a wire depend on what is -+ * determined to be the direction of power flow. This not only removes -+ * locationality entirely, it even removes directionality in a large number of -+ * cases. Unlike in 'RedstoneWireTurbo', however, I have decided to keep a -+ * directional element in ambiguous cases, rather than to introduce randomness, -+ * though this is trivial to change. -+ * -+ *

-+ * While this change fixes the block update order of individual wires, we must -+ * still address the overall block update order of a network. This turns out to -+ * be a simple fix, because of a change we made earlier: we search through the -+ * network for wires that receive power from outside it, and spread the power -+ * from there. If we make each wire transmit its power to neighboring wires in -+ * an order dependent on the direction of power flow, we end up with a -+ * non-locational and largely non-directional wire update order. -+ * -+ * @author Space Walker -+ */ -+public class WireHandler { -+ -+ public static class Directions { -+ -+ public static final Direction[] ALL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.DOWN, Direction.UP }; -+ public static final Direction[] HORIZONTAL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH }; -+ -+ // Indices for the arrays above. -+ // The cardinal directions are ordered clockwise. This allows -+ // for conversion between relative and absolute directions -+ // ('left' 'right' vs 'east' 'west') with simple arithmetic: -+ // If some Direction index 'iDir' is considered 'forward', then -+ // '(iDir + 1) & 0b11' is 'right', '(iDir + 2) & 0b11' is 'backward', etc. -+ public static final int WEST = 0b000; // 0 -+ public static final int NORTH = 0b001; // 1 -+ public static final int EAST = 0b010; // 2 -+ public static final int SOUTH = 0b011; // 3 -+ public static final int DOWN = 0b100; // 4 -+ public static final int UP = 0b101; // 5 -+ -+ public static int iOpposite(int iDir) { -+ return iDir ^ (0b10 >>> (iDir >>> 2)); -+ } -+ -+ // Each array is placed at the index that encodes the direction that is missing -+ // from the array. -+ private static final int[][] I_EXCEPT = { -+ { NORTH, EAST, SOUTH, DOWN, UP }, -+ { WEST, EAST, SOUTH, DOWN, UP }, -+ { WEST, NORTH, SOUTH, DOWN, UP }, -+ { WEST, NORTH, EAST, DOWN, UP }, -+ { WEST, NORTH, EAST, SOUTH, UP }, -+ { WEST, NORTH, EAST, SOUTH, DOWN } -+ }; -+ private static final int[][] I_EXCEPT_CARDINAL = { -+ { NORTH, EAST, SOUTH }, -+ { WEST, EAST, SOUTH }, -+ { WEST, NORTH, SOUTH }, -+ { WEST, NORTH, EAST, }, -+ { WEST, NORTH, EAST, SOUTH }, -+ { WEST, NORTH, EAST, SOUTH } -+ }; -+ } -+ -+ /** -+ * This conversion table takes in information about incoming flow, and outputs -+ * the determined outgoing flow. -+ * -+ *

-+ * The input is a 4 bit number that encodes the incoming flow. Each bit -+ * represents a cardinal direction, and when it is 'on', there is flow in that -+ * direction. -+ * -+ *

-+ * The output is a single Direction index, or -1 for ambiguous cases. -+ * -+ *

-+ * The outgoing flow is determined as follows: -+ * -+ *

-+ * If there is just 1 direction of incoming flow, that direction will be the -+ * direction of outgoing flow. -+ * -+ *

-+ * If there are 2 directions of incoming flow, and these directions are not each -+ * other's opposites, the direction that is 'more clockwise' will be the -+ * direction of outgoing flow. More precisely, the direction that is 1 clockwise -+ * turn from the other is picked. -+ * -+ *

-+ * If there are 3 directions of incoming flow, the two opposing directions -+ * cancel each other out, and the remaining direction will be the direction of -+ * outgoing flow. -+ * -+ *

-+ * In all other cases, the flow is completely ambiguous. -+ */ -+ static final int[] FLOW_IN_TO_FLOW_OUT = { -+ -1, // 0b0000: - -> x -+ Directions.WEST, // 0b0001: west -> west -+ Directions.NORTH, // 0b0010: north -> north -+ Directions.NORTH, // 0b0011: west/north -> north -+ Directions.EAST, // 0b0100: east -> east -+ -1, // 0b0101: west/east -> x -+ Directions.EAST, // 0b0110: north/east -> east -+ Directions.NORTH, // 0b0111: west/north/east -> north -+ Directions.SOUTH, // 0b1000: south -> south -+ Directions.WEST, // 0b1001: west/south -> west -+ -1, // 0b1010: north/south -> x -+ Directions.WEST, // 0b1011: west/north/south -> west -+ Directions.SOUTH, // 0b1100: east/south -> south -+ Directions.SOUTH, // 0b1101: west/east/south -> south -+ Directions.EAST, // 0b1110: north/east/south -> east -+ -1, // 0b1111: west/north/east/south -> x -+ }; -+ /** -+ * Update orders of all directions. Given that the index encodes the direction -+ * that is to be considered 'forward', the resulting update order is -+ * { front, back, right, left, down, up }. -+ */ -+ static final int[][] FULL_UPDATE_ORDERS = { -+ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP }, -+ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP }, -+ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP }, -+ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP } -+ }; -+ /** -+ * The default update order of all directions. It is equivalent to the order of -+ * shape updates in vanilla Minecraft. -+ */ -+ static final int[] DEFAULT_FULL_UPDATE_ORDER = FULL_UPDATE_ORDERS[0]; -+ /** -+ * Update orders of cardinal directions. Given that the index encodes the -+ * direction that is to be considered 'forward', the resulting update order is -+ * { front, back, right, left }. -+ */ -+ static final int[][] CARDINAL_UPDATE_ORDERS = { -+ { Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH }, -+ { Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST }, -+ { Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH }, -+ { Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST } -+ }; -+ /** -+ * The default update order of all cardinal directions. -+ */ -+ static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0]; -+ -+ private static final int POWER_MIN = Redstone.SIGNAL_MIN; -+ private static final int POWER_MAX = Redstone.SIGNAL_MAX; -+ private static final int POWER_STEP = 1; -+ -+ // If Vanilla will ever multi-thread the ticking of levels, there should -+ // be only one WireHandler per level, in case redstone updates in multiple -+ // levels at the same time. There are already mods that add multi-threading -+ // as well. -+ private final ServerLevel level; -+ -+ /** Map of wires and neighboring blocks. */ -+ private final Long2ObjectMap nodes; -+ /** Queue for the breadth-first search through the network. */ -+ private final Queue search; -+ /** Queue of updates to wires and neighboring blocks. */ -+ private final Queue updates; -+ -+ // Rather than creating new nodes every time a network is updated we keep -+ // a cache of nodes that can be re-used. -+ private Node[] nodeCache; -+ private int nodeCount; -+ -+ /** Is this WireHandler currently working through the update queue? */ -+ private boolean updating; -+ -+ public WireHandler(ServerLevel level) { -+ this.level = level; -+ -+ this.nodes = new Long2ObjectOpenHashMap<>(); -+ this.search = new SimpleQueue(); -+ this.updates = new PriorityQueue(); -+ -+ this.nodeCache = new Node[16]; -+ this.fillNodeCache(0, 16); -+ } -+ -+ /** -+ * Retrieve the {@link alternate.current.wire.Node Node} that represents the -+ * block at the given position in the level. -+ */ -+ private Node getOrAddNode(BlockPos pos) { -+ return nodes.compute(pos.asLong(), (key, node) -> { -+ if (node == null) { -+ // If there is not yet a node at this position, retrieve and -+ // update one from the cache. -+ return getNextNode(pos); -+ } -+ if (node.invalid) { -+ return revalidateNode(node); -+ } -+ -+ return node; -+ }); -+ } -+ -+ /** -+ * Remove and return the {@link alternate.current.wire.Node Node} at the given -+ * position. -+ */ -+ private Node removeNode(BlockPos pos) { -+ return nodes.remove(pos.asLong()); -+ } -+ -+ /** -+ * Return a {@link alternate.current.wire.Node Node} that represents the block -+ * at the given position. -+ */ -+ private Node getNextNode(BlockPos pos) { -+ return getNextNode(pos, level.getBlockState(pos)); -+ } -+ -+ /** -+ * Return a node that represents the given position and block state. If it is a -+ * wire, then create a new {@link alternate.current.wire.WireNode WireNode}. -+ * Otherwise, grab the next {@link alternate.current.wire.Node Node} from the -+ * cache and update it. -+ */ -+ private Node getNextNode(BlockPos pos, BlockState state) { -+ return state.is(Blocks.REDSTONE_WIRE) ? new WireNode(level, pos, state) : getNextNode().set(pos, state, true); -+ } -+ -+ /** -+ * Grab the first unused node from the cache. If all of the cache is already in -+ * use, increase it in size first. -+ */ -+ private Node getNextNode() { -+ if (nodeCount == nodeCache.length) { -+ increaseNodeCache(); -+ } -+ -+ return nodeCache[nodeCount++]; -+ } -+ -+ private void increaseNodeCache() { -+ Node[] oldCache = nodeCache; -+ nodeCache = new Node[oldCache.length << 1]; -+ -+ for (int index = 0; index < oldCache.length; index++) { -+ nodeCache[index] = oldCache[index]; -+ } -+ -+ fillNodeCache(oldCache.length, nodeCache.length); -+ } -+ -+ private void fillNodeCache(int start, int end) { -+ for (int index = start; index < end; index++) { -+ nodeCache[index] = new Node(level); -+ } -+ } -+ -+ /** -+ * Try to revalidate the given node by looking at the block state that is -+ * occupying its position. If the given node is a wire but the block state is -+ * not, or vice versa, a new node must be created/grabbed from the cache. -+ * Otherwise, the node can be quickly revalidated with the new block state. -+ */ -+ private Node revalidateNode(Node node) { -+ BlockPos pos = node.pos; -+ BlockState state = level.getBlockState(pos); -+ -+ boolean wasWire = node.isWire(); -+ boolean isWire = state.is(Blocks.REDSTONE_WIRE); -+ -+ if (wasWire != isWire) { -+ return getNextNode(pos, state); -+ } -+ -+ node.invalid = false; -+ -+ if (isWire) { -+ // No need to update the block state of this wire - it will grab -+ // the current block state just before setting power anyway. -+ WireNode wire = node.asWire(); -+ -+ wire.root = false; -+ wire.discovered = false; -+ wire.searched = false; -+ } else { -+ node.set(pos, state, false); -+ } -+ -+ return node; -+ } -+ -+ /** -+ * Retrieve the neighbor of a node in the given direction and create a link -+ * between the two nodes if they are not yet linked. This link makes accessing -+ * neighbors of a node signficantly faster. -+ */ -+ private Node getNeighbor(Node node, int iDir) { -+ Node neighbor = node.neighbors[iDir]; -+ -+ if (neighbor == null || neighbor.invalid) { -+ Direction dir = Directions.ALL[iDir]; -+ BlockPos pos = node.pos.relative(dir); -+ -+ Node oldNeighbor = neighbor; -+ neighbor = getOrAddNode(pos); -+ -+ if (neighbor != oldNeighbor) { -+ int iOpp = Directions.iOpposite(iDir); -+ -+ node.neighbors[iDir] = neighbor; -+ neighbor.neighbors[iOpp] = node; -+ } -+ } -+ -+ return neighbor; -+ } -+ -+ /** -+ * Iterate over all neighboring nodes of the given wire. The iteration order is -+ * designed to be an extension of the default block update order, and is -+ * determined as follows: -+ *
-+ * 1. The direction of power flow through the wire is to be considered -+ * 'forward'. The iteration order depends on the neighbors' relative positions -+ * to the wire. -+ *
-+ * 2. Each neighbor is identified by the step(s) you must take, starting at the -+ * wire, to reach it. Each step is 1 block, thus the position of a neighbor is -+ * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left), -+ * etc. -+ *
-+ * 3. Neighbors are iterated over in pairs that lie on opposite sides of the -+ * wire. -+ *
-+ * 4. Neighbors are iterated over in order of their distance from the wire. This -+ * means they are iterated over in 3 groups: direct neighbors first, then -+ * diagonal neighbors, and last are the far neighbors that are 2 blocks directly -+ * out. -+ *
-+ * 5. The order within each group is determined using the following basic order: -+ * { front, back, right, left, down, up }. This order was chosen because it -+ * converts to the following order of absolute directions when west is said to -+ * be 'forward': { west, east, north, south, down, up } - this is the order of -+ * shape updates. -+ */ -+ private void forEachNeighbor(WireNode wire, Consumer consumer) { -+ int forward = wire.iFlowDir; -+ int rightward = (forward + 1) & 0b11; -+ int backward = (forward + 2) & 0b11; -+ int leftward = (forward + 3) & 0b11; -+ int downward = Directions.DOWN; -+ int upward = Directions.UP; -+ -+ Node front = getNeighbor(wire, forward); -+ Node right = getNeighbor(wire, rightward); -+ Node back = getNeighbor(wire, backward); -+ Node left = getNeighbor(wire, leftward); -+ Node below = getNeighbor(wire, downward); -+ Node above = getNeighbor(wire, upward); -+ -+ // direct neighbors (6) -+ consumer.accept(front); -+ consumer.accept(back); -+ consumer.accept(right); -+ consumer.accept(left); -+ consumer.accept(below); -+ consumer.accept(above); -+ -+ // diagonal neighbors (12) -+ consumer.accept(getNeighbor(front, rightward)); -+ consumer.accept(getNeighbor(back, leftward)); -+ consumer.accept(getNeighbor(front, leftward)); -+ consumer.accept(getNeighbor(back, rightward)); -+ consumer.accept(getNeighbor(front, downward)); -+ consumer.accept(getNeighbor(back, upward)); -+ consumer.accept(getNeighbor(front, upward)); -+ consumer.accept(getNeighbor(back, downward)); -+ consumer.accept(getNeighbor(right, downward)); -+ consumer.accept(getNeighbor(left, upward)); -+ consumer.accept(getNeighbor(right, upward)); -+ consumer.accept(getNeighbor(left, downward)); -+ -+ // far neighbors (6) -+ consumer.accept(getNeighbor(front, forward)); -+ consumer.accept(getNeighbor(back, backward)); -+ consumer.accept(getNeighbor(right, rightward)); -+ consumer.accept(getNeighbor(left, leftward)); -+ consumer.accept(getNeighbor(below, downward)); -+ consumer.accept(getNeighbor(above, upward)); -+ } -+ -+ /** -+ * This method should be called whenever a wire receives a block update. -+ */ -+ public void onWireUpdated(BlockPos pos) { -+ invalidate(); -+ findRoots(pos); -+ tryUpdate(); -+ } -+ -+ /** -+ * This method should be called whenever a wire is placed. -+ */ -+ public void onWireAdded(BlockPos pos) { -+ Node node = getOrAddNode(pos); -+ -+ if (!node.isWire()) { -+ return; // we should never get here -+ } -+ -+ WireNode wire = node.asWire(); -+ wire.added = true; -+ -+ invalidate(); -+ revalidateNode(wire); -+ findRoot(wire); -+ tryUpdate(); -+ } -+ -+ /** -+ * This method should be called whenever a wire is removed. -+ */ -+ public void onWireRemoved(BlockPos pos, BlockState state) { -+ Node node = removeNode(pos); -+ WireNode wire; -+ -+ if (node == null || !node.isWire()) { -+ wire = new WireNode(level, pos, state); -+ } else { -+ wire = node.asWire(); -+ } -+ -+ wire.invalid = true; -+ wire.removed = true; -+ -+ // If these fields are set to 'true', the removal of this wire was part of -+ // already ongoing power changes, so we can exit early here. -+ if (updating && wire.shouldBreak) { -+ return; -+ } -+ -+ invalidate(); -+ revalidateNode(wire); -+ findRoot(wire); -+ tryUpdate(); -+ } -+ -+ /** -+ * The nodes map is a snapshot of the state of the world. It becomes invalid -+ * when power changes are carried out, since the block and shape updates can -+ * lead to block changes. If these block changes cause the network to be updated -+ * again every node must be invalidated, and revalidated before it is used -+ * again. This ensures the power calculations of the network are accurate. -+ */ -+ private void invalidate() { -+ if (updating && !nodes.isEmpty()) { -+ Iterator> it = Long2ObjectMaps.fastIterator(nodes); -+ -+ while (it.hasNext()) { -+ Entry entry = it.next(); -+ Node node = entry.getValue(); -+ -+ node.invalid = true; -+ } -+ } -+ } -+ -+ /** -+ * Look for wires at and around the given position that are in an invalid state -+ * and require power changes. These wires are called 'roots' because it is only -+ * when these wires change power level that neighboring wires must adjust as -+ * well. -+ * -+ *

-+ * While it is strictly only necessary to check the wire at the given position, -+ * if that wire is part of a network, it is beneficial to check its surroundings -+ * for other wires that require power changes. This is because a network can -+ * receive power at multiple points. Consider the following setup: -+ * -+ *

-+ * (top-down view, W = wire, L = lever, _ = air/other) -+ *
{@code _ _ W _ _ } -+ *
{@code _ W W W _ } -+ *
{@code W W L W W } -+ *
{@code _ W W W _ } -+ *
{@code _ _ W _ _ } -+ * -+ *

-+ * The lever powers four wires in the network at once. If this is identified -+ * correctly, the entire network can (un)power at once. While it is not -+ * practical to cover every possible situation where a network is (un)powered -+ * from multiple points at once, checking for common cases like the one -+ * described above is relatively straight-forward. -+ */ -+ private void findRoots(BlockPos pos) { -+ Node node = getOrAddNode(pos); -+ -+ if (!node.isWire()) { -+ return; // we should never get here -+ } -+ -+ WireNode wire = node.asWire(); -+ findRoot(wire); -+ -+ // If the wire at the given position is not in an invalid state or is not -+ // part of a larger network, we can exit early. -+ if (!wire.searched || wire.connections.total == 0) { -+ return; -+ } -+ -+ for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) { -+ Node neighbor = getNeighbor(wire, iDir); -+ -+ if (neighbor.isConductor() || neighbor.isSignalSource()) { -+ findRootsAround(neighbor, Directions.iOpposite(iDir)); -+ } -+ } -+ } -+ -+ /** -+ * Look for wires around the given node that require power changes. -+ */ -+ private void findRootsAround(Node node, int except) { -+ for (int iDir : Directions.I_EXCEPT_CARDINAL[except]) { -+ Node neighbor = getNeighbor(node, iDir); -+ -+ if (neighbor.isWire()) { -+ findRoot(neighbor.asWire()); -+ } -+ } -+ } -+ -+ /** -+ * Check if the given wire requires power changes. If it does, queue it for the -+ * breadth-first search as a root. -+ */ -+ private void findRoot(WireNode wire) { -+ // Each wire only needs to be checked once. -+ if (wire.discovered) { -+ return; -+ } -+ -+ discover(wire); -+ findExternalPower(wire); -+ findPower(wire, false); -+ -+ if (needsUpdate(wire)) { -+ searchRoot(wire); -+ } -+ } -+ -+ /** -+ * Prepare the given wire for the breadth-first search. This means: -+ *
-+ * - Check if the wire should break. Rather than breaking the wire right away, -+ * its effects are integrated into the power calculations. -+ *
-+ * - Reset the virtual and external power. -+ *
-+ * - Find connections to neighboring wires. -+ */ -+ private void discover(WireNode wire) { -+ if (wire.discovered) { -+ return; -+ } -+ -+ wire.discovered = true; -+ wire.searched = false; -+ -+ if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(level, wire.pos)) { -+ wire.shouldBreak = true; -+ } -+ -+ wire.virtualPower = wire.currentPower; -+ wire.externalPower = POWER_MIN - 1; -+ -+ wire.connections.set(this::getNeighbor); -+ } -+ -+ /** -+ * Determine the power level the given wire receives from the blocks around it. -+ * Power from non-wire components only needs to be computed if power from -+ * neighboring wires has decreased, so as to determine how low the power of the -+ * wire can fall. -+ */ -+ private void findPower(WireNode wire, boolean ignoreSearched) { -+ // As wire power is (re-)computed, flow information must be reset. -+ wire.virtualPower = wire.externalPower; -+ wire.flowIn = 0; -+ -+ // If the wire is removed or going to break, its power level should always be -+ // the minimum value. This is because it (effectively) no longer exists, so -+ // cannot provide any power to neighboring wires. -+ if (wire.removed || wire.shouldBreak) { -+ return; -+ } -+ -+ // Power received from neighboring wires will never exceed POWER_MAX - -+ // POWER_STEP, so if the external power is already larger than or equal to -+ // that, there is no need to check for power from neighboring wires. -+ if (wire.externalPower < (POWER_MAX - POWER_STEP)) { -+ findWirePower(wire, ignoreSearched); -+ } -+ } -+ -+ /** -+ * Determine the power the given wire receives from connected neighboring wires -+ * and update the virtual power accordingly. -+ */ -+ private void findWirePower(WireNode wire, boolean ignoreSearched) { -+ wire.connections.forEach(connection -> { -+ if (!connection.accept) { -+ return; -+ } -+ -+ WireNode neighbor = connection.wire; -+ -+ if (!ignoreSearched || !neighbor.searched) { -+ int power = Math.max(POWER_MIN, neighbor.virtualPower - POWER_STEP); -+ int iOpp = Directions.iOpposite(connection.iDir); -+ -+ wire.offerPower(power, iOpp); -+ } -+ }); -+ } -+ -+ /** -+ * Determine the redstone signal the given wire receives from non-wire -+ * components and update the virtual power accordingly. -+ */ -+ private void findExternalPower(WireNode wire) { -+ // If the wire is removed or going to break, its power level should always be -+ // the minimum value. Thus external power need not be computed. -+ // In other cases external power need only be computed once. -+ if (wire.removed || wire.shouldBreak || wire.externalPower >= POWER_MIN) { -+ return; -+ } -+ -+ wire.externalPower = getExternalPower(wire); -+ -+ if (wire.externalPower > wire.virtualPower) { -+ wire.virtualPower = wire.externalPower; -+ } -+ } -+ -+ /** -+ * Determine the redstone signal the given wire receives from non-wire -+ * components. -+ */ -+ private int getExternalPower(WireNode wire) { -+ int power = POWER_MIN; -+ -+ for (int iDir = 0; iDir < Directions.ALL.length; iDir++) { -+ Node neighbor = getNeighbor(wire, iDir); -+ -+ // Power from wires is handled separately. -+ if (neighbor.isWire()) { -+ continue; -+ } -+ -+ // Since 1.16 there is a block that is both a conductor and a signal -+ // source: the target block! -+ if (neighbor.isConductor()) { -+ power = Math.max(power, getDirectSignalTo(wire, neighbor, Directions.iOpposite(iDir))); -+ } -+ if (neighbor.isSignalSource()) { -+ power = Math.max(power, neighbor.state.getSignal(level, neighbor.pos, Directions.ALL[iDir])); -+ } -+ -+ if (power >= POWER_MAX) { -+ return POWER_MAX; -+ } -+ } -+ -+ return power; -+ } -+ -+ /** -+ * Determine the direct signal the given wire receives from neighboring blocks -+ * through the given conductor node. -+ */ -+ private int getDirectSignalTo(WireNode wire, Node node, int except) { -+ int power = POWER_MIN; -+ -+ for (int iDir : Directions.I_EXCEPT[except]) { -+ Node neighbor = getNeighbor(node, iDir); -+ -+ if (neighbor.isSignalSource()) { -+ power = Math.max(power, neighbor.state.getDirectSignal(level, neighbor.pos, Directions.ALL[iDir])); -+ -+ if (power >= POWER_MAX) { -+ return POWER_MAX; -+ } -+ } -+ } -+ -+ return power; -+ } -+ -+ /** -+ * Check if the given wire needs to update its state in the world. -+ */ -+ private boolean needsUpdate(WireNode wire) { -+ return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower; -+ } -+ -+ /** -+ * Queue the given wire for the breadth-first search as a root. -+ */ -+ private void searchRoot(WireNode wire) { -+ int iBackupFlowDir; -+ -+ if (wire.connections.iFlowDir < 0) { -+ iBackupFlowDir = 0; -+ } else { -+ iBackupFlowDir = wire.connections.iFlowDir; -+ } -+ -+ search(wire, true, iBackupFlowDir); -+ } -+ -+ /** -+ * Queue the given wire for the breadth-first search and set a backup flow -+ * direction. -+ */ -+ private void search(WireNode wire, boolean root, int iBackupFlowDir) { -+ search.offer(wire); -+ -+ wire.root = root; -+ wire.searched = true; -+ // Normally the flow is not set until the power level is updated. However, -+ // in networks with multiple power sources the update order between them -+ // depends on which was discovered first. To make this less prone to -+ // directionality, each wire node is given a 'backup' flow. For roots, this -+ // is the determined flow of their connections. For non-roots this is the -+ // direction from which they were discovered. -+ wire.iFlowDir = iBackupFlowDir; -+ } -+ -+ private void tryUpdate() { -+ if (!search.isEmpty()) { -+ update(); -+ } -+ if (!updating) { -+ nodes.clear(); -+ nodeCount = 0; -+ } -+ } -+ -+ /** -+ * Update the network and neighboring blocks. This is done in 3 steps. -+ * -+ *

-+ * 1. Search through the network -+ *
-+ * Conduct a breadth-first search around the roots to find wires that are in an -+ * invalid state and need power changes. -+ * -+ *

-+ * 2. Depower the network -+ *
-+ * Depower all wires in the network. This allows power to be spread most -+ * efficiently. -+ * -+ *

-+ * 3. Power the network -+ *
-+ * Work through the update queue, setting the new power level of each wire and -+ * updating neighboring blocks. After a wire has updated its power level, it -+ * will emit shape updates and queue updates for neighboring wires and blocks. -+ */ -+ private void update() { -+ // Search through the network for wires that need power changes. This includes -+ // the roots as well as any wires that will be affected by power changes to -+ // those roots. -+ searchNetwork(); -+ -+ // Depower all the wires in the network. -+ depowerNetwork(); -+ -+ // Bring each wire up to its new power level and update neighboring blocks. -+ try { -+ powerNetwork(); -+ } catch (Throwable t) { -+ // If anything goes wrong while carrying out power changes, this field must -+ // be reset to 'false', or the wire handler will be locked out of carrying -+ // out power changes until the world is reloaded. -+ updating = false; -+ -+ throw t; -+ } -+ } -+ -+ /** -+ * Search through the network for wires that are in an invalid state and need -+ * power changes. These wires are added to the end of the queue, so that their -+ * neighbors can be searched next. -+ */ -+ private void searchNetwork() { -+ for (WireNode wire : search) { -+ // The order in which wires are searched will influence the order in -+ // which they update their power levels. -+ wire.connections.forEach(connection -> { -+ if (!connection.offer) { -+ return; -+ } -+ -+ WireNode neighbor = connection.wire; -+ -+ if (neighbor.searched) { -+ return; -+ } -+ -+ discover(neighbor); -+ findPower(neighbor, false); -+ -+ // If power from neighboring wires has decreased, check for power -+ // from non-wire components so as to determine how low power can -+ // fall. -+ if (neighbor.virtualPower < neighbor.currentPower) { -+ findExternalPower(neighbor); -+ } -+ -+ if (needsUpdate(neighbor)) { -+ search(neighbor, false, connection.iDir); -+ } -+ }, wire.iFlowDir); -+ } -+ } -+ -+ /** -+ * Depower all wires in the network so that power can be spread from the power -+ * sources. -+ */ -+ private void depowerNetwork() { -+ while (!search.isEmpty()) { -+ WireNode wire = search.poll(); -+ findPower(wire, true); -+ -+ if (wire.root || wire.removed || wire.shouldBreak || wire.virtualPower > POWER_MIN) { -+ queueWire(wire); -+ } else { -+ // Wires that do not receive any power do not queue power changes -+ // until they are offered power from a neighboring wire. To ensure -+ // that they accept any power from neighboring wires and thus queue -+ // their power changes, their virtual power is set to below the -+ // minimum. -+ wire.virtualPower--; -+ } -+ } -+ } -+ -+ /** -+ * Work through the update queue, setting the new power level of each wire, then -+ * queueing updates to connected wires and neighboring blocks. -+ */ -+ private void powerNetwork() { -+ // If an instantaneous update chain causes updates to another network -+ // (or the same network in another place), new power changes will be -+ // integrated into the already ongoing power queue, so we can exit early -+ // here. -+ if (updating) { -+ return; -+ } -+ -+ updating = true; -+ -+ while (!updates.isEmpty()) { -+ Node node = updates.poll(); -+ -+ if (node.isWire()) { -+ WireNode wire = node.asWire(); -+ -+ if (!needsUpdate(wire)) { -+ continue; -+ } -+ -+ findPowerFlow(wire); -+ transmitPower(wire); -+ -+ if (wire.setPower()) { -+ queueNeighbors(wire); -+ -+ // If the wire was newly placed or removed, shape updates have -+ // already been emitted. However, unlike before 1.19, neighbor -+ // updates are now queued, so to preserve behavior parity with -+ // previous versions, we emit extra shape updates here to -+ // notify neighboring observers. -+ updateNeighborShapes(wire); -+ } -+ } else { -+ WireNode neighborWire = node.neighborWire; -+ -+ if (neighborWire != null) { -+ BlockPos neighborPos = neighborWire.pos; -+ Block neighborBlock = neighborWire.state.getBlock(); -+ -+ updateBlock(node, neighborPos, neighborBlock); -+ } -+ } -+ } -+ -+ updating = false; -+ } -+ -+ /** -+ * Use the information of incoming power flow to determine the direction of -+ * power flow through this wire. If that flow is ambiguous, try to use a flow -+ * direction based on connections to neighboring wires. If that is also -+ * ambiguous, use the backup value that was set when the wire was first added to -+ * the network. -+ */ -+ private void findPowerFlow(WireNode wire) { -+ int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn]; -+ -+ if (flow >= 0) { -+ wire.iFlowDir = flow; -+ } else if (wire.connections.iFlowDir >= 0) { -+ wire.iFlowDir = wire.connections.iFlowDir; -+ } -+ } -+ -+ /** -+ * Transmit power from the given wire to neighboring wires and queue updates to -+ * those wires. -+ */ -+ private void transmitPower(WireNode wire) { -+ wire.connections.forEach(connection -> { -+ if (!connection.offer) { -+ return; -+ } -+ -+ WireNode neighbor = connection.wire; -+ -+ int power = Math.max(POWER_MIN, wire.virtualPower - POWER_STEP); -+ int iDir = connection.iDir; -+ -+ if (neighbor.offerPower(power, iDir)) { -+ queueWire(neighbor); -+ } -+ }, wire.iFlowDir); -+ } -+ -+ /** -+ * Emit shape updates around the given wire. -+ */ -+ private void updateNeighborShapes(WireNode wire) { -+ BlockPos wirePos = wire.pos; -+ BlockState wireState = wire.state; -+ -+ for (int iDir : DEFAULT_FULL_UPDATE_ORDER) { -+ Node neighbor = getNeighbor(wire, iDir); -+ -+ if (!neighbor.isWire()) { -+ int iOpp = Directions.iOpposite(iDir); -+ Direction opp = Directions.ALL[iOpp]; -+ -+ updateShape(neighbor, opp, wirePos, wireState); -+ } -+ } -+ } -+ -+ private void updateShape(Node node, Direction dir, BlockPos neighborPos, BlockState neighborState) { -+ BlockPos pos = node.pos; -+ BlockState state = level.getBlockState(pos); -+ -+ // Shape updates to redstone wire are very expensive, and should never happen -+ // as a result of power changes anyway. -+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { -+ BlockState newState = state.updateShape(dir, neighborState, level, pos, neighborPos); -+ Block.updateOrDestroy(state, newState, level, pos, Block.UPDATE_CLIENTS); -+ } -+ } -+ -+ /** -+ * Queue block updates to nodes around the given wire. -+ */ -+ private void queueNeighbors(WireNode wire) { -+ forEachNeighbor(wire, neighbor -> { -+ queueNeighbor(neighbor, wire); -+ }); -+ } -+ -+ /** -+ * Queue the given node for an update from the given neighboring wire. -+ */ -+ private void queueNeighbor(Node node, WireNode neighborWire) { -+ // Updates to wires are queued when power is transmitted. -+ if (!node.isWire()) { -+ node.neighborWire = neighborWire; -+ updates.offer(node); -+ } -+ } -+ -+ /** -+ * Queue the given wire for a power change. If the wire does not need a power -+ * change (perhaps because its power has already changed), transmit power to -+ * neighboring wires. -+ */ -+ private void queueWire(WireNode wire) { -+ if (needsUpdate(wire)) { -+ updates.offer(wire); -+ } else { -+ findPowerFlow(wire); -+ transmitPower(wire); -+ } -+ } -+ -+ /** -+ * Emit a block update to the given node. -+ */ -+ private void updateBlock(Node node, BlockPos neighborPos, Block neighborBlock) { -+ BlockPos pos = node.pos; -+ BlockState state = level.getBlockState(pos); -+ -+ // While this check makes sure wires in the network are not given block -+ // updates, it also prevents block updates to wires in neighboring networks. -+ // While this should not make a difference in theory, in practice, it is -+ // possible to force a network into an invalid state without updating it, even -+ // if it is relatively obscure. -+ // While I was willing to make this compromise in return for some significant -+ // performance gains in certain setups, if you are not, you can add all the -+ // positions of the network to a set and filter out block updates to wires in -+ // the network that way. -+ if (!state.isAir() && !state.is(Blocks.REDSTONE_WIRE)) { -+ state.neighborChanged(level, pos, neighborBlock, neighborPos, false); -+ } -+ } -+ -+ @FunctionalInterface -+ public static interface NodeProvider { -+ -+ public Node getNeighbor(Node node, int iDir); -+ -+ } -+} -diff --git a/src/main/java/alternate/current/wire/WireNode.java b/src/main/java/alternate/current/wire/WireNode.java -new file mode 100644 -index 0000000000000000000000000000000000000000..33cd90c30c22200a4e1ae64f40a0bf7864546b33 ---- /dev/null -+++ b/src/main/java/alternate/current/wire/WireNode.java -@@ -0,0 +1,122 @@ -+package alternate.current.wire; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.RedStoneWireBlock; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.redstone.Redstone; -+ -+/** -+ * A WireNode is a Node that represents a wire in the world. It stores all the -+ * information about the wire that the WireHandler needs to calculate power -+ * changes. -+ * -+ * @author Space Walker -+ */ -+public class WireNode extends Node { -+ -+ final WireConnectionManager connections; -+ -+ /** The power level this wire currently holds in the world. */ -+ int currentPower; -+ /** -+ * While calculating power changes for a network, this field is used to keep -+ * track of the power level this wire should have. -+ */ -+ int virtualPower; -+ /** The power level received from non-wire components. */ -+ int externalPower; -+ /** -+ * A 4-bit number that keeps track of the power flow of the wires that give this -+ * wire its power level. -+ */ -+ int flowIn; -+ /** The direction of power flow, based on the incoming flow. */ -+ int iFlowDir; -+ boolean added; -+ boolean removed; -+ boolean shouldBreak; -+ boolean root; -+ boolean discovered; -+ boolean searched; -+ -+ /** The next wire in the simple queue. */ -+ WireNode next_wire; -+ -+ WireNode(ServerLevel level, BlockPos pos, BlockState state) { -+ super(level); -+ -+ this.pos = pos.immutable(); -+ this.state = state; -+ -+ this.connections = new WireConnectionManager(this); -+ -+ this.virtualPower = this.currentPower = this.state.getValue(RedStoneWireBlock.POWER); -+ this.priority = priority(); -+ } -+ -+ @Override -+ Node set(BlockPos pos, BlockState state, boolean clearNeighbors) { -+ throw new UnsupportedOperationException("Cannot update a WireNode!"); -+ } -+ -+ @Override -+ int priority() { -+ return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX); -+ } -+ -+ @Override -+ public boolean isWire() { -+ return true; -+ } -+ -+ @Override -+ public WireNode asWire() { -+ return this; -+ } -+ -+ boolean offerPower(int power, int iDir) { -+ if (removed || shouldBreak) { -+ return false; -+ } -+ if (power == virtualPower) { -+ flowIn |= (1 << iDir); -+ return false; -+ } -+ if (power > virtualPower) { -+ virtualPower = power; -+ flowIn = (1 << iDir); -+ -+ return true; -+ } -+ -+ return false; -+ } -+ -+ boolean setPower() { -+ if (removed) { -+ return true; -+ } -+ -+ state = level.getBlockState(pos); -+ -+ if (!state.is(Blocks.REDSTONE_WIRE)) { -+ return false; // we should never get here -+ } -+ -+ if (shouldBreak) { -+ Block.dropResources(state, level, pos); -+ level.setBlock(pos, Blocks.AIR.defaultBlockState(), Block.UPDATE_CLIENTS); -+ -+ return true; -+ } -+ -+ currentPower = LevelHelper.doRedstoneEvent(level, pos, currentPower, Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX)); -+ state = state.setValue(RedStoneWireBlock.POWER, currentPower); -+ -+ return LevelHelper.setWireState(level, pos, state, added); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e5a64e70020487b15825a865623afa45b0ae59d4..684063b8433f78ef0adf0de1057ea24720b32181 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -219,6 +219,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final UUID uuid; - public boolean hasPhysicsEvent = true; // Paper - public boolean hasEntityMoveEvent = false; // Paper -+ private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) - public static Throwable getAddToWorldStackTrace(Entity entity) { - return new Throwable(entity + " Added to world at " + new java.util.Date()); - } -@@ -2484,6 +2485,13 @@ public class ServerLevel extends Level implements WorldGenLevel { - return this.entityManager.canPositionTick(pos.toLong()); // Paper - } - -+ // Paper start - optimize redstone (Alternate Current) -+ @Override -+ public alternate.current.wire.WireHandler getWireHandler() { -+ return wireHandler; -+ } -+ // Paper end -+ - private final class EntityCallbacks implements LevelCallback { - - EntityCallbacks() {} -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 30140ae5a74a511c9031b8e772e724b25e56de3d..46c45f249e71ca94044f1260a23cd7331098fb2c 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1453,4 +1453,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return ret; - } - // Paper end -+ -+ // Paper start - optimize redstone (Alternate Current) -+ public alternate.current.wire.WireHandler getWireHandler() { -+ // This method is overridden in ServerLevel. -+ // Since Paper is a server platform there is no risk -+ // of this implementation being called. It is here -+ // only so this method can be called without casting -+ // an instance of Level to ServerLevel. -+ return null; -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -index 3cba4921daad4b346a3f964f0fa48e1bb4d634a3..2bc21e3373f6fc6fbbaa7202ba82e7da86045b6a 100644 ---- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -253,7 +253,7 @@ public class RedStoneWireBlock extends Block { - return floor.isFaceSturdy(world, pos, Direction.UP) || floor.is(Blocks.HOPPER); - } - -- // Paper start - Optimize redstone -+ // Paper start - Optimize redstone (Eigencraft) - // The bulk of the new functionality is found in RedstoneWireTurbo.java - com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); - -@@ -455,7 +455,13 @@ public class RedStoneWireBlock extends Block { - @Override - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (!oldState.is(state.getBlock()) && !world.isClientSide) { -- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone -+ // Paper start - optimize redstone - replace call to updatePowerStrength -+ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { -+ world.getWireHandler().onWireAdded(pos); // Alternate Current -+ } else { -+ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft -+ } -+ // Paper end - Iterator iterator = Direction.Plane.VERTICAL.iterator(); - - while (iterator.hasNext()) { -@@ -482,7 +488,13 @@ public class RedStoneWireBlock extends Block { - world.updateNeighborsAt(pos.relative(enumdirection), this); - } - -- this.updateSurroundingRedstone(world, pos, state, null); // Paper - Optimize redstone -+ // Paper start - optimize redstone - replace call to updatePowerStrength -+ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { -+ world.getWireHandler().onWireRemoved(pos, state); // Alternate Current -+ } else { -+ this.updateSurroundingRedstone(world, pos, state, null); // vanilla/Eigencraft -+ } -+ // Paper end - this.updateNeighborsOfNeighboringWires(world, pos); - } - } -@@ -516,8 +528,14 @@ public class RedStoneWireBlock extends Block { - @Override - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - if (!world.isClientSide) { -+ // Paper start - optimize redstone (Alternate Current) -+ // Alternate Current handles breaking of redstone wires in the WireHandler. -+ if (world.paperConfig().misc.redstoneImplementation == io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { -+ world.getWireHandler().onWireUpdated(pos); -+ } else -+ // Paper end - if (state.canSurvive(world, pos)) { -- this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone -+ this.updateSurroundingRedstone(world, pos, state, sourcePos); // Paper - Optimize redstone (Eigencraft) - } else { - dropResources(state, world, pos); - world.removeBlock(pos, false); diff --git a/patches/server/0901-Dont-resent-entity-on-art-update.patch b/patches/server/0901-Dont-resent-entity-on-art-update.patch new file mode 100644 index 0000000000..ff4ac47ad6 --- /dev/null +++ b/patches/server/0901-Dont-resent-entity-on-art-update.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 8 Jun 2022 11:04:47 -0400 +Subject: [PATCH] Dont resent entity on art update + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +index 1d54465f1835f5e419899a7585d3dec920e1a73b..b7610e880e857058b621228583c841b5d9338fc7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java +@@ -38,7 +38,7 @@ public class CraftPainting extends CraftHanging implements Painting { + painting.setDirection(painting.getDirection()); + return false; + } +- this.update(); ++ //this.update(); Paper - Don't resent entity on art update + return true; + } + diff --git a/patches/server/0902-Add-missing-spawn-eggs.patch b/patches/server/0902-Add-missing-spawn-eggs.patch new file mode 100644 index 0000000000..ba3ec133cc --- /dev/null +++ b/patches/server/0902-Add-missing-spawn-eggs.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 8 Jun 2022 15:58:20 -0400 +Subject: [PATCH] Add missing spawn eggs + + +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +index ce64286ac5b836283318ac1ac0bd4afb29db9bb7..09b6475b77ebc7f43c13861aa2af26e2f6e6a8b5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +@@ -196,6 +196,12 @@ public final class CraftItemFactory implements ItemFactory { + case ZOMBIE_SPAWN_EGG: + case ZOMBIE_VILLAGER_SPAWN_EGG: + case ZOMBIFIED_PIGLIN_SPAWN_EGG: ++ // Paper start ++ case ALLAY_SPAWN_EGG: ++ case FROG_SPAWN_EGG: ++ case TADPOLE_SPAWN_EGG: ++ case WARDEN_SPAWN_EGG: ++ // Paper end + return meta instanceof CraftMetaSpawnEgg ? meta : new CraftMetaSpawnEgg(meta); + case ARMOR_STAND: + return meta instanceof CraftMetaArmorStand ? meta : new CraftMetaArmorStand(meta); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +index c7c5f18cde7a4ad4dd821e452de3068c2e2187d1..f5363a753e5d24ccda946a9d65132eed28f19172 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +@@ -424,6 +424,12 @@ public final class CraftItemStack extends ItemStack { + case ZOMBIE_SPAWN_EGG: + case ZOMBIE_VILLAGER_SPAWN_EGG: + case ZOMBIFIED_PIGLIN_SPAWN_EGG: ++ // Paper start ++ case ALLAY_SPAWN_EGG: ++ case FROG_SPAWN_EGG: ++ case TADPOLE_SPAWN_EGG: ++ case WARDEN_SPAWN_EGG: ++ // Paper end + return new CraftMetaSpawnEgg(item.getTag()); + case ARMOR_STAND: + return new CraftMetaArmorStand(item.getTag()); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +index 155e2b50df754021acdd29a870342db8ad6d1f41..de141f94214838dc049c1797bbdb0dbdac048112 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java +@@ -18,6 +18,12 @@ import org.bukkit.material.MaterialData; + public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { + + private static final Set SPAWN_EGG_MATERIALS = Sets.newHashSet( ++ // Paper start ++ Material. ALLAY_SPAWN_EGG, ++ Material. FROG_SPAWN_EGG, ++ Material. TADPOLE_SPAWN_EGG, ++ Material. WARDEN_SPAWN_EGG, ++ // Paper end + Material.AXOLOTL_SPAWN_EGG, + Material.BAT_SPAWN_EGG, + Material.BEE_SPAWN_EGG, diff --git a/patches/server/0902-Dont-resent-entity-on-art-update.patch b/patches/server/0902-Dont-resent-entity-on-art-update.patch deleted file mode 100644 index ff4ac47ad6..0000000000 --- a/patches/server/0902-Dont-resent-entity-on-art-update.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 8 Jun 2022 11:04:47 -0400 -Subject: [PATCH] Dont resent entity on art update - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -index 1d54465f1835f5e419899a7585d3dec920e1a73b..b7610e880e857058b621228583c841b5d9338fc7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPainting.java -@@ -38,7 +38,7 @@ public class CraftPainting extends CraftHanging implements Painting { - painting.setDirection(painting.getDirection()); - return false; - } -- this.update(); -+ //this.update(); Paper - Don't resent entity on art update - return true; - } - diff --git a/patches/server/0903-Add-WardenAngerChangeEvent.patch b/patches/server/0903-Add-WardenAngerChangeEvent.patch new file mode 100644 index 0000000000..cfe1a98c72 --- /dev/null +++ b/patches/server/0903-Add-WardenAngerChangeEvent.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: nopjar +Date: Sun, 12 Jun 2022 02:26:04 +0200 +Subject: [PATCH] Add WardenAngerChangeEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java +index e9b4f29e1844f7c44e341f9b1c07c676469ca3b6..d76800a79faef26aab0cf99b28dfa4621877ecc7 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java ++++ b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java +@@ -145,7 +145,7 @@ public class AngerManagement { + public int increaseAnger(Entity entity, int amount) { + boolean bl = !this.angerBySuspect.containsKey(entity); + int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> { +- return Math.min(150, (anger == null ? 0 : anger) + amount); ++ return Math.min(150, (anger == null ? 0 : anger) + amount); // Paper - diff on change + }); + if (bl) { + int j = this.angerByUuid.removeInt(entity.getUUID()); +diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +index 42ee13841319ef92bacfeffb2f8881e42b801695..27bd70dc30c8472e5a80f3273f9233a0392f831d 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +@@ -495,6 +495,15 @@ public class Warden extends Monster implements VibrationListener.VibrationListen + @VisibleForTesting + public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) { + if (!this.isNoAi() && this.canTargetEntity(entity)) { ++ // Paper start ++ int activeAnger = this.angerManagement.getActiveAnger(entity); ++ io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount)); ++ this.level.getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ amount = event.getNewAnger() - activeAnger; ++ // Paper end + WardenAi.setDigCooldown(this); + boolean flag1 = !(this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null) instanceof Player); // CraftBukkit - decompile error + int j = this.angerManagement.increaseAnger(entity, amount); diff --git a/patches/server/0903-Add-missing-spawn-eggs.patch b/patches/server/0903-Add-missing-spawn-eggs.patch deleted file mode 100644 index 5be733c857..0000000000 --- a/patches/server/0903-Add-missing-spawn-eggs.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 8 Jun 2022 15:58:20 -0400 -Subject: [PATCH] Add missing spawn eggs - - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index ce64286ac5b836283318ac1ac0bd4afb29db9bb7..09b6475b77ebc7f43c13861aa2af26e2f6e6a8b5 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -196,6 +196,12 @@ public final class CraftItemFactory implements ItemFactory { - case ZOMBIE_SPAWN_EGG: - case ZOMBIE_VILLAGER_SPAWN_EGG: - case ZOMBIFIED_PIGLIN_SPAWN_EGG: -+ // Paper start -+ case ALLAY_SPAWN_EGG: -+ case FROG_SPAWN_EGG: -+ case TADPOLE_SPAWN_EGG: -+ case WARDEN_SPAWN_EGG: -+ // Paper end - return meta instanceof CraftMetaSpawnEgg ? meta : new CraftMetaSpawnEgg(meta); - case ARMOR_STAND: - return meta instanceof CraftMetaArmorStand ? meta : new CraftMetaArmorStand(meta); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index c7c5f18cde7a4ad4dd821e452de3068c2e2187d1..f5363a753e5d24ccda946a9d65132eed28f19172 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -424,6 +424,12 @@ public final class CraftItemStack extends ItemStack { - case ZOMBIE_SPAWN_EGG: - case ZOMBIE_VILLAGER_SPAWN_EGG: - case ZOMBIFIED_PIGLIN_SPAWN_EGG: -+ // Paper start -+ case ALLAY_SPAWN_EGG: -+ case FROG_SPAWN_EGG: -+ case TADPOLE_SPAWN_EGG: -+ case WARDEN_SPAWN_EGG: -+ // Paper end - return new CraftMetaSpawnEgg(item.getTag()); - case ARMOR_STAND: - return new CraftMetaArmorStand(item.getTag()); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java -index 34eb61547d727ffa8c580110cdc5a0f34562f073..f7f3b7cdb92e4852c7745125e3ac7d43512942d0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSpawnEgg.java -@@ -179,6 +179,12 @@ public class CraftMetaSpawnEgg extends CraftMetaItem implements SpawnEggMeta { - case ZOMBIE_SPAWN_EGG: - case ZOMBIE_VILLAGER_SPAWN_EGG: - case ZOMBIFIED_PIGLIN_SPAWN_EGG: -+ // Paper start -+ case ALLAY_SPAWN_EGG: -+ case FROG_SPAWN_EGG: -+ case TADPOLE_SPAWN_EGG: -+ case WARDEN_SPAWN_EGG: -+ // Paper end - return true; - default: - return false; diff --git a/patches/server/0904-Add-WardenAngerChangeEvent.patch b/patches/server/0904-Add-WardenAngerChangeEvent.patch deleted file mode 100644 index cfe1a98c72..0000000000 --- a/patches/server/0904-Add-WardenAngerChangeEvent.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: nopjar -Date: Sun, 12 Jun 2022 02:26:04 +0200 -Subject: [PATCH] Add WardenAngerChangeEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -index e9b4f29e1844f7c44e341f9b1c07c676469ca3b6..d76800a79faef26aab0cf99b28dfa4621877ecc7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -+++ b/src/main/java/net/minecraft/world/entity/monster/warden/AngerManagement.java -@@ -145,7 +145,7 @@ public class AngerManagement { - public int increaseAnger(Entity entity, int amount) { - boolean bl = !this.angerBySuspect.containsKey(entity); - int i = this.angerBySuspect.computeInt(entity, (suspect, anger) -> { -- return Math.min(150, (anger == null ? 0 : anger) + amount); -+ return Math.min(150, (anger == null ? 0 : anger) + amount); // Paper - diff on change - }); - if (bl) { - int j = this.angerByUuid.removeInt(entity.getUUID()); -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -index 42ee13841319ef92bacfeffb2f8881e42b801695..27bd70dc30c8472e5a80f3273f9233a0392f831d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -@@ -495,6 +495,15 @@ public class Warden extends Monster implements VibrationListener.VibrationListen - @VisibleForTesting - public void increaseAngerAt(@Nullable Entity entity, int amount, boolean listening) { - if (!this.isNoAi() && this.canTargetEntity(entity)) { -+ // Paper start -+ int activeAnger = this.angerManagement.getActiveAnger(entity); -+ io.papermc.paper.event.entity.WardenAngerChangeEvent event = new io.papermc.paper.event.entity.WardenAngerChangeEvent((org.bukkit.entity.Warden) this.getBukkitEntity(), entity.getBukkitEntity(), activeAnger, Math.min(150, activeAnger + amount)); -+ this.level.getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) { -+ return; -+ } -+ amount = event.getNewAnger() - activeAnger; -+ // Paper end - WardenAi.setDigCooldown(this); - boolean flag1 = !(this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null) instanceof Player); // CraftBukkit - decompile error - int j = this.angerManagement.increaseAnger(entity, amount); diff --git a/patches/server/0904-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0904-Add-option-for-strict-advancement-dimension-checks.patch new file mode 100644 index 0000000000..3aaa6591bd --- /dev/null +++ b/patches/server/0904-Add-option-for-strict-advancement-dimension-checks.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 12 Jun 2022 11:47:24 -0700 +Subject: [PATCH] Add option for strict advancement dimension checks + +Craftbukkit attempts to translate worlds that use the +same generation as the Overworld, The Nether, or The End +to use those dimensions when checking the `changed_dimension` +criteria trigger, or whether to trigger the `NETHER_TRAVEL` +distance trigger. This adds a config option to ignore that +and use the exact dimension key of the worlds involved. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 3f3ebe28c669419091fd20c18185c61712e7f1e8..3615576c24d5d6790a6894a91180de25fa0e5a9c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -1251,6 +1251,12 @@ public class ServerPlayer extends Player { + // CraftBukkit start + ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); + ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level); ++ // Paper start - config for strict advancement checks for dimensions ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { ++ maindimensionkey = resourcekey; ++ maindimensionkey1 = resourcekey1; ++ } ++ // Paper end + + CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); + if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { diff --git a/patches/server/0905-Add-missing-important-BlockStateListPopulator-method.patch b/patches/server/0905-Add-missing-important-BlockStateListPopulator-method.patch new file mode 100644 index 0000000000..2296cce864 --- /dev/null +++ b/patches/server/0905-Add-missing-important-BlockStateListPopulator-method.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 12 Jun 2022 13:25:52 -0400 +Subject: [PATCH] Add missing important BlockStateListPopulator methods + +Without these methods it causes exceptions due to these being used by certain feature generators. + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +index 8e6a71c1e8b53faa70b893c76f5bd25f96a5e142..19abf7b6000a875be8c7141cfba81b279b2cae60 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +@@ -129,4 +129,32 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { + public DimensionType dimensionType() { + return this.world.dimensionType(); + } ++ // Paper start ++ @Override ++ public boolean isFluidAtPosition(BlockPos pos, Predicate state) { ++ return state.test(this.getFluidState(pos)); ++ } ++ ++ @Override ++ public java.util.Optional getBlockEntity(BlockPos pos, net.minecraft.world.level.block.entity.BlockEntityType type) { ++ BlockEntity tileentity = this.getBlockEntity(pos); ++ ++ return tileentity != null && tileentity.getType() == type ? (java.util.Optional) java.util.Optional.of(tileentity) : java.util.Optional.empty(); ++ } ++ ++ @Override ++ public BlockPos getHeightmapPos(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, BlockPos pos) { ++ return world.getHeightmapPos(heightmap, pos); ++ } ++ ++ @Override ++ public int getHeight(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, int x, int z) { ++ return world.getHeight(heightmap, x, z); ++ } ++ ++ @Override ++ public net.minecraft.world.level.storage.LevelData getLevelData() { ++ return world.getLevelData(); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +index fbd82b6be6604bf854e01ed5718e4e072f42b265..cd0dc080fbd8c5b1509d67e2b60264393b2b7dbb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java +@@ -269,5 +269,17 @@ public class DummyGeneratorAccess implements WorldGenLevel { + + @Override + public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} ++ ++ @Override ++ public void scheduleTick(BlockPos pos, Fluid fluid, int delay) { ++ } ++ ++ @Override ++ public void scheduleTick(BlockPos pos, Block block, int delay, net.minecraft.world.ticks.TickPriority priority) { ++ } ++ ++ @Override ++ public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) { ++ } + // Paper end + } diff --git a/patches/server/0905-Add-option-for-strict-advancement-dimension-checks.patch b/patches/server/0905-Add-option-for-strict-advancement-dimension-checks.patch deleted file mode 100644 index 3aaa6591bd..0000000000 --- a/patches/server/0905-Add-option-for-strict-advancement-dimension-checks.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 12 Jun 2022 11:47:24 -0700 -Subject: [PATCH] Add option for strict advancement dimension checks - -Craftbukkit attempts to translate worlds that use the -same generation as the Overworld, The Nether, or The End -to use those dimensions when checking the `changed_dimension` -criteria trigger, or whether to trigger the `NETHER_TRAVEL` -distance trigger. This adds a config option to ignore that -and use the exact dimension key of the worlds involved. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 3f3ebe28c669419091fd20c18185c61712e7f1e8..3615576c24d5d6790a6894a91180de25fa0e5a9c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1251,6 +1251,12 @@ public class ServerPlayer extends Player { - // CraftBukkit start - ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); - ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level); -+ // Paper start - config for strict advancement checks for dimensions -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { -+ maindimensionkey = resourcekey; -+ maindimensionkey1 = resourcekey1; -+ } -+ // Paper end - - CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); - if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { diff --git a/patches/server/0906-Add-missing-important-BlockStateListPopulator-method.patch b/patches/server/0906-Add-missing-important-BlockStateListPopulator-method.patch deleted file mode 100644 index 2296cce864..0000000000 --- a/patches/server/0906-Add-missing-important-BlockStateListPopulator-method.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 12 Jun 2022 13:25:52 -0400 -Subject: [PATCH] Add missing important BlockStateListPopulator methods - -Without these methods it causes exceptions due to these being used by certain feature generators. - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -index 8e6a71c1e8b53faa70b893c76f5bd25f96a5e142..19abf7b6000a875be8c7141cfba81b279b2cae60 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java -@@ -129,4 +129,32 @@ public class BlockStateListPopulator extends DummyGeneratorAccess { - public DimensionType dimensionType() { - return this.world.dimensionType(); - } -+ // Paper start -+ @Override -+ public boolean isFluidAtPosition(BlockPos pos, Predicate state) { -+ return state.test(this.getFluidState(pos)); -+ } -+ -+ @Override -+ public java.util.Optional getBlockEntity(BlockPos pos, net.minecraft.world.level.block.entity.BlockEntityType type) { -+ BlockEntity tileentity = this.getBlockEntity(pos); -+ -+ return tileentity != null && tileentity.getType() == type ? (java.util.Optional) java.util.Optional.of(tileentity) : java.util.Optional.empty(); -+ } -+ -+ @Override -+ public BlockPos getHeightmapPos(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, BlockPos pos) { -+ return world.getHeightmapPos(heightmap, pos); -+ } -+ -+ @Override -+ public int getHeight(net.minecraft.world.level.levelgen.Heightmap.Types heightmap, int x, int z) { -+ return world.getHeight(heightmap, x, z); -+ } -+ -+ @Override -+ public net.minecraft.world.level.storage.LevelData getLevelData() { -+ return world.getLevelData(); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index fbd82b6be6604bf854e01ed5718e4e072f42b265..cd0dc080fbd8c5b1509d67e2b60264393b2b7dbb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -269,5 +269,17 @@ public class DummyGeneratorAccess implements WorldGenLevel { - - @Override - public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ -+ @Override -+ public void scheduleTick(BlockPos pos, Fluid fluid, int delay) { -+ } -+ -+ @Override -+ public void scheduleTick(BlockPos pos, Block block, int delay, net.minecraft.world.ticks.TickPriority priority) { -+ } -+ -+ @Override -+ public void scheduleTick(BlockPos pos, Fluid fluid, int delay, net.minecraft.world.ticks.TickPriority priority) { -+ } - // Paper end - } diff --git a/patches/server/0906-Nameable-Banner-API.patch b/patches/server/0906-Nameable-Banner-API.patch new file mode 100644 index 0000000000..78f176be32 --- /dev/null +++ b/patches/server/0906-Nameable-Banner-API.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 7 Apr 2022 17:49:25 -0400 +Subject: [PATCH] Nameable Banner API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +index 85f7ed3041befcc37729e9cd25723644600c7f62..31d916bc2364d0c518652b5b5868ab3d45a77ccc 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java +@@ -99,4 +99,25 @@ public class CraftBanner extends CraftBlockEntityState implem + } + banner.itemPatterns = newPatterns; + } ++ // Paper start ++ @Override ++ public net.kyori.adventure.text.Component customName() { ++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getSnapshot().getCustomName()); ++ } ++ ++ @Override ++ public void customName(net.kyori.adventure.text.Component customName) { ++ this.getSnapshot().setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(customName)); ++ } ++ ++ @Override ++ public String getCustomName() { ++ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serializeOrNull(this.customName()); ++ } ++ ++ @Override ++ public void setCustomName(String name) { ++ this.customName(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOrNull(name)); ++ } ++ // Paper end + } diff --git a/patches/server/0907-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0907-Don-t-broadcast-messages-to-command-blocks.patch new file mode 100644 index 0000000000..7ce8867778 --- /dev/null +++ b/patches/server/0907-Don-t-broadcast-messages-to-command-blocks.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 16 Jun 2022 14:22:56 -0700 +Subject: [PATCH] Don't broadcast messages to command blocks + +Previously the broadcast method would update the last output +in command blocks, and if called asynchronously, would throw +an error + +diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +index c0195f73cd2c8721e882c681eaead65471710081..861b348f73867af3199f1cc0dab1ddd4241d1567 100644 +--- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java ++++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -172,6 +172,7 @@ public abstract class BaseCommandBlock implements CommandSource { + @Override + public void sendSystemMessage(Component message) { + if (this.trackOutput) { ++ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper + SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; + Date date = new Date(); + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 61b9d44e4edc4cb6ffbd8bf99e8e098bcc9957b3..3c9ba17cd0f016a21f56c6b6f8861c512734637a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1758,7 +1758,7 @@ public final class CraftServer implements Server { + // Paper end + Set recipients = new HashSet<>(); + for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { +- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { ++ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - don't broadcast to BlockCommandSender (specifically Command Blocks) + recipients.add((CommandSender) permissible); + } + } diff --git a/patches/server/0907-Nameable-Banner-API.patch b/patches/server/0907-Nameable-Banner-API.patch deleted file mode 100644 index 78f176be32..0000000000 --- a/patches/server/0907-Nameable-Banner-API.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 7 Apr 2022 17:49:25 -0400 -Subject: [PATCH] Nameable Banner API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -index 85f7ed3041befcc37729e9cd25723644600c7f62..31d916bc2364d0c518652b5b5868ab3d45a77ccc 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBanner.java -@@ -99,4 +99,25 @@ public class CraftBanner extends CraftBlockEntityState implem - } - banner.itemPatterns = newPatterns; - } -+ // Paper start -+ @Override -+ public net.kyori.adventure.text.Component customName() { -+ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getSnapshot().getCustomName()); -+ } -+ -+ @Override -+ public void customName(net.kyori.adventure.text.Component customName) { -+ this.getSnapshot().setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(customName)); -+ } -+ -+ @Override -+ public String getCustomName() { -+ return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serializeOrNull(this.customName()); -+ } -+ -+ @Override -+ public void setCustomName(String name) { -+ this.customName(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserializeOrNull(name)); -+ } -+ // Paper end - } diff --git a/patches/server/0908-Don-t-broadcast-messages-to-command-blocks.patch b/patches/server/0908-Don-t-broadcast-messages-to-command-blocks.patch deleted file mode 100644 index c9e7cd98d6..0000000000 --- a/patches/server/0908-Don-t-broadcast-messages-to-command-blocks.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 16 Jun 2022 14:22:56 -0700 -Subject: [PATCH] Don't broadcast messages to command blocks - -Previously the broadcast method would update the last output -in command blocks, and if called asynchronously, would throw -an error - -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index c0195f73cd2c8721e882c681eaead65471710081..861b348f73867af3199f1cc0dab1ddd4241d1567 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -172,6 +172,7 @@ public abstract class BaseCommandBlock implements CommandSource { - @Override - public void sendSystemMessage(Component message) { - if (this.trackOutput) { -+ org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; - Date date = new Date(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 051899f6f6bb656a836045ee36e6a2afe83f34f6..32eb6efa94bc5ea3b516cbbd7e53f1460c23154d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1758,7 +1758,7 @@ public final class CraftServer implements Server { - // Paper end - Set recipients = new HashSet<>(); - for (Permissible permissible : this.getPluginManager().getPermissionSubscriptions(permission)) { -- if (permissible instanceof CommandSender && permissible.hasPermission(permission)) { -+ if (permissible instanceof CommandSender && !(permissible instanceof org.bukkit.command.BlockCommandSender) && permissible.hasPermission(permission)) { // Paper - don't broadcast to BlockCommandSender (specifically Command Blocks) - recipients.add((CommandSender) permissible); - } - } diff --git a/patches/server/0908-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0908-Prevent-empty-items-from-being-added-to-world.patch new file mode 100644 index 0000000000..2e13972f86 --- /dev/null +++ b/patches/server/0908-Prevent-empty-items-from-being-added-to-world.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 15 Jun 2022 21:52:57 -0400 +Subject: [PATCH] Prevent empty items from being added to world + +The previous solution caused a bunch of bandaid fixes inorder to resolve edge cases where minecraft/the api might spawn items that are air. +Just simply prevent them from being added to the world instead. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 684063b8433f78ef0adf0de1057ea24720b32181..96bb0e56f12437037b598cd7baabf369e5994517 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -1439,6 +1439,7 @@ public class ServerLevel extends Level implements WorldGenLevel { + // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit + return false; + } else { ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added + // Paper start - capture all item additions to the world + if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { + captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 30c417c3169c1df43662fd77ac6816db64a42802..4c78c04ed031ec2e04642ebe5d79527e848d95f6 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -507,7 +507,7 @@ public class ItemEntity extends Entity { + } + + public void setItem(ItemStack stack) { +- com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit ++ // com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit // Paper - Remove check + this.getEntityData().set(ItemEntity.DATA_ITEM, stack); + this.getEntityData().markDirty(ItemEntity.DATA_ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty + this.despawnRate = level.paperConfig().entities.spawning.altItemDespawnRate.enabled ? level.paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), level.spigotConfig.itemDespawnRate) : level.spigotConfig.itemDespawnRate; // Paper diff --git a/patches/server/0909-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch b/patches/server/0909-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch new file mode 100644 index 0000000000..ad894dbcce --- /dev/null +++ b/patches/server/0909-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Thu, 21 Apr 2022 18:18:02 -0700 +Subject: [PATCH] Fix CCE for SplashPotion and LingeringPotion spawning + +Remove in 1.19 along with the SplashPotion and +LingeringPotion interfaces + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +index 397e0df15a0e64e5bc522f62f3b327a5039ec4c8..a926f4dc51821a05c28872dc90ad000fe8cb51f7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java +@@ -14,7 +14,7 @@ import org.bukkit.entity.ThrownPotion; + import org.bukkit.inventory.ItemStack; + import org.bukkit.potion.PotionEffect; + +-public class CraftThrownPotion extends CraftThrowableProjectile implements ThrownPotion { ++public class CraftThrownPotion extends CraftThrowableProjectile implements ThrownPotion, org.bukkit.entity.SplashPotion, org.bukkit.entity.LingeringPotion { // Paper - implement other classes to avoid violating spawn method generic contracts + public CraftThrownPotion(CraftServer server, net.minecraft.world.entity.projectile.ThrownPotion entity) { + super(server, entity); + } diff --git a/patches/server/0909-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0909-Prevent-empty-items-from-being-added-to-world.patch deleted file mode 100644 index 2e13972f86..0000000000 --- a/patches/server/0909-Prevent-empty-items-from-being-added-to-world.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 15 Jun 2022 21:52:57 -0400 -Subject: [PATCH] Prevent empty items from being added to world - -The previous solution caused a bunch of bandaid fixes inorder to resolve edge cases where minecraft/the api might spawn items that are air. -Just simply prevent them from being added to the world instead. - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 684063b8433f78ef0adf0de1057ea24720b32181..96bb0e56f12437037b598cd7baabf369e5994517 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1439,6 +1439,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added - // Paper start - capture all item additions to the world - if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { - captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 30c417c3169c1df43662fd77ac6816db64a42802..4c78c04ed031ec2e04642ebe5d79527e848d95f6 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -507,7 +507,7 @@ public class ItemEntity extends Entity { - } - - public void setItem(ItemStack stack) { -- com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit -+ // com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit // Paper - Remove check - this.getEntityData().set(ItemEntity.DATA_ITEM, stack); - this.getEntityData().markDirty(ItemEntity.DATA_ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty - this.despawnRate = level.paperConfig().entities.spawning.altItemDespawnRate.enabled ? level.paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), level.spigotConfig.itemDespawnRate) : level.spigotConfig.itemDespawnRate; // Paper diff --git a/patches/server/0910-Don-t-print-component-in-resource-pack-rejection-mes.patch b/patches/server/0910-Don-t-print-component-in-resource-pack-rejection-mes.patch new file mode 100644 index 0000000000..e39a508723 --- /dev/null +++ b/patches/server/0910-Don-t-print-component-in-resource-pack-rejection-mes.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: pop4959 +Date: Fri, 1 Jul 2022 22:00:06 -0700 +Subject: [PATCH] Don't print component in resource pack rejection message + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a7fe72d745625951172a8fdf9a4689b5bf540445..ac08f538087465b383f6127ae3d908fec29d765f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2049,7 +2049,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { +- ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName()); ++ ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message + this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause + } + // Paper start diff --git a/patches/server/0910-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch b/patches/server/0910-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch deleted file mode 100644 index ad894dbcce..0000000000 --- a/patches/server/0910-Fix-CCE-for-SplashPotion-and-LingeringPotion-spawnin.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Thu, 21 Apr 2022 18:18:02 -0700 -Subject: [PATCH] Fix CCE for SplashPotion and LingeringPotion spawning - -Remove in 1.19 along with the SplashPotion and -LingeringPotion interfaces - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -index 397e0df15a0e64e5bc522f62f3b327a5039ec4c8..a926f4dc51821a05c28872dc90ad000fe8cb51f7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java -@@ -14,7 +14,7 @@ import org.bukkit.entity.ThrownPotion; - import org.bukkit.inventory.ItemStack; - import org.bukkit.potion.PotionEffect; - --public class CraftThrownPotion extends CraftThrowableProjectile implements ThrownPotion { -+public class CraftThrownPotion extends CraftThrowableProjectile implements ThrownPotion, org.bukkit.entity.SplashPotion, org.bukkit.entity.LingeringPotion { // Paper - implement other classes to avoid violating spawn method generic contracts - public CraftThrownPotion(CraftServer server, net.minecraft.world.entity.projectile.ThrownPotion entity) { - super(server, entity); - } diff --git a/patches/server/0911-Add-Player-getFishHook.patch b/patches/server/0911-Add-Player-getFishHook.patch new file mode 100644 index 0000000000..bf5ccb5986 --- /dev/null +++ b/patches/server/0911-Add-Player-getFishHook.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: u9g +Date: Tue, 14 Jun 2022 19:36:10 -0400 +Subject: [PATCH] Add Player#getFishHook + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +index 44dab523264c594aa9c619e3ee2e0d7f93982ddc..e779dcc4982ff51e4d450265fd61bc26e8e74d3a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +@@ -153,6 +153,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { + return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); + } + // Paper end ++ // Paper start ++ @Override ++ public org.bukkit.entity.FishHook getFishHook() { ++ if (getHandle().fishing == null) { ++ return null; ++ } ++ return (org.bukkit.entity.FishHook) getHandle().fishing.getBukkitEntity(); ++ } ++ // Paper end + @Override + public boolean sleep(Location location, boolean force) { + Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0911-Don-t-print-component-in-resource-pack-rejection-mes.patch b/patches/server/0911-Don-t-print-component-in-resource-pack-rejection-mes.patch deleted file mode 100644 index 925d8eda82..0000000000 --- a/patches/server/0911-Don-t-print-component-in-resource-pack-rejection-mes.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: pop4959 -Date: Fri, 1 Jul 2022 22:00:06 -0700 -Subject: [PATCH] Don't print component in resource pack rejection message - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 603e404e75d39279fa5c4222ec5373271dd1ae64..b3e165a7731c272e4ad0adf1c5250d230f8d649c 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2048,7 +2048,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); - if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { -- ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName()); -+ ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message - this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause - } - // Paper start diff --git a/patches/server/0912-Add-Player-getFishHook.patch b/patches/server/0912-Add-Player-getFishHook.patch deleted file mode 100644 index 89347ce701..0000000000 --- a/patches/server/0912-Add-Player-getFishHook.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: u9g -Date: Tue, 14 Jun 2022 19:36:10 -0400 -Subject: [PATCH] Add Player#getFishHook - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 6149e74262ca6c4c00686a89c273c59bfaac3e05..c0a7b7bb9757de55424188629087d2ec82479e3c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -150,6 +150,15 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - return new Location(worldServer.getWorld(), bed.getX(), bed.getY(), bed.getZ()); - } - // Paper end -+ // Paper start -+ @Override -+ public org.bukkit.entity.FishHook getFishHook() { -+ if (getHandle().fishing == null) { -+ return null; -+ } -+ return (org.bukkit.entity.FishHook) getHandle().fishing.getBukkitEntity(); -+ } -+ // Paper end - @Override - public boolean sleep(Location location, boolean force) { - Preconditions.checkArgument(location != null, "Location cannot be null"); diff --git a/patches/server/0912-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch b/patches/server/0912-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch new file mode 100644 index 0000000000..f9cbdd0899 --- /dev/null +++ b/patches/server/0912-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sun, 3 Jul 2022 22:31:37 -0700 +Subject: [PATCH] Do not sync load chunk for dynamic game event listener + registration + +These can be called while an entity is being added to the world, +and if the entity is being added from a chunk load context the +sync load will block indefinitely (because the chunk load context +is for completing the chunk to FULL). + +This does raise questions about the current system for these +dynamic registrations, as it looks like there is _zero_ logic +to account for the case where the chunk is _not_ currently loaded +and then later loaded. + +diff --git a/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java b/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java +index 39f79c6d95e0f14d55783375df9ecf053e8d19de..610bfcceec51fcd1d82040f0dbfc03be20b8dce7 100644 +--- a/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java ++++ b/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java +@@ -66,7 +66,7 @@ public class DynamicGameEventListener { + + private static void ifChunkExists(LevelReader world, @Nullable SectionPos sectionPos, Consumer dispatcherConsumer) { + if (sectionPos != null) { +- ChunkAccess chunkAccess = world.getChunk(sectionPos.x(), sectionPos.z(), ChunkStatus.FULL, false); ++ ChunkAccess chunkAccess = world.getChunkIfLoadedImmediately(sectionPos.getX(), sectionPos.getZ()); // Paper - can cause sync loads while completing a chunk, resulting in deadlock + if (chunkAccess != null) { + dispatcherConsumer.accept(chunkAccess.getEventDispatcher(sectionPos.y())); + } diff --git a/patches/server/0913-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0913-Add-various-missing-EntityDropItemEvent-calls.patch new file mode 100644 index 0000000000..7a01a147b2 --- /dev/null +++ b/patches/server/0913-Add-various-missing-EntityDropItemEvent-calls.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Tue, 20 Jul 2021 21:35:47 -0700 +Subject: [PATCH] Add various missing EntityDropItemEvent calls + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 45b740753ac8dd20deb8618d5ac85c48737d34f4..4df5001ecfe5a3519ac3fbde06cfea2a233d2530 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2520,6 +2520,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe + + entityitem.setDefaultPickUpDelay(); ++ // Paper start ++ return this.spawnAtLocation(entityitem); ++ } ++ } ++ @Nullable ++ public ItemEntity spawnAtLocation(ItemEntity entityitem) { ++ { ++ // Paper end + // CraftBukkit start + EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); + Bukkit.getPluginManager().callEvent(event); +diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +index 7c53dddb598de85abf1eb8b8ee183a6e8e6f9c74..3f100d847fbce6db5b625e99c4f3694576237372 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +@@ -593,7 +593,7 @@ public class Dolphin extends WaterAnimal { + float f2 = 0.02F * Dolphin.this.random.nextFloat(); + + entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); +- Dolphin.this.level.addFreshEntity(entityitem); ++ Dolphin.this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent + } + } + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java +index 8f294f10aca2df007830b12da0506f7614206a89..6a66b5d1a3d8615dcc15057f03476e9ccbf4b4f2 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java +@@ -497,14 +497,14 @@ public class Fox extends Animal { + entityitem.setPickUpDelay(40); + entityitem.setThrower(this.getUUID()); + this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); +- this.level.addFreshEntity(entityitem); ++ this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent + } + } + + private void dropItemStack(ItemStack stack) { + ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), stack); + +- this.level.addFreshEntity(entityitem); ++ this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent + } + + @Override +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 56dd01801f56c56d07101e7e22b58ac059f5f07f..31be36e6b7b6bd0c0d7fda4e1b03ecd38947f3a5 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -329,8 +329,7 @@ public class Goat extends Animal { + double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); + ItemEntity entityitem = new ItemEntity(this.level, vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); + +- this.level.addFreshEntity(entityitem); +- return true; ++ return this.spawnAtLocation(entityitem) != null; // Paper - call EntityDropItemEvent by calling spawnAtLocation. + } + } + diff --git a/patches/server/0913-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch b/patches/server/0913-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch deleted file mode 100644 index f9cbdd0899..0000000000 --- a/patches/server/0913-Do-not-sync-load-chunk-for-dynamic-game-event-listen.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 3 Jul 2022 22:31:37 -0700 -Subject: [PATCH] Do not sync load chunk for dynamic game event listener - registration - -These can be called while an entity is being added to the world, -and if the entity is being added from a chunk load context the -sync load will block indefinitely (because the chunk load context -is for completing the chunk to FULL). - -This does raise questions about the current system for these -dynamic registrations, as it looks like there is _zero_ logic -to account for the case where the chunk is _not_ currently loaded -and then later loaded. - -diff --git a/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java b/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java -index 39f79c6d95e0f14d55783375df9ecf053e8d19de..610bfcceec51fcd1d82040f0dbfc03be20b8dce7 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/DynamicGameEventListener.java -@@ -66,7 +66,7 @@ public class DynamicGameEventListener { - - private static void ifChunkExists(LevelReader world, @Nullable SectionPos sectionPos, Consumer dispatcherConsumer) { - if (sectionPos != null) { -- ChunkAccess chunkAccess = world.getChunk(sectionPos.x(), sectionPos.z(), ChunkStatus.FULL, false); -+ ChunkAccess chunkAccess = world.getChunkIfLoadedImmediately(sectionPos.getX(), sectionPos.getZ()); // Paper - can cause sync loads while completing a chunk, resulting in deadlock - if (chunkAccess != null) { - dispatcherConsumer.accept(chunkAccess.getEventDispatcher(sectionPos.y())); - } diff --git a/patches/server/0914-Add-some-minimal-debug-information-to-chat-packet-er.patch b/patches/server/0914-Add-some-minimal-debug-information-to-chat-packet-er.patch new file mode 100644 index 0000000000..2cbecbfbe0 --- /dev/null +++ b/patches/server/0914-Add-some-minimal-debug-information-to-chat-packet-er.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Wed, 6 Jul 2022 05:52:22 +0100 +Subject: [PATCH] Add some minimal debug information to chat packet errors + +TODO: potentially add some kick leeway + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index ac08f538087465b383f6127ae3d908fec29d765f..2d924260b76fd77413d125a009773e4d3620a51f 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2298,7 +2298,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + + private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { + if (!this.updateChatOrder(timestamp)) { +- ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); ++ ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper + this.server.scheduleOnMain(() -> { // Paper - push to main + this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause + }); // Paper - push to main +@@ -2569,7 +2569,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + if (playerchatmessage.hasExpiredServer(Instant.now())) { +- ServerGamePacketListenerImpl.LOGGER.warn("{} sent expired chat: '{}'. Is the client/server system time unsynchronized?", this.player.getName().getString(), playerchatmessage.signedContent().plain()); ++ ServerGamePacketListenerImpl.LOGGER.warn("{} sent expired chat: '{}'. Is the client/server system time unsynchronized? c: {} s: {}", this.player.getName().getString(), playerchatmessage.signedContent().plain(), playerchatmessage.timeStamp().getEpochSecond(), Instant.now().getEpochSecond()); // Paper + } + + return true; diff --git a/patches/server/0914-Add-various-missing-EntityDropItemEvent-calls.patch b/patches/server/0914-Add-various-missing-EntityDropItemEvent-calls.patch deleted file mode 100644 index b326403bda..0000000000 --- a/patches/server/0914-Add-various-missing-EntityDropItemEvent-calls.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 20 Jul 2021 21:35:47 -0700 -Subject: [PATCH] Add various missing EntityDropItemEvent calls - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 33ec6c1e942a7f852e4726683918ed06cde2e16a..700e2299200b536255dd38c2f84e9ab13ab31811 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2506,6 +2506,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - stack.setCount(0); // Paper - destroy this item - if this ever leaks due to game bugs, ensure it doesn't dupe - - entityitem.setDefaultPickUpDelay(); -+ // Paper start -+ return this.spawnAtLocation(entityitem); -+ } -+ } -+ @Nullable -+ public ItemEntity spawnAtLocation(ItemEntity entityitem) { -+ { -+ // Paper end - // CraftBukkit start - EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); - Bukkit.getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -index 7c53dddb598de85abf1eb8b8ee183a6e8e6f9c74..3f100d847fbce6db5b625e99c4f3694576237372 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -593,7 +593,7 @@ public class Dolphin extends WaterAnimal { - float f2 = 0.02F * Dolphin.this.random.nextFloat(); - - entityitem.setDeltaMovement((double) (0.3F * -Mth.sin(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.cos(f1) * f2), (double) (0.3F * Mth.sin(Dolphin.this.getXRot() * 0.017453292F) * 1.5F), (double) (0.3F * Mth.cos(Dolphin.this.getYRot() * 0.017453292F) * Mth.cos(Dolphin.this.getXRot() * 0.017453292F) + Mth.sin(f1) * f2)); -- Dolphin.this.level.addFreshEntity(entityitem); -+ Dolphin.this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index 8f294f10aca2df007830b12da0506f7614206a89..6a66b5d1a3d8615dcc15057f03476e9ccbf4b4f2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -497,14 +497,14 @@ public class Fox extends Animal { - entityitem.setPickUpDelay(40); - entityitem.setThrower(this.getUUID()); - this.playSound(SoundEvents.FOX_SPIT, 1.0F, 1.0F); -- this.level.addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent - } - } - - private void dropItemStack(ItemStack stack) { - ItemEntity entityitem = new ItemEntity(this.level, this.getX(), this.getY(), this.getZ(), stack); - -- this.level.addFreshEntity(entityitem); -+ this.spawnAtLocation(entityitem); // Paper - call EntityDropItemEvent - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 56dd01801f56c56d07101e7e22b58ac059f5f07f..31be36e6b7b6bd0c0d7fda4e1b03ecd38947f3a5 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -329,8 +329,7 @@ public class Goat extends Animal { - double d2 = (double) Mth.randomBetween(this.random, -0.2F, 0.2F); - ItemEntity entityitem = new ItemEntity(this.level, vec3d.x(), vec3d.y(), vec3d.z(), itemstack, d0, d1, d2); - -- this.level.addFreshEntity(entityitem); -- return true; -+ return this.spawnAtLocation(entityitem) != null; // Paper - call EntityDropItemEvent by calling spawnAtLocation. - } - } - diff --git a/patches/server/0915-Add-some-minimal-debug-information-to-chat-packet-er.patch b/patches/server/0915-Add-some-minimal-debug-information-to-chat-packet-er.patch deleted file mode 100644 index 27a8f5fb18..0000000000 --- a/patches/server/0915-Add-some-minimal-debug-information-to-chat-packet-er.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Wed, 6 Jul 2022 05:52:22 +0100 -Subject: [PATCH] Add some minimal debug information to chat packet errors - -TODO: potentially add some kick leeway - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b3e165a7731c272e4ad0adf1c5250d230f8d649c..b91e901792ea967e6612bfe74d73ad9bcfce4ed2 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2297,7 +2297,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - - private boolean tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { - if (!this.updateChatOrder(timestamp)) { -- ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper - this.server.scheduleOnMain(() -> { // Paper - push to main - this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event cause - }); // Paper - push to main -@@ -2565,7 +2565,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - if (playerchatmessage.hasExpiredServer(Instant.now())) { -- ServerGamePacketListenerImpl.LOGGER.warn("{} sent expired chat: '{}'. Is the client/server system time unsynchronized?", this.player.getName().getString(), playerchatmessage.signedContent().plain()); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} sent expired chat: '{}'. Is the client/server system time unsynchronized? c: {} s: {}", this.player.getName().getString(), playerchatmessage.signedContent().plain(), playerchatmessage.timeStamp().getEpochSecond(), Instant.now().getEpochSecond()); // Paper - } - - return true; diff --git a/patches/server/0915-Fix-Bee-flower-NPE.patch b/patches/server/0915-Fix-Bee-flower-NPE.patch new file mode 100644 index 0000000000..d1fca0922f --- /dev/null +++ b/patches/server/0915-Fix-Bee-flower-NPE.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 6 Jul 2022 14:59:38 -0700 +Subject: [PATCH] Fix Bee flower NPE + + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index bd048cc30046f19f9eee89c6ba45d0816a160e67..e5de2c1d11e5de88420caba35bf75c8bbd799db5 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -802,7 +802,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + ++this.pollinatingTicks; + if (this.pollinatingTicks > 600) { + Bee.this.savedFlowerPos = null; +- } else { ++ } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this + Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D); + + if (vec3d.distanceTo(Bee.this.position()) > 1.0D) { diff --git a/patches/server/0916-Fix-Bee-flower-NPE.patch b/patches/server/0916-Fix-Bee-flower-NPE.patch deleted file mode 100644 index d1fca0922f..0000000000 --- a/patches/server/0916-Fix-Bee-flower-NPE.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 6 Jul 2022 14:59:38 -0700 -Subject: [PATCH] Fix Bee flower NPE - - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index bd048cc30046f19f9eee89c6ba45d0816a160e67..e5de2c1d11e5de88420caba35bf75c8bbd799db5 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -802,7 +802,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - ++this.pollinatingTicks; - if (this.pollinatingTicks > 600) { - Bee.this.savedFlowerPos = null; -- } else { -+ } else if (Bee.this.savedFlowerPos != null) { // Paper - add null check since API can manipulate this - Vec3 vec3d = Vec3.atBottomCenterOf(Bee.this.savedFlowerPos).add(0.0D, 0.6000000238418579D, 0.0D); - - if (vec3d.distanceTo(Bee.this.position()) > 1.0D) { diff --git a/patches/server/0916-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch b/patches/server/0916-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch new file mode 100644 index 0000000000..90422ebe0b --- /dev/null +++ b/patches/server/0916-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sun, 17 Jul 2022 11:49:43 -0400 +Subject: [PATCH] Fix Spigot Config not using commands.spam-exclusions + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2d924260b76fd77413d125a009773e4d3620a51f..421d7e28370085dffa415d71d4e28d3c4d38ac87 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2589,7 +2589,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + // Spigot end + // this.chatSpamTickCount += 20; +- if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ if (counted && this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - exclude from SpigotConfig.spamExclusions + // CraftBukkit end + this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause + } diff --git a/patches/server/0917-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch b/patches/server/0917-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch new file mode 100644 index 0000000000..dd94178b5a --- /dev/null +++ b/patches/server/0917-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Wed, 20 Jul 2022 12:07:36 -0400 +Subject: [PATCH] Add SpawnReason to Tadpoles spawned by Frogspawn + + +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index af7f77fb9fdf27509499f9d35fd42a6a50bf9cb0..e1d8ababdb992821cc0ac383c13f1f4d10b09107 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -103,7 +103,7 @@ public class FrogspawnBlock extends Block { + int k = random.nextInt(1, 361); + tadpole.moveTo(d, (double)pos.getY() - 0.5D, e, (float)k, 0.0F); + tadpole.setPersistenceRequired(); +- world.addFreshEntity(tadpole); ++ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper + } + + } diff --git a/patches/server/0917-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch b/patches/server/0917-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch deleted file mode 100644 index f982df3869..0000000000 --- a/patches/server/0917-Fix-Spigot-Config-not-using-commands.spam-exclusions.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Sun, 17 Jul 2022 11:49:43 -0400 -Subject: [PATCH] Fix Spigot Config not using commands.spam-exclusions - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index b91e901792ea967e6612bfe74d73ad9bcfce4ed2..13c253b1d7f6d4713135baba2bc2ad9cd84224f8 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2585,7 +2585,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - // Spigot end - // this.chatSpamTickCount += 20; -- if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { -+ if (counted && this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper - exclude from SpigotConfig.spamExclusions - // CraftBukkit end - this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause - } diff --git a/patches/server/0918-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch b/patches/server/0918-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch deleted file mode 100644 index dd94178b5a..0000000000 --- a/patches/server/0918-Add-SpawnReason-to-Tadpoles-spawned-by-Frogspawn.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Doc -Date: Wed, 20 Jul 2022 12:07:36 -0400 -Subject: [PATCH] Add SpawnReason to Tadpoles spawned by Frogspawn - - -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index af7f77fb9fdf27509499f9d35fd42a6a50bf9cb0..e1d8ababdb992821cc0ac383c13f1f4d10b09107 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -103,7 +103,7 @@ public class FrogspawnBlock extends Block { - int k = random.nextInt(1, 361); - tadpole.moveTo(d, (double)pos.getY() - 0.5D, e, (float)k, 0.0F); - tadpole.setPersistenceRequired(); -- world.addFreshEntity(tadpole); -+ world.addFreshEntity(tadpole, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // Paper - } - - } diff --git a/patches/server/0918-More-Teleport-API.patch b/patches/server/0918-More-Teleport-API.patch new file mode 100644 index 0000000000..b645bde091 --- /dev/null +++ b/patches/server/0918-More-Teleport-API.patch @@ -0,0 +1,195 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 5 Sep 2021 12:15:59 -0400 +Subject: [PATCH] More Teleport API + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 421d7e28370085dffa415d71d4e28d3c4d38ac87..92617a19fb328320e17b58956cf0276d5a08325b 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1729,11 +1729,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + return false; // CraftBukkit - Return event status + } + +- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ // Paper start - Teleport API ++ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.RelativeTeleportFlag.class); ++ for (net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument relativeArgument : set) { ++ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); ++ } ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, flag, java.util.Set.copyOf(relativeFlags)); ++ // Paper end + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled() || !to.equals(event.getTo())) { +- set.clear(); // Can't relative teleport ++ //set.clear(); // Can't relative teleport // Paper - Teleport API: Now you can! + to = event.isCancelled() ? event.getFrom() : event.getTo(); + d0 = to.getX(); + d1 = to.getY(); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index c4ffccddce33cf461d9b04ccbb90026544f16b7d..99b99fae67e53a688b3519d8a8d0cc5f3468e7e8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -542,15 +542,33 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + + @Override + public boolean teleport(Location location, TeleportCause cause) { ++ // Paper start - Teleport passenger API ++ return teleport(location, cause, false); ++ } ++ ++ @Override ++ public boolean teleport(Location location, TeleportCause cause, boolean ignorePassengers, boolean dismount) { ++ // Paper end + Preconditions.checkArgument(location != null, "location cannot be null"); + location.checkFinite(); ++ // Paper start - Teleport passenger API ++ // Don't allow teleporting between worlds while keeping passengers ++ if (ignorePassengers && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + +- if (this.entity.isVehicle() || this.entity.isRemoved()) { ++ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API + return false; + } + + // If this entity is riding another entity, we must dismount before teleporting. +- this.entity.stopRiding(); ++ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API + + // Let the server handle cross world teleports + if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 16fa7bdb8cc4bcad01ed33455cf1e51b69e2f720..a8e63a417ab4971ce35569dbb0b792635e8366ae 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -1135,13 +1135,92 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public void setRotation(float yaw, float pitch) { +- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); ++ // Paper start - Teleport API ++ Location targetLocation = this.getEyeLocation(); ++ targetLocation.setYaw(yaw); ++ targetLocation.setPitch(pitch); ++ ++ org.bukkit.util.Vector direction = targetLocation.getDirection(); ++ targetLocation.add(direction); ++ this.lookAt(targetLocation, io.papermc.paper.entity.LookAnchor.EYES); ++ // Paper end + } + + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { ++ // Paper start - Teleport API ++ return this.teleport(location, cause, false); ++ } ++ ++ @Override ++ public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause, boolean ignorePassengers, boolean dismount) { ++ return this.teleport(location, cause, ignorePassengers, dismount, new io.papermc.paper.entity.RelativeTeleportFlag[0]); ++ } ++ ++ @Override ++ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); ++ } ++ ++ @Override ++ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { ++ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new Vec3(x, y, z)); ++ } ++ ++ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { ++ return switch (nmsAnchor) { ++ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; ++ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { ++ return switch (playerAnchor) { ++ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; ++ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; ++ }; ++ } ++ ++ public static net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument toNmsRelativeFlag(io.papermc.paper.entity.RelativeTeleportFlag apiFlag) { ++ return switch (apiFlag) { ++ case X -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.X; ++ case Y -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Y; ++ case Z -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Z; ++ case PITCH -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.X_ROT; ++ case YAW -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Y_ROT; ++ }; ++ } ++ ++ public static io.papermc.paper.entity.RelativeTeleportFlag toApiRelativeFlag(net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument nmsFlag) { ++ return switch (nmsFlag) { ++ case X -> io.papermc.paper.entity.RelativeTeleportFlag.X; ++ case Y -> io.papermc.paper.entity.RelativeTeleportFlag.Y; ++ case Z -> io.papermc.paper.entity.RelativeTeleportFlag.Z; ++ case X_ROT -> io.papermc.paper.entity.RelativeTeleportFlag.PITCH; ++ case Y_ROT -> io.papermc.paper.entity.RelativeTeleportFlag.YAW; ++ }; ++ } ++ ++ @Override ++ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, boolean ignorePassengers, boolean dismount, io.papermc.paper.entity.RelativeTeleportFlag... flags) { ++ var relativeArguments = java.util.EnumSet.noneOf(net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.class); ++ for (io.papermc.paper.entity.RelativeTeleportFlag flag : flags) { ++ relativeArguments.add(toNmsRelativeFlag(flag)); ++ } ++ // Paper end - Teleport API + Preconditions.checkArgument(location != null, "location"); + Preconditions.checkArgument(location.getWorld() != null, "location.world"); ++ // Paper start - Teleport passenger API ++ // Don't allow teleporting between worlds while keeping passengers ++ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ ++ // Don't allow to teleport between worlds if remaining on vehicle ++ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { ++ return false; ++ } ++ // Paper end + location.checkFinite(); + + ServerPlayer entity = this.getHandle(); +@@ -1154,7 +1233,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return false; + } + +- if (entity.isVehicle()) { ++ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API + return false; + } + +@@ -1172,7 +1251,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + } + + // If this player is riding another entity, we must dismount before teleporting. +- entity.stopRiding(); ++ if (dismount) entity.stopRiding(); // Paper - Teleport API + + // SPIGOT-5509: Wakeup, similar to riding + if (this.isSleeping()) { +@@ -1194,7 +1273,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + // Check if the fromWorld and toWorld are the same. + if (fromWorld == toWorld) { +- entity.connection.teleport(to); ++ entity.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), relativeArguments, dismount); // Paper - Teleport API + } else { + server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck); // Paper + } diff --git a/patches/server/0919-Add-EntityPortalReadyEvent.patch b/patches/server/0919-Add-EntityPortalReadyEvent.patch new file mode 100644 index 0000000000..b3b68153bc --- /dev/null +++ b/patches/server/0919-Add-EntityPortalReadyEvent.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Wed, 12 May 2021 04:30:42 -0700 +Subject: [PATCH] Add EntityPortalReadyEvent + + +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 4df5001ecfe5a3519ac3fbde06cfea2a233d2530..0b3765ff8a25215bc42298b591eeffe022107079 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -2867,6 +2867,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit + this.level.getProfiler().push("portal"); + this.portalTime = i; ++ // Paper start ++ io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); ++ if (!event.callEvent()) { ++ this.portalTime = 0; ++ } else { ++ worldserver1 = event.getTargetWorld() == null ? null : ((CraftWorld) event.getTargetWorld()).getHandle(); ++ // Paper end + this.setPortalCooldown(); + // CraftBukkit start + if (this instanceof ServerPlayer) { +@@ -2874,6 +2881,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + } else { + this.changeDimension(worldserver1); + } ++ } // Paper + // CraftBukkit end + this.level.getProfiler().pop(); + } diff --git a/patches/server/0919-More-Teleport-API.patch b/patches/server/0919-More-Teleport-API.patch deleted file mode 100644 index e133efa140..0000000000 --- a/patches/server/0919-More-Teleport-API.patch +++ /dev/null @@ -1,195 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 5 Sep 2021 12:15:59 -0400 -Subject: [PATCH] More Teleport API - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 13c253b1d7f6d4713135baba2bc2ad9cd84224f8..0e365529695321ab2b164c75d4c67bdd2f51492d 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1729,11 +1729,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - return false; // CraftBukkit - Return event status - } - -- PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); -+ // Paper start - Teleport API -+ Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.RelativeTeleportFlag.class); -+ for (net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument relativeArgument : set) { -+ relativeFlags.add(org.bukkit.craftbukkit.entity.CraftPlayer.toApiRelativeFlag(relativeArgument)); -+ } -+ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause, flag, java.util.Set.copyOf(relativeFlags)); -+ // Paper end - this.cserver.getPluginManager().callEvent(event); - - if (event.isCancelled() || !to.equals(event.getTo())) { -- set.clear(); // Can't relative teleport -+ //set.clear(); // Can't relative teleport // Paper - Teleport API: Now you can! - to = event.isCancelled() ? event.getFrom() : event.getTo(); - d0 = to.getX(); - d1 = to.getY(); -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 0bae967bb9830784d98c2a1cccca512660c6324a..cc7b76a66d87ffa30c4bf6e3c1123df86d73b571 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -540,15 +540,33 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - - @Override - public boolean teleport(Location location, TeleportCause cause) { -+ // Paper start - Teleport passenger API -+ return teleport(location, cause, false); -+ } -+ -+ @Override -+ public boolean teleport(Location location, TeleportCause cause, boolean ignorePassengers, boolean dismount) { -+ // Paper end - Preconditions.checkArgument(location != null, "location cannot be null"); - location.checkFinite(); -+ // Paper start - Teleport passenger API -+ // Don't allow teleporting between worlds while keeping passengers -+ if (ignorePassengers && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && this.entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - -- if (this.entity.isVehicle() || this.entity.isRemoved()) { -+ if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API - return false; - } - - // If this entity is riding another entity, we must dismount before teleporting. -- this.entity.stopRiding(); -+ if (dismount) this.entity.stopRiding(); // Paper - Teleport passenger API - - // Let the server handle cross world teleports - if (location.getWorld() != null && !location.getWorld().equals(this.getWorld())) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 16fa7bdb8cc4bcad01ed33455cf1e51b69e2f720..a8e63a417ab4971ce35569dbb0b792635e8366ae 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1135,13 +1135,92 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setRotation(float yaw, float pitch) { -- throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); -+ // Paper start - Teleport API -+ Location targetLocation = this.getEyeLocation(); -+ targetLocation.setYaw(yaw); -+ targetLocation.setPitch(pitch); -+ -+ org.bukkit.util.Vector direction = targetLocation.getDirection(); -+ targetLocation.add(direction); -+ this.lookAt(targetLocation, io.papermc.paper.entity.LookAnchor.EYES); -+ // Paper end - } - - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { -+ // Paper start - Teleport API -+ return this.teleport(location, cause, false); -+ } -+ -+ @Override -+ public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause, boolean ignorePassengers, boolean dismount) { -+ return this.teleport(location, cause, ignorePassengers, dismount, new io.papermc.paper.entity.RelativeTeleportFlag[0]); -+ } -+ -+ @Override -+ public void lookAt(@NotNull org.bukkit.entity.Entity entity, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor, @NotNull io.papermc.paper.entity.LookAnchor entityAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); -+ } -+ -+ @Override -+ public void lookAt(double x, double y, double z, @NotNull io.papermc.paper.entity.LookAnchor playerAnchor) { -+ this.getHandle().lookAt(toNmsAnchor(playerAnchor), new Vec3(x, y, z)); -+ } -+ -+ public static net.minecraft.commands.arguments.EntityAnchorArgument.Anchor toNmsAnchor(io.papermc.paper.entity.LookAnchor nmsAnchor) { -+ return switch (nmsAnchor) { -+ case EYES -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.EYES; -+ case FEET -> net.minecraft.commands.arguments.EntityAnchorArgument.Anchor.FEET; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.LookAnchor toApiAnchor(net.minecraft.commands.arguments.EntityAnchorArgument.Anchor playerAnchor) { -+ return switch (playerAnchor) { -+ case EYES -> io.papermc.paper.entity.LookAnchor.EYES; -+ case FEET -> io.papermc.paper.entity.LookAnchor.FEET; -+ }; -+ } -+ -+ public static net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument toNmsRelativeFlag(io.papermc.paper.entity.RelativeTeleportFlag apiFlag) { -+ return switch (apiFlag) { -+ case X -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.X; -+ case Y -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Y; -+ case Z -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Z; -+ case PITCH -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.X_ROT; -+ case YAW -> net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.Y_ROT; -+ }; -+ } -+ -+ public static io.papermc.paper.entity.RelativeTeleportFlag toApiRelativeFlag(net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument nmsFlag) { -+ return switch (nmsFlag) { -+ case X -> io.papermc.paper.entity.RelativeTeleportFlag.X; -+ case Y -> io.papermc.paper.entity.RelativeTeleportFlag.Y; -+ case Z -> io.papermc.paper.entity.RelativeTeleportFlag.Z; -+ case X_ROT -> io.papermc.paper.entity.RelativeTeleportFlag.PITCH; -+ case Y_ROT -> io.papermc.paper.entity.RelativeTeleportFlag.YAW; -+ }; -+ } -+ -+ @Override -+ public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, boolean ignorePassengers, boolean dismount, io.papermc.paper.entity.RelativeTeleportFlag... flags) { -+ var relativeArguments = java.util.EnumSet.noneOf(net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket.RelativeArgument.class); -+ for (io.papermc.paper.entity.RelativeTeleportFlag flag : flags) { -+ relativeArguments.add(toNmsRelativeFlag(flag)); -+ } -+ // Paper end - Teleport API - Preconditions.checkArgument(location != null, "location"); - Preconditions.checkArgument(location.getWorld() != null, "location.world"); -+ // Paper start - Teleport passenger API -+ // Don't allow teleporting between worlds while keeping passengers -+ if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ -+ // Don't allow to teleport between worlds if remaining on vehicle -+ if (!dismount && entity.isPassenger() && location.getWorld() != this.getWorld()) { -+ return false; -+ } -+ // Paper end - location.checkFinite(); - - ServerPlayer entity = this.getHandle(); -@@ -1154,7 +1233,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return false; - } - -- if (entity.isVehicle()) { -+ if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API - return false; - } - -@@ -1172,7 +1251,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - // If this player is riding another entity, we must dismount before teleporting. -- entity.stopRiding(); -+ if (dismount) entity.stopRiding(); // Paper - Teleport API - - // SPIGOT-5509: Wakeup, similar to riding - if (this.isSleeping()) { -@@ -1194,7 +1273,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - // Check if the fromWorld and toWorld are the same. - if (fromWorld == toWorld) { -- entity.connection.teleport(to); -+ entity.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), relativeArguments, dismount); // Paper - Teleport API - } else { - server.getHandle().respawn(entity, toWorld, true, to, !toWorld.paperConfig().environment.disableTeleportationSuffocationCheck); // Paper - } diff --git a/patches/server/0920-Add-EntityPortalReadyEvent.patch b/patches/server/0920-Add-EntityPortalReadyEvent.patch deleted file mode 100644 index 9fdc15459a..0000000000 --- a/patches/server/0920-Add-EntityPortalReadyEvent.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Wed, 12 May 2021 04:30:42 -0700 -Subject: [PATCH] Add EntityPortalReadyEvent - - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 700e2299200b536255dd38c2f84e9ab13ab31811..f925a8d550ecbf2044a37bfe58b30d6578c5f6af 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2853,6 +2853,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit - this.level.getProfiler().push("portal"); - this.portalTime = i; -+ // Paper start -+ io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); -+ if (!event.callEvent()) { -+ this.portalTime = 0; -+ } else { -+ worldserver1 = event.getTargetWorld() == null ? null : ((CraftWorld) event.getTargetWorld()).getHandle(); -+ // Paper end - this.setPortalCooldown(); - // CraftBukkit start - if (this instanceof ServerPlayer) { -@@ -2860,6 +2867,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - } else { - this.changeDimension(worldserver1); - } -+ } // Paper - // CraftBukkit end - this.level.getProfiler().pop(); - } diff --git a/patches/server/0920-Don-t-use-level-random-in-entity-constructors.patch b/patches/server/0920-Don-t-use-level-random-in-entity-constructors.patch new file mode 100644 index 0000000000..aea2ef2591 --- /dev/null +++ b/patches/server/0920-Don-t-use-level-random-in-entity-constructors.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 10 Jul 2022 14:13:22 -0700 +Subject: [PATCH] Don't use level random in entity constructors + +Paper makes the entity random thread-safe +and constructing an entity off the main thread +should be supported. Some entities (for whatever +reason) use the level's random in some places. + +diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java +index e5de2c1d11e5de88420caba35bf75c8bbd799db5..a9cdf9034ad269f7a71358443acc053288cfbe6d 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java ++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java +@@ -1028,7 +1028,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + BeeGoToHiveGoal() { + super(); +- this.travellingTicks = Bee.this.level.random.nextInt(10); ++ this.travellingTicks = Bee.this./* level. */random.nextInt(10); // Paper - use entity random + this.blacklistedTargets = Lists.newArrayList(); + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } +@@ -1145,7 +1145,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + + BeeGoToKnownFlowerGoal() { + super(); +- this.travellingTicks = Bee.this.level.random.nextInt(10); ++ this.travellingTicks = Bee.this./* level. */random.nextInt(10); // Paper - use entity random + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + +diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +index 4c78c04ed031ec2e04642ebe5d79527e848d95f6..fcc5444a1268931a0fd2df1e6bbbc17cfd5a61e0 100644 +--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java ++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +@@ -64,7 +64,12 @@ public class ItemEntity extends Entity { + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { +- this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D); ++ // Paper start - don't use world random in entity constructor ++ this(EntityType.ITEM, world); ++ this.setPos(x, y, z); ++ this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D); ++ this.setItem(stack); ++ // Paper end + } + + public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { +diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +index 8101f358975b35b5a2dafbade3d14a910e408fa2..e09271450cae84f6206a20d6622918fe37380d59 100644 +--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +@@ -35,7 +35,7 @@ public class PrimedTnt extends Entity { + public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { + this(EntityType.TNT, world); + this.setPos(x, y, z); +- double d3 = world.random.nextDouble() * 6.2831854820251465D; ++ double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - don't use world random in entity constructor + + this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D); + this.setFuse(80); diff --git a/patches/server/0921-Don-t-use-level-random-in-entity-constructors.patch b/patches/server/0921-Don-t-use-level-random-in-entity-constructors.patch deleted file mode 100644 index aea2ef2591..0000000000 --- a/patches/server/0921-Don-t-use-level-random-in-entity-constructors.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Sun, 10 Jul 2022 14:13:22 -0700 -Subject: [PATCH] Don't use level random in entity constructors - -Paper makes the entity random thread-safe -and constructing an entity off the main thread -should be supported. Some entities (for whatever -reason) use the level's random in some places. - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index e5de2c1d11e5de88420caba35bf75c8bbd799db5..a9cdf9034ad269f7a71358443acc053288cfbe6d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -1028,7 +1028,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - BeeGoToHiveGoal() { - super(); -- this.travellingTicks = Bee.this.level.random.nextInt(10); -+ this.travellingTicks = Bee.this./* level. */random.nextInt(10); // Paper - use entity random - this.blacklistedTargets = Lists.newArrayList(); - this.setFlags(EnumSet.of(Goal.Flag.MOVE)); - } -@@ -1145,7 +1145,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - BeeGoToKnownFlowerGoal() { - super(); -- this.travellingTicks = Bee.this.level.random.nextInt(10); -+ this.travellingTicks = Bee.this./* level. */random.nextInt(10); // Paper - use entity random - this.setFlags(EnumSet.of(Goal.Flag.MOVE)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 4c78c04ed031ec2e04642ebe5d79527e848d95f6..fcc5444a1268931a0fd2df1e6bbbc17cfd5a61e0 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -64,7 +64,12 @@ public class ItemEntity extends Entity { - } - - public ItemEntity(Level world, double x, double y, double z, ItemStack stack) { -- this(world, x, y, z, stack, world.random.nextDouble() * 0.2D - 0.1D, 0.2D, world.random.nextDouble() * 0.2D - 0.1D); -+ // Paper start - don't use world random in entity constructor -+ this(EntityType.ITEM, world); -+ this.setPos(x, y, z); -+ this.setDeltaMovement(this.random.nextDouble() * 0.2D - 0.1D, 0.2D, this.random.nextDouble() * 0.2D - 0.1D); -+ this.setItem(stack); -+ // Paper end - } - - public ItemEntity(Level world, double x, double y, double z, ItemStack stack, double velocityX, double velocityY, double velocityZ) { -diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index 8101f358975b35b5a2dafbade3d14a910e408fa2..e09271450cae84f6206a20d6622918fe37380d59 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -35,7 +35,7 @@ public class PrimedTnt extends Entity { - public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { - this(EntityType.TNT, world); - this.setPos(x, y, z); -- double d3 = world.random.nextDouble() * 6.2831854820251465D; -+ double d3 = this.random.nextDouble() * 6.2831854820251465D; // Paper - don't use world random in entity constructor - - this.setDeltaMovement(-Math.sin(d3) * 0.02D, 0.20000000298023224D, -Math.cos(d3) * 0.02D); - this.setFuse(80); diff --git a/patches/server/0921-Send-block-entities-after-destroy-prediction.patch b/patches/server/0921-Send-block-entities-after-destroy-prediction.patch new file mode 100644 index 0000000000..0d7cf43457 --- /dev/null +++ b/patches/server/0921-Send-block-entities-after-destroy-prediction.patch @@ -0,0 +1,91 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 25 Jun 2022 19:45:20 -0400 +Subject: [PATCH] Send block entities after destroy prediction + +Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during +block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index 9378e83a67a70dbb1fb4f05b33f1e553d008e62b..5a60f5dc202c44b06ca34e9a19d45cb715f74fd3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -62,6 +62,8 @@ public class ServerPlayerGameMode { + private BlockPos delayedDestroyPos; + private int delayedTickStart; + private int lastSentState; ++ public boolean captureSentBlockEntities = false; // Paper ++ public boolean capturedBlockEntity = false; // Paper + + public ServerPlayerGameMode(ServerPlayer player) { + this.gameModeForPlayer = GameType.DEFAULT_MODE; +@@ -187,10 +189,7 @@ public class ServerPlayerGameMode { + this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); + this.debugLogging(pos, false, sequence, "may not interact"); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - send block entity after predicting + // CraftBukkit end + return; + } +@@ -206,10 +205,7 @@ public class ServerPlayerGameMode { + // Paper end + this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + // Update any tile entity data for this block +- BlockEntity tileentity = this.level.getBlockEntity(pos); +- if (tileentity != null) { +- this.player.connection.send(tileentity.getUpdatePacket()); +- } ++ capturedBlockEntity = true; // Paper - send block entity after predicting + return; + } + // CraftBukkit end +@@ -385,10 +381,12 @@ public class ServerPlayerGameMode { + } + + // Update any tile entity data for this block ++ if (!captureSentBlockEntities) { // Paper - Toggle this location for capturing as this is used for api + BlockEntity tileentity = this.level.getBlockEntity(pos); + if (tileentity != null) { + this.player.connection.send(tileentity.getUpdatePacket()); + } ++ } else {capturedBlockEntity = true;} // Paper end + return false; + } + } +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 92617a19fb328320e17b58956cf0276d5a08325b..e88c82aac6d9947c7205f865c18146e7a5756b40 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1874,8 +1874,28 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + return; + } + // Paper end - Don't allow digging in unloaded chunks ++ // Paper start - send block entities after prediction ++ this.player.gameMode.capturedBlockEntity = false; ++ this.player.gameMode.captureSentBlockEntities = true; ++ // Paper end - send block entities after prediction + this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level.getMaxBuildHeight(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ // Paper start - send block entities after prediction ++ this.player.gameMode.captureSentBlockEntities = false; ++ // If a block entity was modified speedup the block change ack to avoid the block entity ++ // being overriden. ++ if (this.player.gameMode.capturedBlockEntity) { ++ // manually tick ++ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); ++ this.player.connection.ackBlockChangesUpTo = -1; ++ ++ this.player.gameMode.capturedBlockEntity = false; ++ BlockEntity tileentity = this.player.level.getBlockEntity(blockposition); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } ++ } ++ // Paper end - send block entities after prediction + return; + default: + throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0922-Send-block-entities-after-destroy-prediction.patch b/patches/server/0922-Send-block-entities-after-destroy-prediction.patch deleted file mode 100644 index 9796172b9c..0000000000 --- a/patches/server/0922-Send-block-entities-after-destroy-prediction.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 25 Jun 2022 19:45:20 -0400 -Subject: [PATCH] Send block entities after destroy prediction - -Minecraft's prediction system does not handle block entities, so if we are manually sending block entities during -block breaking we need to set it after the prediction is finished. This fixes block entities not showing when cancelling the BlockBreakEvent. - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 9378e83a67a70dbb1fb4f05b33f1e553d008e62b..5a60f5dc202c44b06ca34e9a19d45cb715f74fd3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -62,6 +62,8 @@ public class ServerPlayerGameMode { - private BlockPos delayedDestroyPos; - private int delayedTickStart; - private int lastSentState; -+ public boolean captureSentBlockEntities = false; // Paper -+ public boolean capturedBlockEntity = false; // Paper - - public ServerPlayerGameMode(ServerPlayer player) { - this.gameModeForPlayer = GameType.DEFAULT_MODE; -@@ -187,10 +189,7 @@ public class ServerPlayerGameMode { - this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); - this.debugLogging(pos, false, sequence, "may not interact"); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - send block entity after predicting - // CraftBukkit end - return; - } -@@ -206,10 +205,7 @@ public class ServerPlayerGameMode { - // Paper end - this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); - // Update any tile entity data for this block -- BlockEntity tileentity = this.level.getBlockEntity(pos); -- if (tileentity != null) { -- this.player.connection.send(tileentity.getUpdatePacket()); -- } -+ capturedBlockEntity = true; // Paper - send block entity after predicting - return; - } - // CraftBukkit end -@@ -385,10 +381,12 @@ public class ServerPlayerGameMode { - } - - // Update any tile entity data for this block -+ if (!captureSentBlockEntities) { // Paper - Toggle this location for capturing as this is used for api - BlockEntity tileentity = this.level.getBlockEntity(pos); - if (tileentity != null) { - this.player.connection.send(tileentity.getUpdatePacket()); - } -+ } else {capturedBlockEntity = true;} // Paper end - return false; - } - } -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0e365529695321ab2b164c75d4c67bdd2f51492d..6296683d900fa30b784e4e345856bdc3b175fb5e 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1874,8 +1874,28 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - return; - } - // Paper end - Don't allow digging in unloaded chunks -+ // Paper start - send block entities after prediction -+ this.player.gameMode.capturedBlockEntity = false; -+ this.player.gameMode.captureSentBlockEntities = true; -+ // Paper end - send block entities after prediction - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level.getMaxBuildHeight(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ // Paper start - send block entities after prediction -+ this.player.gameMode.captureSentBlockEntities = false; -+ // If a block entity was modified speedup the block change ack to avoid the block entity -+ // being overriden. -+ if (this.player.gameMode.capturedBlockEntity) { -+ // manually tick -+ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); -+ this.player.connection.ackBlockChangesUpTo = -1; -+ -+ this.player.gameMode.capturedBlockEntity = false; -+ BlockEntity tileentity = this.player.level.getBlockEntity(blockposition); -+ if (tileentity != null) { -+ this.player.connection.send(tileentity.getUpdatePacket()); -+ } -+ } -+ // Paper end - send block entities after prediction - return; - default: - throw new IllegalArgumentException("Invalid player action"); diff --git a/patches/server/0922-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0922-Warn-on-plugins-accessing-faraway-chunks.patch new file mode 100644 index 0000000000..003620357a --- /dev/null +++ b/patches/server/0922-Warn-on-plugins-accessing-faraway-chunks.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Fri, 29 Jul 2022 12:35:19 -0400 +Subject: [PATCH] Warn on plugins accessing faraway chunks + + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 46c45f249e71ca94044f1260a23cd7331098fb2c..ceb1f18ec16ddcda792ef0393b5f4649fbef3017 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -418,7 +418,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + } + + private static boolean isInWorldBoundsHorizontal(BlockPos pos) { +- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; ++ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Dif on change + } + + private static boolean isOutsideSpawnableHeight(int y) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 9d27b093922f3dee9b459f8a9cdfa96f12f2e654..1b6ae90acffa06502902e11473b65c8431616b05 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -313,9 +313,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { + public boolean setSpawnLocation(int x, int y, int z) { + return this.setSpawnLocation(x, y, z, 0.0F); + } ++ // Paper start ++ private static void warnUnsafeChunk(String reason, int x, int z) { ++ // if any chunk coord is outside of 30 million blocks ++ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { ++ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); ++ if (plugin != null) { ++ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); ++ } ++ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { ++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); ++ } ++ } ++ } ++ // Paper end + + @Override + public Chunk getChunkAt(int x, int z) { ++ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper + // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it + net.minecraft.world.level.chunk.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (chunk == null) { +@@ -420,6 +435,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean regenerateChunk(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot ++ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper + // Paper start - implement regenerateChunk method + final ServerLevel serverLevel = this.world; + final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); +@@ -516,6 +532,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean loadChunk(int x, int z, boolean generate) { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper + // Paper start - Optimize this method + ChunkPos chunkPos = new ChunkPos(x, z); + ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); // Paper +@@ -580,6 +597,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { ++ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper + Preconditions.checkArgument(plugin != null, "null plugin"); + Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); + +@@ -661,6 +679,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void setChunkForceLoaded(int x, int z, boolean forced) { ++ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper + this.getHandle().setChunkForced(x, z, forced); + } + +@@ -972,6 +991,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper + // Transient load for this tick + return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); + } +@@ -2333,6 +2353,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { + // Spigot end + // Paper start + public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { ++ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper + if (Bukkit.isPrimaryThread()) { + net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); + if (immediate != null) { diff --git a/patches/server/0923-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0923-Custom-Chat-Completion-Suggestions-API.patch new file mode 100644 index 0000000000..3259824656 --- /dev/null +++ b/patches/server/0923-Custom-Chat-Completion-Suggestions-API.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sat, 30 Jul 2022 11:23:05 -0400 +Subject: [PATCH] Custom Chat Completion Suggestions API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index a8e63a417ab4971ce35569dbb0b792635e8366ae..41258fcfa0505500665c2f185b9ef6d050213355 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -654,6 +654,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); + } ++ ++ @Override ++ public void addAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, ++ new ArrayList<>(completions) ++ )); ++ } ++ ++ @Override ++ public void removeAdditionalChatCompletions(@NotNull Collection completions) { ++ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( ++ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, ++ new ArrayList<>(completions) ++ )); ++ } + // Paper end + + @Override diff --git a/patches/server/0923-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0923-Warn-on-plugins-accessing-faraway-chunks.patch deleted file mode 100644 index e66ae073c7..0000000000 --- a/patches/server/0923-Warn-on-plugins-accessing-faraway-chunks.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Fri, 29 Jul 2022 12:35:19 -0400 -Subject: [PATCH] Warn on plugins accessing faraway chunks - - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 9467ccaa1d73e1913495a46919aee530e749977d..5a2a1d394852d39ea576624586f7fa736dec807c 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -418,7 +418,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - - private static boolean isInWorldBoundsHorizontal(BlockPos pos) { -- return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; -+ return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000; // Dif on change - } - - private static boolean isOutsideSpawnableHeight(int y) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 9d27b093922f3dee9b459f8a9cdfa96f12f2e654..1b6ae90acffa06502902e11473b65c8431616b05 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -313,9 +313,24 @@ public class CraftWorld extends CraftRegionAccessor implements World { - public boolean setSpawnLocation(int x, int y, int z) { - return this.setSpawnLocation(x, y, z, 0.0F); - } -+ // Paper start -+ private static void warnUnsafeChunk(String reason, int x, int z) { -+ // if any chunk coord is outside of 30 million blocks -+ if (x > 1875000 || z > 1875000 || x < -1875000 || z < -1875000) { -+ Plugin plugin = io.papermc.paper.util.StackWalkerUtil.getFirstPluginCaller(); -+ if (plugin != null) { -+ plugin.getLogger().warning("Plugin is %s at (%s, %s), this might cause issues.".formatted(reason, x, z)); -+ } -+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { -+ io.papermc.paper.util.TraceUtil.dumpTraceForThread("Dangerous chunk retrieval"); -+ } -+ } -+ } -+ // Paper end - - @Override - public Chunk getChunkAt(int x, int z) { -+ warnUnsafeChunk("getting a faraway chunk", x, z); // Paper - // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it - net.minecraft.world.level.chunk.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (chunk == null) { -@@ -420,6 +435,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean regenerateChunk(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot -+ warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper - // Paper start - implement regenerateChunk method - final ServerLevel serverLevel = this.world; - final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); -@@ -516,6 +532,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @Override - public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ warnUnsafeChunk("loading a faraway chunk", x, z); // Paper - // Paper start - Optimize this method - ChunkPos chunkPos = new ChunkPos(x, z); - ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); // Paper -@@ -580,6 +597,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public boolean addPluginChunkTicket(int x, int z, Plugin plugin) { -+ warnUnsafeChunk("adding a faraway chunk ticket", x, z); // Paper - Preconditions.checkArgument(plugin != null, "null plugin"); - Preconditions.checkArgument(plugin.isEnabled(), "plugin is not enabled"); - -@@ -661,6 +679,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public void setChunkForceLoaded(int x, int z, boolean forced) { -+ warnUnsafeChunk("forceloading a faraway chunk", x, z); // Paper - this.getHandle().setChunkForced(x, z, forced); - } - -@@ -972,6 +991,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); - } -@@ -2333,6 +2353,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot end - // Paper start - public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { -+ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper - if (Bukkit.isPrimaryThread()) { - net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); - if (immediate != null) { diff --git a/patches/server/0924-Add-missing-BlockFadeEvents.patch b/patches/server/0924-Add-missing-BlockFadeEvents.patch new file mode 100644 index 0000000000..6b887deccf --- /dev/null +++ b/patches/server/0924-Add-missing-BlockFadeEvents.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 21 Jul 2022 12:07:54 -0400 +Subject: [PATCH] Add missing BlockFadeEvents + + +diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +index e1d8ababdb992821cc0ac383c13f1f4d10b09107..d6232d6f14a195a0e3f8489f148eb8b44d0355c6 100644 +--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java +@@ -84,6 +84,11 @@ public class FrogspawnBlock extends Block { + } + + private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) { ++ // Paper start - Call BlockFadeEvent ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { ++ return; ++ } ++ // Paper end + this.destroyBlock(world, pos); + world.playSound((Player)null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F); + this.spawnTadpoles(world, pos, random); diff --git a/patches/server/0924-Custom-Chat-Completion-Suggestions-API.patch b/patches/server/0924-Custom-Chat-Completion-Suggestions-API.patch deleted file mode 100644 index 560a26bd5c..0000000000 --- a/patches/server/0924-Custom-Chat-Completion-Suggestions-API.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sat, 30 Jul 2022 11:23:05 -0400 -Subject: [PATCH] Custom Chat Completion Suggestions API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index e6411d8293c36f41b790cf17ecc507507f04d604..743b6e51817ffa1b7e602b1ac82eb1017841b691 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -654,6 +654,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - this.getHandle().getServer().getPlayerList().sendPlayerPermissionLevel(this.getHandle(), level, false); - } -+ -+ @Override -+ public void addAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.ADD, -+ new ArrayList<>(completions) -+ )); -+ } -+ -+ @Override -+ public void removeAdditionalChatCompletions(@NotNull Collection completions) { -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket( -+ net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket.Action.REMOVE, -+ new ArrayList<>(completions) -+ )); -+ } - // Paper end - - @Override diff --git a/patches/server/0925-Add-missing-BlockFadeEvents.patch b/patches/server/0925-Add-missing-BlockFadeEvents.patch deleted file mode 100644 index 6b887deccf..0000000000 --- a/patches/server/0925-Add-missing-BlockFadeEvents.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Thu, 21 Jul 2022 12:07:54 -0400 -Subject: [PATCH] Add missing BlockFadeEvents - - -diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -index e1d8ababdb992821cc0ac383c13f1f4d10b09107..d6232d6f14a195a0e3f8489f148eb8b44d0355c6 100644 ---- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java -@@ -84,6 +84,11 @@ public class FrogspawnBlock extends Block { - } - - private void hatchFrogspawn(ServerLevel world, BlockPos pos, RandomSource random) { -+ // Paper start - Call BlockFadeEvent -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { -+ return; -+ } -+ // Paper end - this.destroyBlock(world, pos); - world.playSound((Player)null, pos, SoundEvents.FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F); - this.spawnTadpoles(world, pos, random); diff --git a/patches/server/0925-Collision-API.patch b/patches/server/0925-Collision-API.patch new file mode 100644 index 0000000000..e1980a48f2 --- /dev/null +++ b/patches/server/0925-Collision-API.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 6 Oct 2021 20:10:44 -0400 +Subject: [PATCH] Collision API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +index 6445c2e4c97860e1c98f5263188d309cf55936f0..62bca85da6c5d9877e21fecb702370506ddf671c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java +@@ -962,5 +962,12 @@ public abstract class CraftRegionAccessor implements RegionAccessor { + + return this.getHandle().clip(new net.minecraft.world.level.ClipContext(vec3d, vec3d1, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, null)).getType() == net.minecraft.world.phys.HitResult.Type.MISS; + } ++ ++ @Override ++ public boolean hasCollisionsIn(@org.jetbrains.annotations.NotNull org.bukkit.util.BoundingBox boundingBox) { ++ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ(), false); ++ ++ return !this.getHandle().noCollision(aabb); ++ } + // 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 99b99fae67e53a688b3519d8a8d0cc5f3468e7e8..d4ea7d19ae16a8ccafcfe5300bb380b28fd42b75 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -1357,4 +1357,19 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. + } + // Paper end ++ // Paper Start - Collision API ++ @Override ++ public boolean collidesAt(@org.jetbrains.annotations.NotNull Location location) { ++ net.minecraft.world.phys.AABB aabb = this.getHandle().getBoundingBoxAt(location.getX(), location.getY(), location.getZ()); ++ ++ return !this.getHandle().level.noCollision(this.getHandle(), aabb); ++ } ++ ++ @Override ++ public boolean wouldCollideUsing(@org.jetbrains.annotations.NotNull BoundingBox boundingBox) { ++ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ(), false); ++ ++ return !this.getHandle().level.noCollision(this.getHandle(), aabb); ++ } ++ // Paper End - Collision API + } diff --git a/patches/server/0926-Collision-API.patch b/patches/server/0926-Collision-API.patch deleted file mode 100644 index 510fa23e1a..0000000000 --- a/patches/server/0926-Collision-API.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 6 Oct 2021 20:10:44 -0400 -Subject: [PATCH] Collision API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -index 6445c2e4c97860e1c98f5263188d309cf55936f0..62bca85da6c5d9877e21fecb702370506ddf671c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java -@@ -962,5 +962,12 @@ public abstract class CraftRegionAccessor implements RegionAccessor { - - return this.getHandle().clip(new net.minecraft.world.level.ClipContext(vec3d, vec3d1, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, null)).getType() == net.minecraft.world.phys.HitResult.Type.MISS; - } -+ -+ @Override -+ public boolean hasCollisionsIn(@org.jetbrains.annotations.NotNull org.bukkit.util.BoundingBox boundingBox) { -+ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ(), false); -+ -+ return !this.getHandle().noCollision(aabb); -+ } - // 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 b80cc0938b2b3928f4450f1314a9fbd7ea9c116b..6549ade8e19807c523e5a1dc68b66585aad438b1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1340,4 +1340,19 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed. - } - // Paper end -+ // Paper Start - Collision API -+ @Override -+ public boolean collidesAt(@org.jetbrains.annotations.NotNull Location location) { -+ net.minecraft.world.phys.AABB aabb = this.getHandle().getBoundingBoxAt(location.getX(), location.getY(), location.getZ()); -+ -+ return !this.getHandle().level.noCollision(this.getHandle(), aabb); -+ } -+ -+ @Override -+ public boolean wouldCollideUsing(@org.jetbrains.annotations.NotNull BoundingBox boundingBox) { -+ net.minecraft.world.phys.AABB aabb = new AABB(boundingBox.getMinX(), boundingBox.getMinY(), boundingBox.getMinZ(), boundingBox.getMaxX(), boundingBox.getMaxY(), boundingBox.getMaxZ(), false); -+ -+ return !this.getHandle().level.noCollision(this.getHandle(), aabb); -+ } -+ // Paper End - Collision API - } diff --git a/patches/server/0926-Fix-suggest-command-message-for-brigadier-syntax-exc.patch b/patches/server/0926-Fix-suggest-command-message-for-brigadier-syntax-exc.patch new file mode 100644 index 0000000000..3a5bf3be59 --- /dev/null +++ b/patches/server/0926-Fix-suggest-command-message-for-brigadier-syntax-exc.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: chickeneer +Date: Mon, 1 Aug 2022 20:13:02 -0500 +Subject: [PATCH] Fix suggest command message for brigadier syntax exceptions + +This is a bug accidentally introduced in upstream CB + +diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java +index 67ab16743b36dbf8b4336e33988d8e78433f566d..7c96f7fc5997761426a0c62cad0cab5cc668f282 100644 +--- a/src/main/java/net/minecraft/commands/Commands.java ++++ b/src/main/java/net/minecraft/commands/Commands.java +@@ -314,7 +314,7 @@ public class Commands { + if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { + int j = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); + MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { +- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit ++ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper + }); + + if (j > 10) { diff --git a/patches/server/0927-Fix-command-preprocess-cancelling-and-command-changi.patch b/patches/server/0927-Fix-command-preprocess-cancelling-and-command-changi.patch new file mode 100644 index 0000000000..501d00e40b --- /dev/null +++ b/patches/server/0927-Fix-command-preprocess-cancelling-and-command-changi.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Tue, 2 Aug 2022 19:16:23 +0200 +Subject: [PATCH] Fix command preprocess cancelling and command changing + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index e88c82aac6d9947c7205f865c18146e7a5756b40..1ff25ce408aba3620455f74e1535b5862b3bbdf9 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -2259,13 +2259,24 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); + +- if (event.isCancelled()) { +- return; +- } + command = event.getMessage().substring(1); + +- ParseResults parseresults = this.parseCommand(command); +- Map map = (packet.command().equals(command)) ? this.collectSignedArguments(packet, PreviewableCommand.of(parseresults)) : Collections.emptyMap(); ++ // Paper start - send message headers for cancelled or changed commands ++ ParseResults parseresults = this.parseCommand(packet.command()); ++ Map map = this.collectSignedArguments(packet, PreviewableCommand.of(parseresults)); ++ if (event.isCancelled() || !packet.command().equals(command)) { ++ for (final PlayerChatMessage message : map.values()) { ++ player.server.getPlayerList().broadcastMessageHeader(message, Set.of()); ++ } ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ // Remove signatures if the command was changed and use the changed command source stack ++ map.clear(); ++ parseresults = this.parseCommand(command); ++ } ++ // Paper end + // CraftBukkit end + Iterator iterator = map.values().iterator(); + diff --git a/patches/server/0927-Fix-suggest-command-message-for-brigadier-syntax-exc.patch b/patches/server/0927-Fix-suggest-command-message-for-brigadier-syntax-exc.patch deleted file mode 100644 index 2d62b243ec..0000000000 --- a/patches/server/0927-Fix-suggest-command-message-for-brigadier-syntax-exc.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: chickeneer -Date: Mon, 1 Aug 2022 20:13:02 -0500 -Subject: [PATCH] Fix suggest command message for brigadier syntax exceptions - -This is a bug accidentally introduced in upstream CB - -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 584d2539d715fef26a2d01f014c7c3f4f8ce8fd9..135b341e5b975fc542f66ef9c52e29f4c6dd4a53 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -314,7 +314,7 @@ public class Commands { - if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) { - int j = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor()); - MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> { -- return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit -+ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + label)); // CraftBukkit // Paper - }); - - if (j > 10) { diff --git a/patches/server/0928-Fix-command-preprocess-cancelling-and-command-changi.patch b/patches/server/0928-Fix-command-preprocess-cancelling-and-command-changi.patch deleted file mode 100644 index dcb37bcd3d..0000000000 --- a/patches/server/0928-Fix-command-preprocess-cancelling-and-command-changi.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Tue, 2 Aug 2022 19:16:23 +0200 -Subject: [PATCH] Fix command preprocess cancelling and command changing - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 6296683d900fa30b784e4e345856bdc3b175fb5e..c478693c3829937f64b9bed5d69fcfd2d7759a00 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2258,13 +2258,24 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); - this.cserver.getPluginManager().callEvent(event); - -- if (event.isCancelled()) { -- return; -- } - command = event.getMessage().substring(1); - -- ParseResults parseresults = this.parseCommand(command); -- Map map = (packet.command().equals(command)) ? this.collectSignedArguments(packet, PreviewableCommand.of(parseresults)) : Collections.emptyMap(); -+ // Paper start - send message headers for cancelled or changed commands -+ ParseResults parseresults = this.parseCommand(packet.command()); -+ Map map = this.collectSignedArguments(packet, PreviewableCommand.of(parseresults)); -+ if (event.isCancelled() || !packet.command().equals(command)) { -+ for (final PlayerChatMessage message : map.values()) { -+ player.server.getPlayerList().broadcastMessageHeader(message, Set.of()); -+ } -+ if (event.isCancelled()) { -+ return; -+ } -+ -+ // Remove signatures if the command was changed and use the changed command source stack -+ map.clear(); -+ parseresults = this.parseCommand(command); -+ } -+ // Paper end - // CraftBukkit end - Iterator iterator = map.values().iterator(); - diff --git a/patches/server/0928-Remove-invalid-signature-login-stacktrace.patch b/patches/server/0928-Remove-invalid-signature-login-stacktrace.patch new file mode 100644 index 0000000000..e51733ffdc --- /dev/null +++ b/patches/server/0928-Remove-invalid-signature-login-stacktrace.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Fri, 10 Jun 2022 16:02:35 +0200 +Subject: [PATCH] Remove invalid signature login stacktrace + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 51ff7ab5d8740e755cc893723f659c8481c1ec89..2f0b2d0f3a3dc02076cee9ab5e6dd6ab931143e3 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -177,7 +177,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + + profilepublickey = ServerLoginPacketListenerImpl.validatePublicKey(this.profilePublicKeyData, this.gameProfile.getId(), signaturevalidator, this.server.enforceSecureProfile()); + } catch (ProfilePublicKey.ValidationException profilepublickey_b) { +- ServerLoginPacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); ++ // ServerLoginPacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - unnecessary log + if (!this.connection.isMemoryConnection()) { + this.disconnect(profilepublickey_b.getComponent()); + return; diff --git a/patches/server/0929-Add-async-catcher-to-PlayerConnection-internalTelepo.patch b/patches/server/0929-Add-async-catcher-to-PlayerConnection-internalTelepo.patch new file mode 100644 index 0000000000..05e9d349b8 --- /dev/null +++ b/patches/server/0929-Add-async-catcher-to-PlayerConnection-internalTelepo.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Wed, 3 Aug 2022 12:57:36 -0700 +Subject: [PATCH] Add async catcher to PlayerConnection internalTeleport + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1ff25ce408aba3620455f74e1535b5862b3bbdf9..9eb921fec32afa360f3a402e978411fcf3ec618c 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1757,6 +1757,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + } + + public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set, boolean flag) { ++ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + // Paper start + if (player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); diff --git a/patches/server/0929-Remove-invalid-signature-login-stacktrace.patch b/patches/server/0929-Remove-invalid-signature-login-stacktrace.patch deleted file mode 100644 index e51733ffdc..0000000000 --- a/patches/server/0929-Remove-invalid-signature-login-stacktrace.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Fri, 10 Jun 2022 16:02:35 +0200 -Subject: [PATCH] Remove invalid signature login stacktrace - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 51ff7ab5d8740e755cc893723f659c8481c1ec89..2f0b2d0f3a3dc02076cee9ab5e6dd6ab931143e3 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -177,7 +177,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - - profilepublickey = ServerLoginPacketListenerImpl.validatePublicKey(this.profilePublicKeyData, this.gameProfile.getId(), signaturevalidator, this.server.enforceSecureProfile()); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { -- ServerLoginPacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -+ // ServerLoginPacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - unnecessary log - if (!this.connection.isMemoryConnection()) { - this.disconnect(profilepublickey_b.getComponent()); - return; diff --git a/patches/server/0930-Add-async-catcher-to-PlayerConnection-internalTelepo.patch b/patches/server/0930-Add-async-catcher-to-PlayerConnection-internalTelepo.patch deleted file mode 100644 index 37115a72a4..0000000000 --- a/patches/server/0930-Add-async-catcher-to-PlayerConnection-internalTelepo.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 3 Aug 2022 12:57:36 -0700 -Subject: [PATCH] Add async catcher to PlayerConnection internalTeleport - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c478693c3829937f64b9bed5d69fcfd2d7759a00..325def3149693f64b2b21e0e655b5b3e888fd9a0 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1757,6 +1757,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - } - - public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set, boolean flag) { -+ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper - // Paper start - if (player.isRemoved()) { - LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); diff --git a/patches/server/0930-Block-Ticking-API.patch b/patches/server/0930-Block-Ticking-API.patch new file mode 100644 index 0000000000..43db29d605 --- /dev/null +++ b/patches/server/0930-Block-Ticking-API.patch @@ -0,0 +1,47 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Sun, 26 Dec 2021 13:23:46 -0500 +Subject: [PATCH] Block Ticking API + + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +index bfe9dc935c87e01fb435d8b46ce413b84ca74856..0d47460494135d4ec4c95260de033e054c2f0404 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -736,5 +736,21 @@ public class CraftBlock implements Block { + public boolean isValidTool(ItemStack itemStack) { + return getDrops(itemStack).size() != 0; + } ++ ++ @Override ++ public void tick() { ++ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); ++ net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld(); ++ ++ blockData.getBlock().tick(blockData, level, this.position, level.random); ++ } ++ ++ @Override ++ public void randomTick() { ++ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); ++ net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld(); ++ ++ blockData.getBlock().randomTick(blockData, level, this.position, level.random); ++ } + // Paper end + } +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 aae7f7ab4931db8f955c3055157fe01f99931ec7..e4c15fcbd21f70836c26133ef10f3d0da9b6c238 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -614,4 +614,10 @@ public class CraftBlockData implements BlockData { + + return this.state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CraftBlock.blockFaceToNotch(face), CraftBlockSupport.toNMS(support)); + } ++ // Paper start - Block tick API ++ @Override ++ public boolean isRandomlyTicked() { ++ return this.state.isRandomlyTicking(); ++ } ++ // Paper end + } diff --git a/patches/server/0931-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0931-Add-Velocity-IP-Forwarding-Support.patch new file mode 100644 index 0000000000..7ccbe4bb19 --- /dev/null +++ b/patches/server/0931-Add-Velocity-IP-Forwarding-Support.patch @@ -0,0 +1,211 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 8 Oct 2018 14:36:14 -0400 +Subject: [PATCH] Add Velocity IP Forwarding Support + +While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users +have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. +Further, the BungeeCord IP forwarding protocol still retains essentially its original +form, when there is brand new support for custom login plugin messages in 1.13. + +Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity +of messages, is packed into a binary format that is smaller than BungeeCord's +forwarding, and is integrated into the Minecraft login process by using the 1.13 +login plugin message packet. + +diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5de2dabbc076a9482b1d6c299f1cff74313af74e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java +@@ -0,0 +1,74 @@ ++package com.destroystokyo.paper.proxy; ++ ++import io.papermc.paper.configuration.GlobalConfiguration; ++import com.google.common.net.InetAddresses; ++import com.mojang.authlib.GameProfile; ++import com.mojang.authlib.properties.Property; ++import java.net.InetAddress; ++import java.security.InvalidKeyException; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.UUID; ++ ++import javax.crypto.Mac; ++import javax.crypto.spec.SecretKeySpec; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.world.entity.player.ProfilePublicKey; ++ ++public class VelocityProxy { ++ private static final int SUPPORTED_FORWARDING_VERSION = 1; ++ public static final int MODERN_FORWARDING_WITH_KEY = 2; ++ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; ++ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_FORWARDING_WITH_KEY_V2; ++ public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); ++ ++ public static boolean checkIntegrity(final FriendlyByteBuf buf) { ++ final byte[] signature = new byte[32]; ++ buf.readBytes(signature); ++ ++ final byte[] data = new byte[buf.readableBytes()]; ++ buf.getBytes(buf.readerIndex(), data); ++ ++ try { ++ final Mac mac = Mac.getInstance("HmacSHA256"); ++ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); ++ final byte[] mySignature = mac.doFinal(data); ++ if (!MessageDigest.isEqual(signature, mySignature)) { ++ return false; ++ } ++ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { ++ throw new AssertionError(e); ++ } ++ ++ return true; ++ } ++ ++ public static InetAddress readAddress(final FriendlyByteBuf buf) { ++ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); ++ } ++ ++ public static GameProfile createProfile(final FriendlyByteBuf buf) { ++ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); ++ readProperties(buf, profile); ++ return profile; ++ } ++ ++ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { ++ final int properties = buf.readVarInt(); ++ for (int i1 = 0; i1 < properties; i1++) { ++ final String name = buf.readUtf(Short.MAX_VALUE); ++ final String value = buf.readUtf(Short.MAX_VALUE); ++ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; ++ profile.getProperties().put(name, new Property(name, value, signature)); ++ } ++ } ++ ++ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { ++ return new ProfilePublicKey.Data(buf); ++ } ++ ++ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { ++ return buf.readBoolean() ? buf.readUUID() : orElse; ++ } ++} +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 2f0b2d0f3a3dc02076cee9ab5e6dd6ab931143e3..bf488013e45b9ab97568e587f4dad899498b2f73 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -67,6 +67,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + private ProfilePublicKey.Data profilePublicKeyData; + public String hostname = ""; // CraftBukkit - add field + public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding ++ private int velocityLoginMessageId = -1; // Paper - Velocity support + + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { + this.state = ServerLoginPacketListenerImpl.State.HELLO; +@@ -300,6 +301,16 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + this.state = ServerLoginPacketListenerImpl.State.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.nonce)); + } else { ++ // Paper start - Velocity support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); ++ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); ++ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf); ++ this.connection.send(packet1); ++ return; ++ } ++ // Paper end + // Spigot start + // Paper start - Cache authenticator threads + authenticatorPool.execute(new Runnable() { +@@ -413,6 +424,12 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + public class LoginHandler { + + public void fireEvents() throws Exception { ++ // Paper start - Velocity support ++ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { ++ disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ // Paper end + String playerName = ServerLoginPacketListenerImpl.this.gameProfile.getName(); + java.net.InetAddress address = ((java.net.InetSocketAddress) ServerLoginPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(); + java.net.InetAddress rawAddress = ((java.net.InetSocketAddress) connection.getRawAddress()).getAddress(); // Paper +@@ -461,6 +478,60 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + // Spigot end + + public void handleCustomQueryPacket(ServerboundCustomQueryPacket packet) { ++ // Paper start - Velocity support ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.getTransactionId() == this.velocityLoginMessageId) { ++ net.minecraft.network.FriendlyByteBuf buf = packet.getData(); ++ if (buf == null) { ++ this.disconnect("This server requires you to connect with Velocity."); ++ return; ++ } ++ ++ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { ++ this.disconnect("Unable to verify player details"); ++ return; ++ } ++ ++ int version = buf.readVarInt(); ++ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { ++ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); ++ } ++ ++ java.net.SocketAddress listening = this.connection.getRemoteAddress(); ++ int port = 0; ++ if (listening instanceof java.net.InetSocketAddress) { ++ port = ((java.net.InetSocketAddress) listening).getPort(); ++ } ++ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); ++ ++ this.gameProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); ++ ++ // We should already have this, but, we'll read it out anyway ++ //noinspection NonStrictComparisonCanBeEquality ++ if (version >= com.destroystokyo.paper.proxy.VelocityProxy.MODERN_FORWARDING_WITH_KEY_V2) { ++ final ProfilePublicKey.Data forwardedKeyData = com.destroystokyo.paper.proxy.VelocityProxy.readForwardedKey(buf); ++ final UUID signer = com.destroystokyo.paper.proxy.VelocityProxy.readSignerUuidOrElse(buf, this.gameProfile.getId()); ++ if (this.profilePublicKeyData == null) { ++ try { ++ ServerLoginPacketListenerImpl.validatePublicKey(forwardedKeyData, signer, this.server.getServiceSignatureValidator(), this.server.enforceSecureProfile()); ++ this.profilePublicKeyData = forwardedKeyData; ++ } catch (ProfilePublicKey.ValidationException err) { ++ this.disconnect("Unable to validate forwarded player key"); ++ } ++ } ++ } ++ ++ // Proceed with login ++ authenticatorPool.execute(() -> { ++ try { ++ new LoginHandler().fireEvents(); ++ } catch (Exception ex) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + gameProfile.getName(), ex); ++ } ++ }); ++ return; ++ } ++ // Paper end + this.disconnect(Component.translatable("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 3c9ba17cd0f016a21f56c6b6f8861c512734637a..b443aba38258f501f8f00be6b055f07b709277c4 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -784,7 +784,7 @@ public final class CraftServer implements Server { + @Override + public long getConnectionThrottle() { + // Spigot Start - Automatically set connection throttle for bungee configurations +- if (org.spigotmc.SpigotConfig.bungee) { ++ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Velocity support + return -1; + } else { + return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0931-Block-Ticking-API.patch b/patches/server/0931-Block-Ticking-API.patch deleted file mode 100644 index 43db29d605..0000000000 --- a/patches/server/0931-Block-Ticking-API.patch +++ /dev/null @@ -1,47 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Sun, 26 Dec 2021 13:23:46 -0500 -Subject: [PATCH] Block Ticking API - - -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index bfe9dc935c87e01fb435d8b46ce413b84ca74856..0d47460494135d4ec4c95260de033e054c2f0404 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -736,5 +736,21 @@ public class CraftBlock implements Block { - public boolean isValidTool(ItemStack itemStack) { - return getDrops(itemStack).size() != 0; - } -+ -+ @Override -+ public void tick() { -+ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); -+ net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld(); -+ -+ blockData.getBlock().tick(blockData, level, this.position, level.random); -+ } -+ -+ @Override -+ public void randomTick() { -+ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); -+ net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld(); -+ -+ blockData.getBlock().randomTick(blockData, level, this.position, level.random); -+ } - // Paper end - } -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 aae7f7ab4931db8f955c3055157fe01f99931ec7..e4c15fcbd21f70836c26133ef10f3d0da9b6c238 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java -@@ -614,4 +614,10 @@ public class CraftBlockData implements BlockData { - - return this.state.isFaceSturdy(EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CraftBlock.blockFaceToNotch(face), CraftBlockSupport.toNMS(support)); - } -+ // Paper start - Block tick API -+ @Override -+ public boolean isRandomlyTicked() { -+ return this.state.isRandomlyTicking(); -+ } -+ // Paper end - } diff --git a/patches/server/0932-Add-Velocity-IP-Forwarding-Support.patch b/patches/server/0932-Add-Velocity-IP-Forwarding-Support.patch deleted file mode 100644 index 376b2d6d2a..0000000000 --- a/patches/server/0932-Add-Velocity-IP-Forwarding-Support.patch +++ /dev/null @@ -1,211 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Andrew Steinborn -Date: Mon, 8 Oct 2018 14:36:14 -0400 -Subject: [PATCH] Add Velocity IP Forwarding Support - -While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users -have a lot of problems setting up firewalls or setting up plugins like IPWhitelist. -Further, the BungeeCord IP forwarding protocol still retains essentially its original -form, when there is brand new support for custom login plugin messages in 1.13. - -Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity -of messages, is packed into a binary format that is smaller than BungeeCord's -forwarding, and is integrated into the Minecraft login process by using the 1.13 -login plugin message packet. - -diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5de2dabbc076a9482b1d6c299f1cff74313af74e ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java -@@ -0,0 +1,74 @@ -+package com.destroystokyo.paper.proxy; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import com.google.common.net.InetAddresses; -+import com.mojang.authlib.GameProfile; -+import com.mojang.authlib.properties.Property; -+import java.net.InetAddress; -+import java.security.InvalidKeyException; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.UUID; -+ -+import javax.crypto.Mac; -+import javax.crypto.spec.SecretKeySpec; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.world.entity.player.ProfilePublicKey; -+ -+public class VelocityProxy { -+ private static final int SUPPORTED_FORWARDING_VERSION = 1; -+ public static final int MODERN_FORWARDING_WITH_KEY = 2; -+ public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; -+ public static final byte MAX_SUPPORTED_FORWARDING_VERSION = MODERN_FORWARDING_WITH_KEY_V2; -+ public static final ResourceLocation PLAYER_INFO_CHANNEL = new ResourceLocation("velocity", "player_info"); -+ -+ public static boolean checkIntegrity(final FriendlyByteBuf buf) { -+ final byte[] signature = new byte[32]; -+ buf.readBytes(signature); -+ -+ final byte[] data = new byte[buf.readableBytes()]; -+ buf.getBytes(buf.readerIndex(), data); -+ -+ try { -+ final Mac mac = Mac.getInstance("HmacSHA256"); -+ mac.init(new SecretKeySpec(GlobalConfiguration.get().proxies.velocity.secret.getBytes(java.nio.charset.StandardCharsets.UTF_8), "HmacSHA256")); -+ final byte[] mySignature = mac.doFinal(data); -+ if (!MessageDigest.isEqual(signature, mySignature)) { -+ return false; -+ } -+ } catch (final InvalidKeyException | NoSuchAlgorithmException e) { -+ throw new AssertionError(e); -+ } -+ -+ return true; -+ } -+ -+ public static InetAddress readAddress(final FriendlyByteBuf buf) { -+ return InetAddresses.forString(buf.readUtf(Short.MAX_VALUE)); -+ } -+ -+ public static GameProfile createProfile(final FriendlyByteBuf buf) { -+ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUtf(16)); -+ readProperties(buf, profile); -+ return profile; -+ } -+ -+ private static void readProperties(final FriendlyByteBuf buf, final GameProfile profile) { -+ final int properties = buf.readVarInt(); -+ for (int i1 = 0; i1 < properties; i1++) { -+ final String name = buf.readUtf(Short.MAX_VALUE); -+ final String value = buf.readUtf(Short.MAX_VALUE); -+ final String signature = buf.readBoolean() ? buf.readUtf(Short.MAX_VALUE) : null; -+ profile.getProperties().put(name, new Property(name, value, signature)); -+ } -+ } -+ -+ public static ProfilePublicKey.Data readForwardedKey(FriendlyByteBuf buf) { -+ return new ProfilePublicKey.Data(buf); -+ } -+ -+ public static UUID readSignerUuidOrElse(FriendlyByteBuf buf, UUID orElse) { -+ return buf.readBoolean() ? buf.readUUID() : orElse; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 2f0b2d0f3a3dc02076cee9ab5e6dd6ab931143e3..bf488013e45b9ab97568e587f4dad899498b2f73 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -67,6 +67,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - private ProfilePublicKey.Data profilePublicKeyData; - public String hostname = ""; // CraftBukkit - add field - public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding -+ private int velocityLoginMessageId = -1; // Paper - Velocity support - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -300,6 +301,16 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - this.state = ServerLoginPacketListenerImpl.State.KEY; - this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.nonce)); - } else { -+ // Paper start - Velocity support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt(); -+ net.minecraft.network.FriendlyByteBuf buf = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()); -+ buf.writeByte(com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ net.minecraft.network.protocol.login.ClientboundCustomQueryPacket packet1 = new net.minecraft.network.protocol.login.ClientboundCustomQueryPacket(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, buf); -+ this.connection.send(packet1); -+ return; -+ } -+ // Paper end - // Spigot start - // Paper start - Cache authenticator threads - authenticatorPool.execute(new Runnable() { -@@ -413,6 +424,12 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - public class LoginHandler { - - public void fireEvents() throws Exception { -+ // Paper start - Velocity support -+ if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ disconnect("This server requires you to connect with Velocity."); -+ return; -+ } -+ // Paper end - String playerName = ServerLoginPacketListenerImpl.this.gameProfile.getName(); - java.net.InetAddress address = ((java.net.InetSocketAddress) ServerLoginPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(); - java.net.InetAddress rawAddress = ((java.net.InetSocketAddress) connection.getRawAddress()).getAddress(); // Paper -@@ -461,6 +478,60 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - // Spigot end - - public void handleCustomQueryPacket(ServerboundCustomQueryPacket packet) { -+ // Paper start - Velocity support -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.getTransactionId() == this.velocityLoginMessageId) { -+ net.minecraft.network.FriendlyByteBuf buf = packet.getData(); -+ if (buf == null) { -+ this.disconnect("This server requires you to connect with Velocity."); -+ return; -+ } -+ -+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { -+ this.disconnect("Unable to verify player details"); -+ return; -+ } -+ -+ int version = buf.readVarInt(); -+ if (version > com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION) { -+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + com.destroystokyo.paper.proxy.VelocityProxy.MAX_SUPPORTED_FORWARDING_VERSION); -+ } -+ -+ java.net.SocketAddress listening = this.connection.getRemoteAddress(); -+ int port = 0; -+ if (listening instanceof java.net.InetSocketAddress) { -+ port = ((java.net.InetSocketAddress) listening).getPort(); -+ } -+ this.connection.address = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port); -+ -+ this.gameProfile = com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf); -+ -+ // We should already have this, but, we'll read it out anyway -+ //noinspection NonStrictComparisonCanBeEquality -+ if (version >= com.destroystokyo.paper.proxy.VelocityProxy.MODERN_FORWARDING_WITH_KEY_V2) { -+ final ProfilePublicKey.Data forwardedKeyData = com.destroystokyo.paper.proxy.VelocityProxy.readForwardedKey(buf); -+ final UUID signer = com.destroystokyo.paper.proxy.VelocityProxy.readSignerUuidOrElse(buf, this.gameProfile.getId()); -+ if (this.profilePublicKeyData == null) { -+ try { -+ ServerLoginPacketListenerImpl.validatePublicKey(forwardedKeyData, signer, this.server.getServiceSignatureValidator(), this.server.enforceSecureProfile()); -+ this.profilePublicKeyData = forwardedKeyData; -+ } catch (ProfilePublicKey.ValidationException err) { -+ this.disconnect("Unable to validate forwarded player key"); -+ } -+ } -+ } -+ -+ // Proceed with login -+ authenticatorPool.execute(() -> { -+ try { -+ new LoginHandler().fireEvents(); -+ } catch (Exception ex) { -+ disconnect("Failed to verify username!"); -+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + gameProfile.getName(), ex); -+ } -+ }); -+ return; -+ } -+ // Paper end - this.disconnect(Component.translatable("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 32eb6efa94bc5ea3b516cbbd7e53f1460c23154d..964ec590ef5302576ecb3ba2b8ea95dbc2acf103 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -784,7 +784,7 @@ public final class CraftServer implements Server { - @Override - public long getConnectionThrottle() { - // Spigot Start - Automatically set connection throttle for bungee configurations -- if (org.spigotmc.SpigotConfig.bungee) { -+ if (org.spigotmc.SpigotConfig.bungee || io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { // Paper - Velocity support - return -1; - } else { - return this.configuration.getInt("settings.connection-throttle"); diff --git a/patches/server/0932-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch b/patches/server/0932-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch new file mode 100644 index 0000000000..81d6721a69 --- /dev/null +++ b/patches/server/0932-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shane Freeder +Date: Thu, 11 Aug 2022 14:37:33 +0100 +Subject: [PATCH] Use thread safe random in ServerLoginPacketListenerImpl + + +diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index bf488013e45b9ab97568e587f4dad899498b2f73..88a849a21d6e39fd70f6e7b554528da1a5a7dd57 100644 +--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -52,7 +52,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se + private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); + static final Logger LOGGER = LogUtils.getLogger(); + private static final int MAX_TICKS_BEFORE_LOGIN = 600; +- private static final RandomSource RANDOM = RandomSource.create(); ++ private static final RandomSource RANDOM = new org.bukkit.craftbukkit.util.RandomSourceWrapper(new java.util.Random()); // Paper - This is called across threads, make safe + private final byte[] nonce; + final MinecraftServer server; + public final Connection connection; diff --git a/patches/server/0933-Add-NamespacedKey-biome-methods.patch b/patches/server/0933-Add-NamespacedKey-biome-methods.patch new file mode 100644 index 0000000000..3266ac0fb2 --- /dev/null +++ b/patches/server/0933-Add-NamespacedKey-biome-methods.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Roy <10731363+JRoy@users.noreply.github.com> +Date: Sun, 14 Aug 2022 12:23:11 -0400 +Subject: [PATCH] Add NamespacedKey biome methods + +Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 0a4e9b0957c9b0abbb88d472b5b3d7946c256af2..1628913b1e9b91e68dcd942a38da4aed95b12d4a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -604,6 +604,19 @@ public final class CraftMagicNumbers implements UnsafeValues { + Preconditions.checkArgument(material.isBlock(), material + " is not a block"); + return getBlock(material).hasCollision; + } ++ ++ @Override ++ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); ++ } ++ ++ @Override ++ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { ++ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; ++ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); ++ cra.setBiome(x, y, z, biomeBase); ++ } + // Paper end + + /** diff --git a/patches/server/0933-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch b/patches/server/0933-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch deleted file mode 100644 index 81d6721a69..0000000000 --- a/patches/server/0933-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder -Date: Thu, 11 Aug 2022 14:37:33 +0100 -Subject: [PATCH] Use thread safe random in ServerLoginPacketListenerImpl - - -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index bf488013e45b9ab97568e587f4dad899498b2f73..88a849a21d6e39fd70f6e7b554528da1a5a7dd57 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -52,7 +52,7 @@ public class ServerLoginPacketListenerImpl implements TickablePacketListener, Se - private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); - static final Logger LOGGER = LogUtils.getLogger(); - private static final int MAX_TICKS_BEFORE_LOGIN = 600; -- private static final RandomSource RANDOM = RandomSource.create(); -+ private static final RandomSource RANDOM = new org.bukkit.craftbukkit.util.RandomSourceWrapper(new java.util.Random()); // Paper - This is called across threads, make safe - private final byte[] nonce; - final MinecraftServer server; - public final Connection connection; diff --git a/patches/server/0934-Add-NamespacedKey-biome-methods.patch b/patches/server/0934-Add-NamespacedKey-biome-methods.patch deleted file mode 100644 index 3266ac0fb2..0000000000 --- a/patches/server/0934-Add-NamespacedKey-biome-methods.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Josh Roy <10731363+JRoy@users.noreply.github.com> -Date: Sun, 14 Aug 2022 12:23:11 -0400 -Subject: [PATCH] Add NamespacedKey biome methods - -Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com> - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 0a4e9b0957c9b0abbb88d472b5b3d7946c256af2..1628913b1e9b91e68dcd942a38da4aed95b12d4a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -604,6 +604,19 @@ public final class CraftMagicNumbers implements UnsafeValues { - Preconditions.checkArgument(material.isBlock(), material + " is not a block"); - return getBlock(material).hasCollision; - } -+ -+ @Override -+ public org.bukkit.NamespacedKey getBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getKey(cra.getHandle().getBiome(new net.minecraft.core.BlockPos(x, y, z)).value())); -+ } -+ -+ @Override -+ public void setBiomeKey(org.bukkit.RegionAccessor accessor, int x, int y, int z, org.bukkit.NamespacedKey biomeKey) { -+ org.bukkit.craftbukkit.CraftRegionAccessor cra = (org.bukkit.craftbukkit.CraftRegionAccessor) accessor; -+ net.minecraft.core.Holder biomeBase = cra.getHandle().registryAccess().registryOrThrow(net.minecraft.core.Registry.BIOME_REGISTRY).getHolderOrThrow(net.minecraft.resources.ResourceKey.create(net.minecraft.core.Registry.BIOME_REGISTRY, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(biomeKey))); -+ cra.setBiome(x, y, z, biomeBase); -+ } - // Paper end - - /** diff --git a/patches/server/0934-Fix-plugin-loggers-on-server-shutdown.patch b/patches/server/0934-Fix-plugin-loggers-on-server-shutdown.patch new file mode 100644 index 0000000000..e2c9ffe621 --- /dev/null +++ b/patches/server/0934-Fix-plugin-loggers-on-server-shutdown.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= +Date: Sat, 5 Jun 2021 13:45:15 +0200 +Subject: [PATCH] Fix plugin loggers on server shutdown + + +diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc +--- /dev/null ++++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java +@@ -0,0 +1,26 @@ ++package io.papermc.paper.log; ++ ++import java.util.logging.LogManager; ++ ++public class CustomLogManager extends LogManager { ++ private static CustomLogManager instance; ++ ++ public CustomLogManager() { ++ instance = this; ++ } ++ ++ @Override ++ public void reset() { ++ // Ignore calls to this method ++ } ++ ++ private void superReset() { ++ super.reset(); ++ } ++ ++ public static void forceReset() { ++ if (instance != null) { ++ instance.superReset(); ++ } ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index dd9ab51e904be2f2f2a2981d4f0f6638a6895e8d..d2161a3c3b9a2b2d463ac778656c95167c10a49d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1025,6 +1025,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sat, 5 Jun 2021 13:45:15 +0200 -Subject: [PATCH] Fix plugin loggers on server shutdown - - -diff --git a/src/main/java/io/papermc/paper/log/CustomLogManager.java b/src/main/java/io/papermc/paper/log/CustomLogManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c1d3bac79bb8b4796c013ff4472f75dcd79602dc ---- /dev/null -+++ b/src/main/java/io/papermc/paper/log/CustomLogManager.java -@@ -0,0 +1,26 @@ -+package io.papermc.paper.log; -+ -+import java.util.logging.LogManager; -+ -+public class CustomLogManager extends LogManager { -+ private static CustomLogManager instance; -+ -+ public CustomLogManager() { -+ instance = this; -+ } -+ -+ @Override -+ public void reset() { -+ // Ignore calls to this method -+ } -+ -+ private void superReset() { -+ super.reset(); -+ } -+ -+ public static void forceReset() { -+ if (instance != null) { -+ instance.superReset(); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index dd9ab51e904be2f2f2a2981d4f0f6638a6895e8d..d2161a3c3b9a2b2d463ac778656c95167c10a49d 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1025,6 +1025,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Sat, 18 Dec 2021 19:43:36 +0100 +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). + +Original patch by: MeFisto94 +Co-authored-by: =?UTF-8?q?Dani=C3=ABl=20Goossens?= +Co-authored-by: Nassim Jahnke + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 47657f20652a80f50a2e46207c9c05d1a12111b4..c2c01988bf3b6fbb0a7a4716373c2ff0cffce27d 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -2169,6 +2169,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + } + ++ // Paper start - Fix MC-162253 ++ /** ++ * Returns the light mask for the given chunk consisting of all non-empty sections that may need sending. ++ */ ++ private BitSet lightMask(final LevelChunk chunk) { ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); ++ final BitSet mask = new BitSet(this.lightEngine.getLightSectionCount()); ++ ++ // There are 2 more light sections than chunk sections so when iterating over ++ // sections we have to increment the index by 1 ++ for (int i = 0; i < sections.length; i++) { ++ if (!sections[i].hasOnlyAir()) { ++ // Whenever a section is not empty, it can change lighting for the section itself (i + 1), the section below, and the section above ++ mask.set(i); ++ mask.set(i + 1); ++ mask.set(i + 2); ++ i++; // We can skip the already set upper section ++ } ++ } ++ return mask; ++ } ++ ++ /** ++ * Returns the ceiling light mask of all sections that are equal or lower to the highest non-empty section. ++ */ ++ private BitSet ceilingLightMask(final LevelChunk chunk) { ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); ++ for (int i = sections.length - 1; i >= 0; i--) { ++ if (!sections[i].hasOnlyAir()) { ++ // Add one to get the light section, one because blocks in the section above may change, and another because BitSet's toIndex is exclusive ++ final int highest = i + 3; ++ final BitSet mask = new BitSet(highest); ++ mask.set(0, highest); ++ return mask; ++ } ++ } ++ return new BitSet(); ++ } ++ // Paper end - Fix MC-162253 ++ + // Paper start - Anti-Xray - Bypass + private void playerLoadedChunk(ServerPlayer player, MutableObject> cachedDataPackets, LevelChunk chunk) { + if (cachedDataPackets.getValue() == null) { +@@ -2177,6 +2217,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); + player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> { ++ // Paper start - Fix MC-162253 ++ final int viewDistance = getEffectiveViewDistance(); ++ final int playerChunkX = player.getBlockX() >> 4; ++ final int playerChunkZ = player.getBlockZ() >> 4; ++ ++ // For all loaded neighbours, send sky light for empty sections above highest non-empty section (+1) of the center chunk ++ // otherwise the client will try to calculate lighting there on its own ++ final BitSet lightMask = lightMask(chunk); ++ if (!lightMask.isEmpty()) { ++ for (int x = -1; x <= 1; x++) { ++ for (int z = -1; z <= 1; z++) { ++ if (x == 0 && z == 0) { ++ continue; ++ } ++ ++ if (!chunk.isNeighbourLoaded(x, z)) { ++ continue; ++ } ++ ++ final int neighborChunkX = chunk.getPos().x + x; ++ final int neighborChunkZ = chunk.getPos().z + z; ++ final int distX = Math.abs(playerChunkX - neighborChunkX); ++ final int distZ = Math.abs(playerChunkZ - neighborChunkZ); ++ if (Math.max(distX, distZ) > viewDistance) { ++ continue; ++ } ++ ++ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z); ++ final BitSet updateLightMask = (BitSet) lightMask.clone(); ++ updateLightMask.andNot(ceilingLightMask(neighbor)); ++ if (updateLightMask.isEmpty()) { ++ continue; ++ } ++ ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(new ChunkPos(neighborChunkX, neighborChunkZ), this.lightEngine, updateLightMask, null, true)); ++ } ++ } ++ } ++ // Paper end - Fix MC-162253 + return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s); + })); + // Paper end diff --git a/patches/server/0936-Workaround-for-client-lag-spikes-MC-162253.patch b/patches/server/0936-Workaround-for-client-lag-spikes-MC-162253.patch deleted file mode 100644 index 7da4def3c8..0000000000 --- a/patches/server/0936-Workaround-for-client-lag-spikes-MC-162253.patch +++ /dev/null @@ -1,114 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Brokkonaut -Date: Sat, 18 Dec 2021 19:43:36 +0100 -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). - -Original patch by: MeFisto94 -Co-authored-by: =?UTF-8?q?Dani=C3=ABl=20Goossens?= -Co-authored-by: Nassim Jahnke - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 47657f20652a80f50a2e46207c9c05d1a12111b4..c2c01988bf3b6fbb0a7a4716373c2ff0cffce27d 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -2169,6 +2169,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - } - -+ // Paper start - Fix MC-162253 -+ /** -+ * Returns the light mask for the given chunk consisting of all non-empty sections that may need sending. -+ */ -+ private BitSet lightMask(final LevelChunk chunk) { -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); -+ final BitSet mask = new BitSet(this.lightEngine.getLightSectionCount()); -+ -+ // There are 2 more light sections than chunk sections so when iterating over -+ // sections we have to increment the index by 1 -+ for (int i = 0; i < sections.length; i++) { -+ if (!sections[i].hasOnlyAir()) { -+ // Whenever a section is not empty, it can change lighting for the section itself (i + 1), the section below, and the section above -+ mask.set(i); -+ mask.set(i + 1); -+ mask.set(i + 2); -+ i++; // We can skip the already set upper section -+ } -+ } -+ return mask; -+ } -+ -+ /** -+ * Returns the ceiling light mask of all sections that are equal or lower to the highest non-empty section. -+ */ -+ private BitSet ceilingLightMask(final LevelChunk chunk) { -+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); -+ for (int i = sections.length - 1; i >= 0; i--) { -+ if (!sections[i].hasOnlyAir()) { -+ // Add one to get the light section, one because blocks in the section above may change, and another because BitSet's toIndex is exclusive -+ final int highest = i + 3; -+ final BitSet mask = new BitSet(highest); -+ mask.set(0, highest); -+ return mask; -+ } -+ } -+ return new BitSet(); -+ } -+ // Paper end - Fix MC-162253 -+ - // Paper start - Anti-Xray - Bypass - private void playerLoadedChunk(ServerPlayer player, MutableObject> cachedDataPackets, LevelChunk chunk) { - if (cachedDataPackets.getValue() == null) { -@@ -2177,6 +2217,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk); - player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> { -+ // Paper start - Fix MC-162253 -+ final int viewDistance = getEffectiveViewDistance(); -+ final int playerChunkX = player.getBlockX() >> 4; -+ final int playerChunkZ = player.getBlockZ() >> 4; -+ -+ // For all loaded neighbours, send sky light for empty sections above highest non-empty section (+1) of the center chunk -+ // otherwise the client will try to calculate lighting there on its own -+ final BitSet lightMask = lightMask(chunk); -+ if (!lightMask.isEmpty()) { -+ for (int x = -1; x <= 1; x++) { -+ for (int z = -1; z <= 1; z++) { -+ if (x == 0 && z == 0) { -+ continue; -+ } -+ -+ if (!chunk.isNeighbourLoaded(x, z)) { -+ continue; -+ } -+ -+ final int neighborChunkX = chunk.getPos().x + x; -+ final int neighborChunkZ = chunk.getPos().z + z; -+ final int distX = Math.abs(playerChunkX - neighborChunkX); -+ final int distZ = Math.abs(playerChunkZ - neighborChunkZ); -+ if (Math.max(distX, distZ) > viewDistance) { -+ continue; -+ } -+ -+ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z); -+ final BitSet updateLightMask = (BitSet) lightMask.clone(); -+ updateLightMask.andNot(ceilingLightMask(neighbor)); -+ if (updateLightMask.isEmpty()) { -+ continue; -+ } -+ -+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(new ChunkPos(neighborChunkX, neighborChunkZ), this.lightEngine, updateLightMask, null, true)); -+ } -+ } -+ } -+ // Paper end - Fix MC-162253 - return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s); - })); - // Paper end diff --git a/work/Bukkit b/work/Bukkit index 2683d76602..9ae3f10f8f 160000 --- a/work/Bukkit +++ b/work/Bukkit @@ -1 +1 @@ -Subproject commit 2683d766020628844637e800d4ec12fd19cc674a +Subproject commit 9ae3f10f8fa50a825af823131c468c36da3be880 diff --git a/work/CraftBukkit b/work/CraftBukkit index 5901d580a4..5cc9c022a0 160000 --- a/work/CraftBukkit +++ b/work/CraftBukkit @@ -1 +1 @@ -Subproject commit 5901d580a4902919b10fc23a07d40eda50497634 +Subproject commit 5cc9c022a0bd297f348d3c7e24e06227dca40c23 diff --git a/work/Spigot b/work/Spigot index e53686f7e0..4c157bb49a 160000 --- a/work/Spigot +++ b/work/Spigot @@ -1 +1 @@ -Subproject commit e53686f7e0406156c8fa91a6e7748fbfed19c3a4 +Subproject commit 4c157bb49aa5705967b066e9c2dec5520f720b22 -- cgit v1.2.3